Skip to main content

Integration with other cddl-codegen libraries

This guide is written in general for integrating with other libraries generated by cddl-codegen, but in particular references CML (cardano-multiplatform-lib) for examples. Most things referencing CML will be relevant to other common cddl-codegen generated libraries used as dependencies.

Common cddl-codegen traits

When generating a library that has as a dependency another cddl-codegen-generated library you can share the common cddl-codegen types/traits like Deserialize, RawBytesEncoding, etc. Remember to pass in --common-import-override tag. For CML we pass in --common-import-override=cml_core. This is where all the common cddl-codegen traits are located so we can avoid having duplicate incompatible traits in other libraries.

CML macros

In CML we have macros for implementing WASM conversions and JSON/bytes. We pass in --wasm-cbor-json-api-macro=cml_core_wasm::impl_wasm_cbor_json_api and --wasm-conversions-macro=cml_core_wasm::impl_wasm_conversions which are both located in cml_core_wasm. This drastically reduces WASM wrapper boilerplate.

For list wrappers there is additionally --wasm-list-macro=cml_core_wasm::impl_wasm_list, which collapses each Vec<T>-backed list wrapper (the struct + new/len/get/add accessor block + conversion traits) into a single impl_wasm_list!(rust_elem, wasm_elem, WasmName, needs_into, is_copy); invocation. It supersedes --wasm-conversions-macro for list wrappers (the list macro emits the conversions itself). Maps are unaffected.

Externally defined types

_CDDL_CODEGEN_EXTERN_TYPE_ vs _CDDL_CODEGEN_RAW_BYTES_TYPE_

There are two ways to have explicitly externally-defined types in cddl-codegen: _CDDL_CODEGEN_EXTERN_TYPE_ and _CDDL_CODEGEN_RAW_BYTES_TYPE_. It is important to choose the appropriate one. If the type was defined originally as _CDDL_CODEGEN_RAW_BYTES_TYPE_ in CML (or whatever library) then it is important to define it using this so it will be encoded correctly. If the type was either defined using _CDDL_CODEGEN_EXTERN_TYPE_ (hand-written) or was explicitly defined normally in the dependency lib (e.g. CML) then use _CDDL_CODEGEN_EXTERN_TYPE_.

Import pathing

If your input directory includes a /_CDDL_CODEGEN_EXTERN_DEPS_DIR_/ directory, everything inside will be treated as an external dependency. This allows users to specify the import tree of any dependency CDDL structures. You can define these types as _CDDL_CODEGEN_EXTERN_TYPE_ if it is entirely self-contained or _CDDL_CODEGEN_RAW_BYTES_TYPE_ if it is CBOR bytes. For an example see the _CDDL_CODEGEN_EXTERN_DEPS_DIR_ directory inside of the specs/multiera. Each folder within the directory will be treated as a separate dependency. Nothing will be generated by any definitions inside this folder. You will still need to specify the dependency inside the Cargo.toml file afterwards — a one-time edit: regeneration merges into the existing manifest rather than overwriting it, so hand-added dependencies survive (see Output format).

When generating wasm bindings (--wasm=true) and a dependency's wasm-bindgen wrappers live in a separate crate (the split <dep> / <dep>-wasm layout cddl-codegen itself generates), map it with --extern-wasm-crate <dep>=<wasm_crate> so the wasm pass imports and qualifies that dependency's boundary types through the wasm crate. Without the mapping the dependency is assumed to follow the single-crate convention (its rust types are themselves #[wasm_bindgen]-annotated). Either way the generated wasm Cargo.toml needs the appropriate dependency entries added by hand (both crates under the split layout).

When the consumer's spec uses list/map shapes over a dependency's extern types ([* dep_foo], {* uint => dep_foo}), it would otherwise re-mint the dependency's own collection wrappers — two #[wasm_bindgen] classes of the same name fail to link a single wasm cdylib (duplicate symbol: __wbg_<wrapper>_free). Point --extern-wrapper-index <dep>=<path/to/collections.rs> at the dependency's committed wrapper index (every wasm crate emits wasm/src/generated/collections.rs; see Output format) so the consumer defers to the dependency's wrappers instead of re-minting them. Regenerate the dependency before the consumer — the index is committed generated output that must reflect the dependency's current wrapper inventory.

Non-black-box types

Another important detail, demonstrated in the above multiera CDDL spec, is that when using external types that aren't 100% self-contained (i.e. can't be treated as a black box that implements Serialize + Deserialize, nor as CBOR bytes implementing RawBytesEncoding) like uint aliases should be explicitly defined and then removed afterwards. Use the above directory/pathing tip.