This page describes the structure of the packages, and the internal source layout of each package.
Largely, there are five kinds of packages- the connector, the core, the plugin, the user facing (contract-case) libraries, the documentation, and the maintainer packages.
The connector is the boundary that the user-facing libraries should include.
- case-connector: The gRPC connector that is the entrypoint for contract-case in other languages. It can be used as a server to call contract-case from languages that support gRPC, and it also exposes the same interface as JS bindings, in case the target language can call javascript directly.
- case-connector-proto: The protobuf / gRPC definitions for case-connector. This is separate so that language dependent implementations can more comfortably include it.
The main case-core package implements the core contract testing engine, which sets up the mocks, calls the state handlers and triggers, and writes the contract. General behaviour goes in here.
The core-plugin-{xxx} packages contain the types and behaviour for the plugins that ContractCase includes. They're built using the same plugin framework available to external users, and should not
rely on internal knowledge or undocumented APIs.
case-core-plugin-{xxx}: Implements the behaviours for various core plugins that ContractCase ships with (eg, thehttpplugin contains the mock client, mock server, and any HTTP related matchers)case-core-plugin-{xxx}-dsl: Contains the types for describing the matchers or mocks for each plugin. This is a separate package so that packages that expose these (eg the definition-dsl) don't need to depend on the behaviour package.
There are some additional packages that are part of the core layer:
case-entities: Base types and helpers imported by several packages. Separate to avoid circular dependencies. Custom plugins should not need to import this package. If you feel you need to import something from this package outside the core, it probably means something that's in this package should move out, please open an issue.
These contain the interface that plugins rely on - it describes the boundary between the core and the plugin interfaces. External plugins should depend on these.
case-plugin-base: Base behaviour needed to implement plugins for ContractCasecase-plugin-dsl-types: Base types needed to implement the type-only DSL layer for ContractCase.
These are the packages that users will actually import and use in their tests. These generally depend on case-connector, and know how to start the server. See adding DSLs for details.
contract-case-jest: The user-facing Jest library for ContractCasedsl-java: The user-facing Java library for ContractCase (this should be renamedcontract-case-java)
Additionally, there are two cross-platform packages:
contract-case-cli: The cross-platform CLI used for contract manipulation and contacting a broker.case-definition-dsl: The JSii layer that just describes the definition language for defining interactions to be run. It currently re-exports the http-plugin-dsl types (and the function-plugin-dsl types). This package is being deprecated in favour of the definition generator (@contract-case/definition-generator), which generates the DSL source files instead - see DSL Generators.
The main documentation is built in the documentation package. Any custom code that's specific to contract-case is in a package prefixed with documentation - eg documentation-matchers-generator generates the matcher documentation. None of the documentation packages are published to any package managers.
case-definition-generator(published as@contract-case/definition-generator): Generates DSL source files for the matcher DSLs from a JSON definition. See DSL Generators.
case-maintainer-config: Common settings for maintaining packages (doesn't include the eslint config, as it needs specific naming conventions).eslint-config-case-maintainer: Common eslint settings for all typescript packages
Most of the javascript packages follow the same general layout, unless there's a technology-specific reason not to do it this way:
- boundaries: For the DSL that will be used by different languages. Everything in here should only be for exposing ContractCase to the users, and there should be no behaviour
- connectors: General connections outside ContractCase (including incoming)
- core: The engine that runs the mocks
- diffmatch: The engine that runs the data matching
- entities: The base layer of application wide concepts
No lower layer is allowed to include code from an upper layer.
The source files follow the direct-child include rule, which aims to provide the following benefits:
- It is clear from the structure which code is globally imported and which is used only locally, which means you can assess impact of changes without checking
- It is clear which modules depend on each other, which means you can assess the appropriate interface easily
- Where to put new code is clear
- Where to look to find existing code is clear
- A module can only import modules from:
- The same directory - eg:
- code in
src/a/a.tsmay import fromsrc/a/b(which is eithersrc/a/b.tsorsrc/a/b/index.ts) - code in
src/a/a.tsmay not import fromsrc/a/b/c.tsorsrc/a/b/c/d
- code in
- One of the very top level directories under
src- these are where common code goes. For example, code anywhere may import fromsrc/components. This means:- code in
src/pages/b/c/d.tsmay import fromsrc/components/a - code in
src/pages/b/c/d.tsmay not import fromsrc/components/a/b - Any module in a common directory (eg
src/*/) must be imported in more than one place. If the code changes so that previously common code is only used in one other module, it should be moved out to be in the same directory as the module.
- code in
- If a module is not imported by the main module in a directory, then it should be down one more level.
- Eg, if
src/a/a.tsimportssrc/a/b.tswhich importssrc/a/c.ts(which is not imported bysrc/a/a.ts), then b and c move down one level:
// Not like this
src/
├─ a/
│ ├─ index.ts // re-exports ./a
│ ├─ a.ts // imports ./b
│ ├─ b.ts // imports ./c
│ ├─ c.ts
so that it looks like this:
src/
├─ a/
│ ├─ index.ts // re-exports ./a
│ ├─ a.ts // imports ./b
│ ├─ b/
│ │ ├─ index.ts // re-exports ./b (imported by a)
│ │ ├─ b.ts // imports ./c
│ │ ├─ c.ts
This structure has the advantage that you can see that b is only used by a, and c is only used by b
- The main module in a directory is named the same as the directory - eg, the
main export from
src/aissrc/a/a.ts. This is so that your editor tabs doesn't get cluttered withindex.tsfiles that contain different code. index.tsfiles only contain re-exports. They describe the public interface of the module that is in that folder, and may re-export anything according to the rules below. This code follows the Direct Child Include Rule:
Although there's no requirement to follow a particular structure for the other languages, the preference is to follow the philosophy above, with modifications so that it remains idiomatic in the language.