├── rust-toolchain ├── tests ├── extern-deps │ ├── inputs │ │ ├── qux.cddl │ │ ├── a │ │ │ ├── mod.cddl │ │ │ └── c │ │ │ │ └── foo.cddl │ │ ├── _CDDL_CODEGEN_EXTERN_DEPS_DIR_ │ │ │ └── extern_dep_crate │ │ │ │ ├── sub │ │ │ │ └── module.cddl │ │ │ │ └── mod.cddl │ │ ├── b │ │ │ └── bar.cddl │ │ └── lib.cddl │ └── tests.rs ├── multifile │ ├── inputs │ │ ├── a │ │ │ ├── mod.cddl │ │ │ └── c │ │ │ │ └── foo.cddl │ │ ├── qux.cddl │ │ ├── b │ │ │ └── bar.cddl │ │ └── lib.cddl │ └── tests.rs ├── extern-dep-crate │ ├── src │ │ ├── sub │ │ │ ├── mod.rs │ │ │ └── module.rs │ │ ├── cbor_encodings.rs │ │ ├── ordered_hash_map.rs │ │ ├── lib.rs │ │ ├── error.rs │ │ └── serialization.rs │ ├── README.md │ └── Cargo.toml ├── raw-bytes │ ├── input.cddl │ └── tests.rs ├── raw-bytes-preserve │ ├── input.cddl │ └── tests.rs ├── json │ ├── input.cddl │ └── tests.rs ├── external_wasm_raw_bytes_def ├── external_rust_raw_bytes_def ├── comment-dsl │ ├── input.cddl │ └── tests.rs ├── rust-wasm-split │ ├── input.cddl │ └── tests.rs ├── canonical │ └── input.cddl ├── external_json_impls ├── external_wasm_defs ├── custom_serialization ├── external_rust_defs ├── external_rust_defs_compiles_with_json_preserve ├── custom_serialization_preserve ├── preserve-encodings │ └── input.cddl ├── deser_test └── core │ └── input.cddl ├── docs ├── .gitattributes ├── static │ ├── img │ │ ├── output.png │ │ ├── output2.png │ │ ├── output3.png │ │ ├── output4.png │ │ └── output5.png │ └── icons │ │ ├── danger-icon.svg │ │ └── info-icon.svg ├── src │ ├── fonts │ │ ├── Manrope-Bold.ttf │ │ ├── Manrope-Light.ttf │ │ ├── Manrope-Medium.ttf │ │ ├── Manrope-Regular.ttf │ │ └── Manrope-SemiBold.ttf │ └── css │ │ └── custom.css ├── babel.config.js ├── postcss.config.js ├── crowdin.yml ├── tailwind.config.js ├── sidebars.js ├── .gitignore ├── README.md ├── docs │ ├── index.md │ ├── getting_started.mdx │ ├── output_format.mdx │ ├── wasm_differences.mdx │ ├── current_capacities.mdx │ ├── integration-other.mdx │ ├── command_line_flags.mdx │ └── comment_dsl.mdx ├── package.json └── docusaurus.config.js ├── README.md ├── src ├── lib.rs ├── rust_reserved.rs ├── cli.rs ├── dep_graph.rs ├── utils.rs ├── main.rs └── test.rs ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── deploy-docs.yml ├── static ├── Cargo_rust.toml ├── Cargo_json_gen.toml ├── Cargo_wasm.toml ├── package.json ├── ordered_hash_map_schemars.rs ├── serialization_non_force_canonical.rs ├── raw_bytes_encoding.rs ├── serialization.rs ├── ordered_hash_map.rs ├── package_json_schemas.json ├── ordered_hash_map_json.rs ├── json-ts-types.js ├── serialization_non_preserve.rs ├── serialization_preserve_non_force_canonical.rs ├── run-json2ts.js ├── serialization_preserve_force_canonical.rs ├── serialization_preserve.rs └── error.rs ├── .gitignore ├── example ├── README.md ├── test.cddl ├── serialization_unit_tests.rs └── supported.cddl ├── Cargo.toml ├── LICENSE ├── supported.cddl └── GENERATING_SERIALIZATION_LIB.md /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /tests/extern-deps/inputs/qux.cddl: -------------------------------------------------------------------------------- 1 | qux = [2, text] -------------------------------------------------------------------------------- /tests/multifile/inputs/a/mod.cddl: -------------------------------------------------------------------------------- 1 | baz = [0, uint] -------------------------------------------------------------------------------- /tests/multifile/inputs/qux.cddl: -------------------------------------------------------------------------------- 1 | qux = [2, text] -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/sub/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod module; -------------------------------------------------------------------------------- /tests/extern-deps/inputs/a/mod.cddl: -------------------------------------------------------------------------------- 1 | baz = [0, uint] -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/sub/module.rs: -------------------------------------------------------------------------------- 1 | pub type UintTypedef = u64; -------------------------------------------------------------------------------- /tests/multifile/inputs/a/c/foo.cddl: -------------------------------------------------------------------------------- 1 | foo = [uint, text, bytes] 2 | -------------------------------------------------------------------------------- /tests/extern-deps/inputs/a/c/foo.cddl: -------------------------------------------------------------------------------- 1 | foo = [uint_typedef, text, bytes] 2 | -------------------------------------------------------------------------------- /docs/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /tests/extern-deps/tests.rs: -------------------------------------------------------------------------------- 1 | // the test really only tests compilation so there are no unit tests here -------------------------------------------------------------------------------- /docs/static/img/output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/static/img/output.png -------------------------------------------------------------------------------- /tests/extern-deps/inputs/_CDDL_CODEGEN_EXTERN_DEPS_DIR_/extern_dep_crate/sub/module.cddl: -------------------------------------------------------------------------------- 1 | uint_typedef = uint -------------------------------------------------------------------------------- /tests/multifile/tests.rs: -------------------------------------------------------------------------------- 1 | // the multifile test really only tests compilation so there are no unit tests here -------------------------------------------------------------------------------- /docs/static/img/output2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/static/img/output2.png -------------------------------------------------------------------------------- /docs/static/img/output3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/static/img/output3.png -------------------------------------------------------------------------------- /docs/static/img/output4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/static/img/output4.png -------------------------------------------------------------------------------- /docs/static/img/output5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/static/img/output5.png -------------------------------------------------------------------------------- /docs/src/fonts/Manrope-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/src/fonts/Manrope-Bold.ttf -------------------------------------------------------------------------------- /tests/raw-bytes/input.cddl: -------------------------------------------------------------------------------- 1 | pub_key = _CDDL_CODEGEN_RAW_BYTES_TYPE_ 2 | 3 | foo = [ 4 | 0, 5 | pub_key, 6 | ] -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/src/fonts/Manrope-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/src/fonts/Manrope-Light.ttf -------------------------------------------------------------------------------- /docs/src/fonts/Manrope-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/src/fonts/Manrope-Medium.ttf -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /docs/src/fonts/Manrope-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/src/fonts/Manrope-Regular.ttf -------------------------------------------------------------------------------- /docs/src/fonts/Manrope-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dcSpark/cddl-codegen/HEAD/docs/src/fonts/Manrope-SemiBold.ttf -------------------------------------------------------------------------------- /tests/extern-deps/inputs/_CDDL_CODEGEN_EXTERN_DEPS_DIR_/extern_dep_crate/mod.cddl: -------------------------------------------------------------------------------- 1 | extern_crate_foo = _CDDL_CODEGEN_EXTERN_TYPE_ -------------------------------------------------------------------------------- /tests/raw-bytes-preserve/input.cddl: -------------------------------------------------------------------------------- 1 | pub_key = _CDDL_CODEGEN_RAW_BYTES_TYPE_ 2 | 3 | foo = [ 4 | 0, 5 | pub_key, 6 | ] -------------------------------------------------------------------------------- /tests/extern-dep-crate/README.md: -------------------------------------------------------------------------------- 1 | This is a simple crate to test out the `_CDDL_CODEGEN_EXTERN_DEPS_DIR_` feature. 2 | 3 | It is not a test but is instead used by other tests. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cddl-codegen 2 | 3 | cddl-codegen is a library to generate Rust, WASM and JSON code from CDDL specifications 4 | 5 | See docs [here](https://dcspark.github.io/cddl-codegen/) 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli; 2 | pub mod comment_ast; 3 | pub mod dep_graph; 4 | pub mod generation; 5 | pub mod intermediate; 6 | pub mod parsing; 7 | pub mod rust_reserved; 8 | pub mod utils; 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 99 8 | rebase-strategy: disabled 9 | -------------------------------------------------------------------------------- /static/Cargo_rust.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cddl-lib" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | cbor_event = "2.4.0" 11 | -------------------------------------------------------------------------------- /static/Cargo_json_gen.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cddl-lib-json-schema-gen" 3 | version = "0.0.1" 4 | edition = "2018" 5 | 6 | 7 | [dependencies] 8 | serde_json = "1.0.57" 9 | schemars = "0.8.8" 10 | cddl-lib = { path = "../../rust" } 11 | -------------------------------------------------------------------------------- /tests/extern-deps/inputs/b/bar.cddl: -------------------------------------------------------------------------------- 1 | bar = { 2 | foo: #6.1337([* foo]), 3 | ? derp: uint, 4 | 1 : uint / null, 5 | ? 5: text, 6 | five: 5, 7 | foo_map: { * foo => foo }, 8 | ? baz: baz, 9 | } 10 | 11 | ; test refs too 12 | bar_typedef = bar -------------------------------------------------------------------------------- /tests/multifile/inputs/b/bar.cddl: -------------------------------------------------------------------------------- 1 | bar = { 2 | foo: #6.1337([* foo]), 3 | ? derp: uint, 4 | 1 : uint / null, 5 | ? 5: text, 6 | five: 5, 7 | foo_map: { * foo => foo }, 8 | ? baz: baz, 9 | } 10 | 11 | ; test refs too 12 | bar_typedef = bar -------------------------------------------------------------------------------- /tests/extern-dep-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "extern-dep-crate" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | cbor_event = "2.4.0" 11 | wasm-bindgen = "0.2" 12 | linked-hash-map = "0.5.3" 13 | -------------------------------------------------------------------------------- /docs/crowdin.yml: -------------------------------------------------------------------------------- 1 | preserve_hierarchy: true 2 | files: 3 | - source: /i18n/en/**/* 4 | translation: /i18n/%two_letters_code%/**/%original_file_name% 5 | - source: /docs/**/* 6 | translation: /i18n/%two_letters_code%/docusaurus-plugin-content-docs/current/**/%original_file_name% 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # global .gitignore 2 | *.swp 3 | 4 | # IDE 5 | .idea/ 6 | .vscode/ 7 | docs/node_modules 8 | 9 | # Rust 10 | target/ 11 | book/ 12 | 13 | # Mac files 14 | **/.DS_STore 15 | 16 | # Test files 17 | tests/*/export/** 18 | tests/*/export_wasm/** 19 | tests/*/export_preserve/** 20 | -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}", "./docs/**/*.{md,mdx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | corePlugins: { 9 | preflight: false, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 4 | const sidebars = { 5 | // By default, Docusaurus generates a sidebar from the docs folder structure 6 | tutorialSidebar: [{ type: "autogenerated", dirName: "." }], 7 | 8 | }; 9 | 10 | module.exports = sidebars; 11 | -------------------------------------------------------------------------------- /static/Cargo_wasm.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cddl-lib-wasm" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "rlib"] 8 | 9 | [dependencies] 10 | cddl-lib = { path = "../rust" } 11 | cbor_event = "2.4.0" 12 | wasm-bindgen = { version = "0.2", features=["serde-serialize"] } 13 | linked-hash-map = "0.5.3" 14 | -------------------------------------------------------------------------------- /tests/extern-deps/inputs/lib.cddl: -------------------------------------------------------------------------------- 1 | everything = [ 2 | ; from a single-level nested mod 3 | foo, 4 | extern_crate_foo, 5 | ; test using in arrays too 6 | bars: [* bar], 7 | ; and maps 8 | table: { * uint => bar_typedef } 9 | ; and from a multi-level nested mod 10 | baz: baz, 11 | ; and root dir mod 12 | qux: qux, 13 | ] -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Crowdin 23 | # website/i18n 24 | .vercel 25 | -------------------------------------------------------------------------------- /tests/multifile/inputs/lib.cddl: -------------------------------------------------------------------------------- 1 | external_foo = _CDDL_CODEGEN_EXTERN_TYPE_ 2 | 3 | everything = [ 4 | ; from a single-level nested mod 5 | foo, 6 | external_foo, 7 | ; test using in arrays too 8 | bars: [* bar], 9 | ; and maps 10 | table: { * uint => bar_typedef } 11 | ; and from a multi-level nested mod 12 | baz: baz, 13 | ; and root dir mod 14 | qux: qux, 15 | ] -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | Example + unit tests for all supported serialization + deserialization features are inside of `test.cddl`. 2 | 3 | Move `serialization_unit_tests.rs` to the generated `/export/` directory generated from running cddl-codegen on `test.cddl`, or alternatively copy the code into the generated `export/serialization.rs` to function as unit tests. 4 | 5 | To run unit tests run `cargo test` from the `/export/` directory. 6 | -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/cbor_encodings.rs: -------------------------------------------------------------------------------- 1 | use crate::serialization::{LenEncoding, StringEncoding}; 2 | 3 | #[derive(Clone, Debug, Default)] 4 | pub struct ExternCrateFooEncoding { 5 | pub len_encoding: LenEncoding, 6 | pub tag_encoding: Option, 7 | pub index_0_encoding: Option, 8 | pub index_1_encoding: StringEncoding, 9 | pub index_2_encoding: StringEncoding, 10 | } 11 | -------------------------------------------------------------------------------- /tests/json/input.cddl: -------------------------------------------------------------------------------- 1 | bytes_wrapper = bytes ; @newtype 2 | str_wrapper = text ; @newtype 3 | u8_wrapper = uint .lt 256 ; @newtype 4 | u64_wrapper = uint ; @newtype 5 | i16_wrapper = int .size 2 ; @newtype 6 | i64_wrapper = int .size 8 ; @newtype 7 | nint_wrapper = nint ; @newtype 8 | 9 | ; TODO: issue: https://github.com/dcSpark/cddl-codegen/issues/223 10 | ; bool_wrapper = bool ; @newtype 11 | 12 | struct_wrapper = u64_wrapper ; @newtype 13 | custom_wrapper = uint ; @newtype @custom_json -------------------------------------------------------------------------------- /docs/static/icons/danger-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/external_wasm_raw_bytes_def: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | #[wasm_bindgen] 3 | pub struct PubKey(cddl_lib::PubKey); 4 | 5 | impl From for PubKey { 6 | fn from(native: cddl_lib::PubKey) -> Self { 7 | Self(native) 8 | } 9 | } 10 | 11 | impl From for cddl_lib::PubKey { 12 | fn from(wasm: PubKey) -> Self { 13 | wasm.0 14 | } 15 | } 16 | 17 | impl AsRef for PubKey { 18 | fn as_ref(&self) -> &cddl_lib::PubKey { 19 | &self.0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/external_rust_raw_bytes_def: -------------------------------------------------------------------------------- 1 | use crate::RawBytesEncoding; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct PubKey([u8; 32]); 5 | 6 | impl RawBytesEncoding for PubKey { 7 | fn to_raw_bytes(&self) -> &[u8] { 8 | &self.0 9 | } 10 | 11 | fn from_raw_bytes(bytes: &[u8]) -> Result { 12 | use std::convert::TryInto; 13 | bytes 14 | .try_into() 15 | .map(PubKey) 16 | .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

CDDL-Codegen Docs

2 | 3 | ### Installation 4 | 5 | ``` 6 | $ npm install 7 | ``` 8 | 9 | ### Local Development 10 | 11 | ``` 12 | $ npm run start 13 | ``` 14 | 15 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 16 | 17 | ### Build 18 | 19 | ``` 20 | $ npm run build 21 | ``` 22 | 23 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 24 | -------------------------------------------------------------------------------- /static/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cddl-lib", 3 | "version": "0.0.1", 4 | "description": "cddl-codegen generated library", 5 | "scripts": { 6 | "rust:build-nodejs": "rimraf ./rust/wasm/pkg && cd rust/wasm; cross-env WASM_BINDGEN_WEAKREF=1 wasm-pack build --target=nodejs; wasm-pack pack", 7 | "rust:build-browser": "rimraf ./rust/wasm/pkg && cd rust/wasm; cross-env WASM_BINDGEN_WEAKREF=1 wasm-pack build --target=browser; wasm-pack pack" 8 | }, 9 | "devDependencies": { 10 | "rimraf": "3.0.2", 11 | "cross-env": "^7.0.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /static/ordered_hash_map_schemars.rs: -------------------------------------------------------------------------------- 1 | impl schemars::JsonSchema for OrderedHashMap where 2 | K: Hash + Eq + Ord + schemars::JsonSchema, 3 | V: schemars::JsonSchema { 4 | fn schema_name() -> String { format!("OrderedHashMap<{}, {}>", K::schema_name(), V::schema_name()) } 5 | fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { 6 | std::collections::BTreeMap::::json_schema(gen) 7 | } 8 | fn is_referenceable() -> bool { std::collections::BTreeMap::::is_referenceable() } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_label: "Introduction" 3 | sidebar_position: 1 4 | --- 5 | 6 | 7 | # cddl-codegen 8 | 9 | Experimental library for generating rust code for CBOR (de) serialization from CDDL specs. 10 | 11 | ### Purpose ### 12 | 13 | Instead of hand-writing code for CBOR parsing and writing tests to make sure it matches your CDDL spec, it's much faster to just generate the code from the spec! It will save time and make it easier to keep all your code in sync with any changes to your specification. 14 | 15 | You can learn more about CDDL [here](https://github.com/cbor-wg/cddl) 16 | -------------------------------------------------------------------------------- /static/serialization_non_force_canonical.rs: -------------------------------------------------------------------------------- 1 | pub trait SerializeEmbeddedGroup { 2 | fn serialize_as_embedded_group<'a, W: Write + Sized>( 3 | &self, 4 | serializer: &'a mut Serializer, 5 | ) -> cbor_event::Result<&'a mut Serializer>; 6 | } 7 | 8 | pub trait ToCBORBytes { 9 | fn to_cbor_bytes(&self) -> Vec; 10 | } 11 | 12 | impl ToCBORBytes for T { 13 | fn to_cbor_bytes(&self) -> Vec { 14 | let mut buf = Serializer::new_vec(); 15 | self.serialize(&mut buf).unwrap(); 16 | buf.finalize() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/static/icons/info-icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/raw_bytes_encoding.rs: -------------------------------------------------------------------------------- 1 | pub trait RawBytesEncoding { 2 | fn to_raw_bytes(&self) -> &[u8]; 3 | 4 | fn from_raw_bytes(bytes: &[u8]) -> Result 5 | where 6 | Self: Sized; 7 | 8 | fn to_raw_hex(&self) -> String { 9 | hex::encode(self.to_raw_bytes()) 10 | } 11 | 12 | fn from_raw_hex(hex_str: &str) -> Result 13 | where 14 | Self: Sized, 15 | { 16 | let bytes = hex::decode(hex_str) 17 | .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)))?; 18 | Self::from_raw_bytes(bytes.as_ref()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/comment-dsl/input.cddl: -------------------------------------------------------------------------------- 1 | address = [ 2 | bytes, ; @name address 3 | uint ; @name checksum 4 | ] 5 | 6 | protocol_param_update = { 7 | 0: uint, ; @name minfee_a 8 | 1: uint, ; @name minfee_b 9 | } 10 | 11 | block = [ 12 | ; @name ebb_block_wrapper 13 | 0, bytes ; @name ebb_block_cbor 14 | // 15 | ; @name main_block_wrapper 16 | 1, bytes ; @name main_block_cbor 17 | ] 18 | 19 | typechoice = 20 | [0, bytes] ; @name case_1 21 | / 22 | [1, bytes] ; @name case_2 23 | 24 | protocol_magic = uint ; @newtype get 25 | 26 | typechoice_variants = 27 | text ; @name case_1 28 | / 29 | [* text] ; @name case_2 30 | -------------------------------------------------------------------------------- /docs/docs/getting_started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Installation & Getting Started 6 | 7 | 8 | ## Install 9 | 10 | 11 | ```bash 12 | 13 | git clone https://github.com/dcSpark/cddl-codegen && cd cddl-codegen 14 | ``` 15 | 16 | 17 | ## Run Example 18 | 19 | 20 | To run execute `cargo run -- --input=input.cddl --output=EXPORT_DIR` to read `./input.cddl` and produce output code in `./EXPORT_DIR/`. 21 | 22 | 23 | ```bash 24 | cargo run -- --input=example/test.cddl --output=export 25 | ``` 26 | 27 | 28 | ## Build 29 | 30 | ```bash 31 | cargo build --release 32 | target/release/cddl-codegen --input example/test.cddl --output export 33 | ``` 34 | -------------------------------------------------------------------------------- /example/test.cddl: -------------------------------------------------------------------------------- 1 | hash = bytes 2 | 3 | foo = [uint, text, bytes] 4 | 5 | tagged_text = #6.42(text) 6 | 7 | opt_text = tagged_text / null 8 | 9 | foo2 = #6.23([uint, opt_text]) 10 | 11 | bar = { 12 | foo: #6.1337(foo), 13 | ? derp: uint, 14 | 1 : uint / null, 15 | ? 5: text, 16 | five: 5, 17 | } 18 | 19 | plain = (d: #6.23(uint), e: tagged_text) 20 | outer = [a: uint, b: plain, c: "some text"] 21 | 22 | table = { * uint => text } 23 | 24 | table_arr_members = { 25 | tab: { * text => text }, 26 | arr: [*uint], 27 | arr2: [*foo], 28 | } 29 | 30 | type_choice = 0 / "hello world" / uint / text / bytes / #6.64([*uint]) 31 | 32 | group_choice = [ foo // 0, x: uint // plain ] 33 | -------------------------------------------------------------------------------- /static/serialization.rs: -------------------------------------------------------------------------------- 1 | // same as cbor_event::de::Deserialize but with our DeserializeError 2 | pub trait Deserialize { 3 | fn from_cbor_bytes(data: &[u8]) -> Result where Self: Sized { 4 | let mut raw = Deserializer::from(std::io::Cursor::new(data)); 5 | Self::deserialize(&mut raw) 6 | } 7 | 8 | fn deserialize( 9 | raw: &mut Deserializer, 10 | ) -> Result where Self: Sized; 11 | } 12 | 13 | impl Deserialize for T { 14 | fn deserialize(raw: &mut Deserializer) -> Result { 15 | T::deserialize(raw).map_err(DeserializeError::from) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/raw-bytes/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | use cbor_event::de::Deserializer; 5 | use serialization::Deserialize; 6 | 7 | fn deser_test(orig: &T) { 8 | let orig_bytes = orig.to_cbor_bytes(); 9 | print_cbor_types("orig", &orig_bytes); 10 | let mut deserializer = Deserializer::from(std::io::Cursor::new(orig_bytes.clone())); 11 | let deser = T::deserialize(&mut deserializer).unwrap(); 12 | print_cbor_types("deser", &deser.to_cbor_bytes()); 13 | assert_eq!(orig.to_cbor_bytes(), deser.to_cbor_bytes()); 14 | assert_eq!(deserializer.as_ref().position(), orig_bytes.len() as u64); 15 | } 16 | 17 | #[test] 18 | fn foo() { 19 | deser_test(&Foo::new(PubKey([0; 32]))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /static/ordered_hash_map.rs: -------------------------------------------------------------------------------- 1 | use core::hash::Hash; 2 | 3 | #[derive(Clone, Debug, Default, Hash, Ord, Eq, PartialEq, PartialOrd)] 4 | pub struct OrderedHashMap(linked_hash_map::LinkedHashMap) where 5 | K : Hash + Eq + Ord; 6 | 7 | impl std::ops::Deref for OrderedHashMap where K : Hash + Eq + Ord { 8 | type Target = linked_hash_map::LinkedHashMap; 9 | 10 | fn deref(&self) -> &Self::Target { 11 | &self.0 12 | } 13 | } 14 | 15 | impl std::ops::DerefMut for OrderedHashMap where K : Hash + Eq + Ord { 16 | fn deref_mut(&mut self) -> &mut Self::Target { 17 | &mut self.0 18 | } 19 | } 20 | 21 | impl OrderedHashMap where K : Hash + Eq + Ord { 22 | pub fn new() -> Self { 23 | Self(linked_hash_map::LinkedHashMap::new()) 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /static/package_json_schemas.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cddl-lib", 3 | "version": "0.0.1", 4 | "description": "cddl-codegen generated library", 5 | "scripts": { 6 | "rust:build-nodejs": "rimraf ./rust/wasm/pkg && cd rust/wasm; cross-env WASM_BINDGEN_WEAKREF=1 wasm-pack build --target=nodejs; cd ../..; npm run js:ts-json-gen; cd rust/wasm; wasm-pack pack", 7 | "rust:build-browser": "rimraf ./rust/wasm/pkg && cd rust/wasm; cross-env WASM_BINDGEN_WEAKREF=1 wasm-pack build --target=browser; cd ../..; npm run js:ts-json-gen; cd rust/wasm; wasm-pack pack", 8 | "js:ts-json-gen": "cd rust/json-gen && cargo +stable run && cd ../.. && node ./scripts/run-json2ts.js && node ./scripts/json-ts-types.js" 9 | }, 10 | "devDependencies": { 11 | "json-schema-to-typescript": "^10.1.5", 12 | "rimraf": "3.0.2", 13 | "cross-env": "^7.0.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /static/ordered_hash_map_json.rs: -------------------------------------------------------------------------------- 1 | impl serde::Serialize for OrderedHashMap where 2 | K : Hash + Eq + Ord + serde::Serialize, 3 | V: serde::Serialize { 4 | fn serialize(&self, serializer: S) -> Result 5 | where S: serde::Serializer { 6 | let map = self.iter().collect::>(); 7 | map.serialize(serializer) 8 | } 9 | } 10 | 11 | impl<'de, K, V> serde::de::Deserialize<'de> for OrderedHashMap where 12 | K: Hash + Eq + Ord + serde::Deserialize<'de>, 13 | V: serde::Deserialize<'de> { 14 | fn deserialize(deserializer: D) -> Result where 15 | D: serde::de::Deserializer<'de> { 16 | let map = as serde::de::Deserialize>::deserialize(deserializer)?; 17 | Ok(Self(map.into_iter().collect())) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /tests/rust-wasm-split/input.cddl: -------------------------------------------------------------------------------- 1 | foo = [uint, text, bytes] 2 | 3 | tagged_text = #6.42(text) 4 | 5 | opt_tagged_text = tagged_text / null 6 | 7 | opt_text = text / null 8 | 9 | foo2 = #6.23([uint, opt_text]) 10 | ; TODO: fix this up 11 | ; alias resolving issue possibly as foo3::new_foo() is Alias(Option(RustIdent(opt_tagged_text))) 12 | ; and not the Alias(Option(String)) it would be from wasm 13 | ;foo3 = #6.23([uint, opt_tagged_text]) 14 | 15 | bar = { 16 | foo: foo, 17 | ;foo: #6.1337(foo), 18 | ? derp: uint, 19 | 1 : uint / null, 20 | ? 5: text, 21 | five: 5, 22 | pi: 3.14159, 23 | } 24 | 25 | plain = (d: #6.23(uint), e: tagged_text) 26 | outer = [a: uint, b: plain, c: "some text"] 27 | 28 | type_choice = 0 / "hello world" / text / bytes / #6.64(uint) 29 | 30 | group_choice = [ foo // 0, x: uint // plain ] 31 | 32 | tagged_type_choice = #6.23("string literal" / foo / uint) 33 | -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/ordered_hash_map.rs: -------------------------------------------------------------------------------- 1 | use core::hash::Hash; 2 | 3 | #[derive(Clone, Debug, Default, Hash, Ord, Eq, PartialEq, PartialOrd)] 4 | pub struct OrderedHashMap(linked_hash_map::LinkedHashMap) 5 | where 6 | K: Hash + Eq + Ord; 7 | 8 | impl std::ops::Deref for OrderedHashMap 9 | where 10 | K: Hash + Eq + Ord, 11 | { 12 | type Target = linked_hash_map::LinkedHashMap; 13 | 14 | fn deref(&self) -> &Self::Target { 15 | &self.0 16 | } 17 | } 18 | 19 | impl std::ops::DerefMut for OrderedHashMap 20 | where 21 | K: Hash + Eq + Ord, 22 | { 23 | fn deref_mut(&mut self) -> &mut Self::Target { 24 | &mut self.0 25 | } 26 | } 27 | 28 | impl OrderedHashMap 29 | where 30 | K: Hash + Eq + Ord, 31 | { 32 | pub fn new() -> Self { 33 | Self(linked_hash_map::LinkedHashMap::new()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cddl-codegen" 3 | version = "0.1.0" 4 | edition = "2018" 5 | license = "MIT" 6 | repository = "https://github.com/dcSpark/cddl-codegen" 7 | keywords = ["cddl", "codegen", "cbor", "wasm"] 8 | description = "Codegen serialization logic for CBOR automatically from a CDDL specification" 9 | 10 | [features] 11 | default = ["which-rustfmt"] 12 | # Dynamically discover a `rustfmt` binary using the `which` crate 13 | which-rustfmt = ["which"] 14 | 15 | [dependencies] 16 | cbor_event = "2.4.0" 17 | # we don't update due to https://github.com/anweiss/cddl/issues/222 18 | cddl = "=0.9.1" 19 | clap = { version = "4.3.12", features = ["derive"] } 20 | codegen = { git = "https://github.com/dcSpark/codegen", branch = "master" } 21 | once_cell = "1.18.0" 22 | nom = "7.1.1" 23 | pathdiff = "0.2.1" 24 | which = { version = "4.4.0", optional = true, default-features = false } 25 | syn = "2.0.16" 26 | quote = "1.0.31" -------------------------------------------------------------------------------- /tests/canonical/input.cddl: -------------------------------------------------------------------------------- 1 | foo = #6.11([uint, text, bytes]) 2 | 3 | bar = { 4 | foo: #6.13(foo), 5 | ? derp: uint, 6 | 1 : uint / null, 7 | ? 5: text, 8 | five: 5, 9 | } 10 | 11 | u32 = uint .size 4 ; 4 bytes 12 | 13 | table_arr_members = { 14 | arr: [*u32], 15 | arr2: [*foo], 16 | table: { * uint => text }, 17 | } 18 | 19 | data = { * #6.14(bytes) => { * uint => { * #6.9(uint) => [* #6.18([* text]) ] } } } 20 | 21 | deeply_nested = [ data: data ] 22 | 23 | string64 = text .size (0..64) 24 | 25 | tagged_text = #6.9(text) 26 | 27 | string32 = #6.7(text .size (16..32)) 28 | 29 | type_choice = 0 / "hello world" / uint / text / #6.16([*uint]) 30 | 31 | plain = (d: #6.13(uint), e: tagged_text) 32 | 33 | group_choice = [ 3 // #6.10(2) // foo // 0, x: uint // plain ] 34 | 35 | foo_bytes = bytes .cbor foo 36 | 37 | ; since we don't generate code for definitions like the above (should we if no one refers to it?) 38 | cbor_in_cbor = [foo_bytes, uint_bytes: bytes .cbor uint] 39 | -------------------------------------------------------------------------------- /static/json-ts-types.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const inputFile = fs.readFileSync('./rust/wasm/pkg/cddl_lib_wasm.d.ts', 'utf8').split(/\r?\n/); 4 | let currentClass = null; 5 | for (let i = 0; i < inputFile.length; ++i) { 6 | let line = inputFile[i]; 7 | const classDef = /export class(.*){/.exec(line); 8 | if (classDef != null && classDef.length > 1) { 9 | currentClass = classDef[1].trim(); 10 | continue; 11 | } 12 | inputFile[i] = line.replace(/(\s?to_js_value\(\)\s?:\s?)(any)(;)/, `$1${currentClass}JSON$3`); 13 | if (line != inputFile[i]) { 14 | continue; 15 | } 16 | inputFile[i] = line.replace(/(\s?\*\s?\@returns\s\{)(any)(\})/, `$1${currentClass}JSON$3`); 17 | } 18 | const jsonDefs = fs.readFileSync('./rust/json-gen/output/json-types.d.ts', 'utf8'); 19 | fs.writeFile( 20 | './rust/wasm/pkg/cddl_lib_wasm.d.ts', 21 | `${inputFile.join('\n')}\n${jsonDefs}`, 22 | (err) => { 23 | if (err != null) { 24 | console.log(`err writing file: ${err}`) 25 | } 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 EMURGO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/docs/output_format.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Output format 6 | 7 | - Inside of the output directly the tool always produces a `rust/` directory (including Cargo.toml, etc). 8 | - Unless we pass in `--wasm=false` the tool also generates a corresponding `wasm/` directory. 9 | - The default format for `rust/` is to have a `lib.rs` containing the structs and `serialization.rs` containing their (de)serialization implementations/corresponding types. 10 | - The `wasm/` directory is full of wasm_bindgen-annotated wrappers all in `lib.rs` for the corresponding rust-use-only structs in `rust/` and can be compiled for WASM builds by running `wasm-pack build` on it. 11 | 12 | **Example Output** 13 | 14 | ![](/img/output.png) 15 | 16 | :::note 17 | The output format can change slightly depending on certain command line flags: 18 | ::: 19 | 20 | `--wasm=false` 21 | 22 | ![](/img/output2.png) 23 | 24 | 25 | `--preserve-encodings=true` 26 | 27 | ![](/img/output3.png) 28 | 29 | 30 | `--json-schema-export true` 31 | 32 | ![](/img/output4.png) 33 | 34 | 35 | `--package-json true --json-schema-export true` 36 | 37 | ![](/img/output5.png) 38 | -------------------------------------------------------------------------------- /src/rust_reserved.rs: -------------------------------------------------------------------------------- 1 | // You can find all the types included in the Rust std lib below 2 | // .rustup/toolchains/stable-x86_64-unknown-linux-gnu/share/doc/rust/html/std/all.html 3 | // However, we're particularly interested in avoiding collisions with the prelude 4 | // So we only disallow types that can be found in the prelude 5 | // https://doc.rust-lang.org/std/prelude/index.html 6 | 7 | pub const STD_TYPES: [&str; 40] = [ 8 | "Copy", 9 | "Send", 10 | "Sized", 11 | "Sync", 12 | "Unpin", 13 | "Drop", 14 | "Fn", 15 | "FnMut", 16 | "FnOnce", 17 | "drop", 18 | "Box", 19 | "ToOwned", 20 | "Clone", 21 | "PartialEq", 22 | "PartialOrd", 23 | "Eq", 24 | "Ord", 25 | "AsRef", 26 | "AsMut", 27 | "Into", 28 | "From", 29 | "Default", 30 | "Iterator", 31 | "Extend", 32 | "IntoIterator", 33 | "DoubleEndedIterator", 34 | "ExactSizeIterator", 35 | "Self", 36 | "Option", 37 | "Some", 38 | "None", 39 | "Result", 40 | "Ok", 41 | "Err", 42 | "String", 43 | "ToString", 44 | "Vec", 45 | "TryFrom", 46 | "TryInto", 47 | "FromIterator", 48 | ]; 49 | -------------------------------------------------------------------------------- /tests/external_json_impls: -------------------------------------------------------------------------------- 1 | impl serde::Serialize for CustomWrapper { 2 | fn serialize(&self, serializer: S) -> Result 3 | where 4 | S: serde::Serializer, 5 | { 6 | serializer.serialize_str(&u64::from(self.clone()).to_string()) 7 | } 8 | } 9 | 10 | impl<'de> serde::de::Deserialize<'de> for CustomWrapper { 11 | fn deserialize(deserializer: D) -> Result 12 | where 13 | D: serde::de::Deserializer<'de>, 14 | { 15 | use std::str::FromStr; 16 | let s = ::deserialize(deserializer)?; 17 | u64::from_str(&s) 18 | .map(CustomWrapper::new) 19 | .map_err(|_e| { 20 | serde::de::Error::invalid_value( 21 | serde::de::Unexpected::Str(&s), 22 | &"invalid u64 as string", 23 | ) 24 | }) 25 | } 26 | } 27 | 28 | impl schemars::JsonSchema for CustomWrapper { 29 | fn schema_name() -> String { 30 | String::from("CustomWrapper") 31 | } 32 | 33 | fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { 34 | String::json_schema(gen) 35 | } 36 | 37 | fn is_referenceable() -> bool { 38 | String::is_referenceable() 39 | } 40 | } -------------------------------------------------------------------------------- /docs/docs/wasm_differences.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | 6 | # Wasm Differences 7 | 8 | In the wasm crate we can't always go one to one with the rust crate. Here are some differences/extra types in the WASM create. `AsRef` `From` and `Into` are implemented to go between the rust and wasm crate types to help. 9 | 10 | ## Heterogeneous Arrays 11 | 12 | `wasm_bindgen` cannot expose doubly-nested types like `Vec` which can be a limitation if `T` was a non-byte primitive. 13 | Any array of non-primitives such as `[foo]` will generate another type called `FooList` which supports all basic array operations. 14 | This lets us get around the `wasm_bindgen` limitation (without implementing cross-boundary traits which could be inefficient/tedious/complicated). 15 | This array wrapper implements `len() -> self`, `get(usize) -> T` and `add(T)`. 16 | 17 | ## Tables 18 | 19 | Map literals also generate a type for them with `len() -> usize` and `insert(K, V) -> Option`. The table type will have a `MapKeyToValue` name for whichever `Key` and `Value` types it's exposed as if it's anonymously inlined as a member, or will take on the identifier if it's a named one. 20 | 21 | ## Enums 22 | 23 | Both type/group choices generate rust-style enums. On the wasm side we can't do that so we directly wrap the rust type, and then provide a `FooKind` c-style enum for each rust enum `Foo` just for checking which variant it is. 24 | -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | use wasm_bindgen::prelude::{wasm_bindgen, JsError}; 3 | use cbor_encodings::ExternCrateFooEncoding; 4 | 5 | pub mod cbor_encodings; 6 | pub mod error; 7 | pub mod ordered_hash_map; 8 | pub mod serialization; 9 | pub mod sub; 10 | 11 | #[wasm_bindgen] 12 | #[derive(Clone, Debug)] 13 | pub struct ExternCrateFoo { 14 | index_0: u64, 15 | index_1: String, 16 | index_2: Vec, 17 | encodings: Option, 18 | } 19 | 20 | #[wasm_bindgen] 21 | impl ExternCrateFoo { 22 | pub fn new(index_0: u64, index_1: String, index_2: Vec) -> Self { 23 | Self { 24 | index_0, 25 | index_1, 26 | index_2, 27 | encodings: None, 28 | } 29 | } 30 | 31 | pub fn to_cbor_bytes(&self) -> Vec { 32 | serialization::ToCBORBytes::to_cbor_bytes(&self) 33 | } 34 | 35 | pub fn from_cbor_bytes(cbor_bytes: &[u8]) -> Result { 36 | serialization::Deserialize::from_cbor_bytes(cbor_bytes) 37 | .map_err(|e| JsError::new(&format!("from_bytes: {}", e))) 38 | } 39 | 40 | pub fn index_0(&self) -> u64 { 41 | self.index_0 42 | } 43 | 44 | pub fn index_1(&self) -> String { 45 | self.index_1.clone() 46 | } 47 | 48 | pub fn index_2(&self) -> Vec { 49 | self.index_2.clone() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cddl-codegen-documentation", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "swizzle": "docusaurus swizzle", 9 | "deploy": "docusaurus deploy", 10 | "clear": "docusaurus clear", 11 | "serve": "docusaurus serve", 12 | "write-translations": "docusaurus write-translations", 13 | "write-heading-ids": "docusaurus write-heading-ids", 14 | "crowdin": "crowdin", 15 | "build": "docusaurus build" 16 | }, 17 | "dependencies": { 18 | "@crowdin/cli": "^3.7.9", 19 | "@docusaurus/core": "^2.4.0", 20 | "@docusaurus/preset-classic": "^2.4.0", 21 | "@easyops-cn/docusaurus-search-local": "^0.32.0", 22 | "@mdx-js/react": "^1.6.22", 23 | "clsx": "^1.2.1", 24 | "prism-react-renderer": "^1.3.5", 25 | "react": "^17.0.2", 26 | "react-dom": "^17.0.2" 27 | }, 28 | "devDependencies": { 29 | "@docusaurus/module-type-aliases": "^2.4.0", 30 | "autoprefixer": "^10.4.8", 31 | "postcss": "^8.4.16", 32 | "tailwindcss": "^3.1.8" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.5%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "engines": { 47 | "node": ">=16.14" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Linter and unit tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | paths: 7 | - '.github/workflows/build.yml' 8 | - "**/Cargo.*" 9 | - "src/**" 10 | - "tests/**" 11 | pull_request: 12 | branches: [ master ] 13 | paths: 14 | - '.github/workflows/build.yml' 15 | - "**/Cargo.*" 16 | - "src/**" 17 | - "tests/**" 18 | 19 | jobs: 20 | test: 21 | name: Rust project 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: "Checkout" 25 | uses: actions/checkout@v2 26 | 27 | - name: "Install latest nightly" 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | profile: minimal 31 | toolchain: nightly 32 | override: true 33 | components: rustfmt, clippy 34 | 35 | - name: "Format check" 36 | uses: actions-rs/cargo@v1 37 | with: 38 | command: fmt 39 | args: --all -- --check 40 | 41 | - name: "Linter checks" 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: clippy 45 | args: --locked --workspace --all-features --all-targets -- --deny "clippy::all" 46 | 47 | - name: "Run cargo build" 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: build 51 | args: --locked --workspace --all-features --all-targets 52 | 53 | - name: "Run cargo test" 54 | uses: actions-rs/cargo@v1 55 | with: 56 | command: test 57 | args: --all-features --all-targets 58 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # Review gh actions docs if you want to further define triggers, paths, etc 8 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 9 | 10 | jobs: 11 | deploy: 12 | name: Deploy to GitHub Pages 13 | runs-on: ubuntu-latest 14 | defaults: 15 | run: 16 | working-directory: 'docs' 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v3 20 | with: 21 | node-version: 16.x 22 | cache: npm 23 | cache-dependency-path: ./docs/yarn.lock 24 | 25 | - name: Install dependencies 26 | run: npm install 27 | 28 | - name: Build website 29 | run: npm run build 30 | 31 | # Popular action to deploy to GitHub Pages: 32 | # Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus 33 | - name: Deploy to GitHub Pages 34 | uses: peaceiris/actions-gh-pages@v3 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | # Build output to publish to the `gh-pages` branch: 38 | publish_dir: ./docs/build 39 | # The following lines assign commit authorship to the official 40 | # GH-Actions bot for deploys to `gh-pages` branch: 41 | # https://github.com/actions/checkout/issues/13#issuecomment-724415212 42 | # The GH actions bot is used by default if you didn't specify the two fields. 43 | # You can swap them out with your own user credentials. 44 | user_name: github-actions[bot] 45 | user_email: 41898282+github-actions[bot]@users.noreply.github.com 46 | -------------------------------------------------------------------------------- /static/serialization_non_preserve.rs: -------------------------------------------------------------------------------- 1 | pub struct CBORReadLen { 2 | deser_len: cbor_event::Len, 3 | read: u64, 4 | } 5 | 6 | impl CBORReadLen { 7 | pub fn new(len: cbor_event::Len) -> Self { 8 | Self { 9 | deser_len: len, 10 | read: 0, 11 | } 12 | } 13 | 14 | pub fn read(&self) -> u64 { 15 | self.read 16 | } 17 | 18 | // Marks {n} values as being read, and if we go past the available definite length 19 | // given by the CBOR, we return an error. 20 | pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> { 21 | match self.deser_len { 22 | cbor_event::Len::Len(n) => { 23 | self.read += count as u64; 24 | if self.read > n { 25 | Err(DeserializeFailure::DefiniteLenMismatch(n, None)) 26 | } else { 27 | Ok(()) 28 | } 29 | }, 30 | cbor_event::Len::Indefinite => Ok(()), 31 | } 32 | } 33 | 34 | pub fn finish(&self) -> Result<(), DeserializeFailure> { 35 | match self.deser_len { 36 | cbor_event::Len::Len(n) => { 37 | if self.read == n { 38 | Ok(()) 39 | } else { 40 | Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read))) 41 | } 42 | }, 43 | cbor_event::Len::Indefinite => Ok(()), 44 | } 45 | } 46 | } 47 | 48 | pub trait DeserializeEmbeddedGroup { 49 | fn deserialize_as_embedded_group( 50 | raw: &mut Deserializer, 51 | read_len: &mut CBORReadLen, 52 | len: cbor_event::Len, 53 | ) -> Result where Self: Sized; 54 | } 55 | -------------------------------------------------------------------------------- /static/serialization_preserve_non_force_canonical.rs: -------------------------------------------------------------------------------- 1 | #[inline] 2 | pub fn fit_sz(len: u64, sz: Option) -> cbor_event::Sz { 3 | match sz { 4 | Some(sz) => if len <= sz_max(sz) { 5 | sz 6 | } else { 7 | cbor_event::Sz::canonical(len) 8 | }, 9 | None => cbor_event::Sz::canonical(len), 10 | } 11 | } 12 | 13 | impl LenEncoding { 14 | pub fn to_len_sz(&self, len: u64) -> cbor_event::LenSz { 15 | match self { 16 | Self::Canonical => cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)), 17 | Self::Definite(sz) => if sz_max(*sz) >= len { 18 | cbor_event::LenSz::Len(len, *sz) 19 | } else { 20 | cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)) 21 | }, 22 | Self::Indefinite => cbor_event::LenSz::Indefinite, 23 | } 24 | } 25 | 26 | pub fn end<'a, W: Write + Sized>(&self, serializer: &'a mut Serializer) -> cbor_event::Result<&'a mut Serializer> { 27 | if *self == Self::Indefinite { 28 | serializer.write_special(cbor_event::Special::Break)?; 29 | } 30 | Ok(serializer) 31 | } 32 | } 33 | 34 | impl StringEncoding { 35 | pub fn to_str_len_sz(&self, len: u64) -> cbor_event::StringLenSz { 36 | match self { 37 | Self::Canonical => cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)), 38 | Self::Definite(sz) => if sz_max(*sz) >= len { 39 | cbor_event::StringLenSz::Len(*sz) 40 | } else { 41 | cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)) 42 | }, 43 | Self::Indefinite(lens) => cbor_event::StringLenSz::Indefinite(lens.clone()), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/raw-bytes-preserve/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | use cbor_event::{Sz, StringLenSz, de::Deserializer}; 5 | use serialization::Deserialize; 6 | 7 | #[test] 8 | fn foo() { 9 | let str_32_encodings = vec![ 10 | StringLenSz::Len(Sz::One), 11 | StringLenSz::Len(Sz::Eight), 12 | StringLenSz::Indefinite(vec![(4, Sz::One), (28, Sz::Four)]) 13 | ]; 14 | for str_enc in str_32_encodings.iter() { 15 | let irregular_bytes = vec![ 16 | arr_sz(2, Sz::One), 17 | cbor_int(0, Sz::Inline), 18 | cbor_bytes_sz(vec![0xFF; 32], str_enc.clone()) 19 | ].into_iter().flatten().clone().collect::>(); 20 | let irregular = Foo::from_cbor_bytes(&irregular_bytes).unwrap(); 21 | assert_eq!(irregular_bytes, irregular.to_cbor_bytes()); 22 | } 23 | } 24 | 25 | #[test] 26 | #[should_panic] 27 | fn foo_too_big() { 28 | let irregular_bytes = vec![ 29 | arr_sz(2, Sz::One), 30 | cbor_int(0, Sz::Inline), 31 | cbor_bytes_sz(vec![0x00; 33], StringLenSz::Len(Sz::One),) 32 | ].into_iter().flatten().clone().collect::>(); 33 | let irregular = Foo::from_cbor_bytes(&irregular_bytes).unwrap(); 34 | } 35 | 36 | #[test] 37 | #[should_panic] 38 | fn foo_too_small() { 39 | let irregular_bytes = vec![ 40 | arr_sz(2, Sz::One), 41 | cbor_int(0, Sz::Inline), 42 | cbor_bytes_sz(vec![0x00; 31], StringLenSz::Len(Sz::One),) 43 | ].into_iter().flatten().clone().collect::>(); 44 | let irregular = Foo::from_cbor_bytes(&irregular_bytes).unwrap(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/external_wasm_defs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | #[wasm_bindgen] 3 | pub struct ExternalFoo(cddl_lib::ExternalFoo); 4 | 5 | #[wasm_bindgen] 6 | impl ExternalFoo { 7 | pub fn to_cbor_bytes(&self) -> Vec { 8 | cddl_lib::serialization::ToCBORBytes::to_cbor_bytes(&self.0) 9 | } 10 | 11 | pub fn from_cbor_bytes(cbor_bytes: &[u8]) -> Result { 12 | cddl_lib::serialization::Deserialize::from_cbor_bytes(cbor_bytes) 13 | .map(Self) 14 | .map_err(Into::into) 15 | } 16 | 17 | pub fn index_0(&self) -> u64 { 18 | self.0.index_0 19 | } 20 | 21 | pub fn index_1(&self) -> String { 22 | self.0.index_1.clone() 23 | } 24 | 25 | pub fn index_2(&self) -> Vec { 26 | self.0.index_2.clone() 27 | } 28 | 29 | pub fn new(index_0: u64, index_1: String, index_2: Vec) -> Self { 30 | Self(cddl_lib::ExternalFoo::new(index_0, index_1, index_2)) 31 | } 32 | } 33 | 34 | impl From for ExternalFoo { 35 | fn from(native: cddl_lib::ExternalFoo) -> Self { 36 | Self(native) 37 | } 38 | } 39 | 40 | impl From for cddl_lib::ExternalFoo { 41 | fn from(wasm: ExternalFoo) -> Self { 42 | wasm.0 43 | } 44 | } 45 | 46 | impl AsRef for ExternalFoo { 47 | fn as_ref(&self) -> &cddl_lib::ExternalFoo { 48 | &self.0 49 | } 50 | } 51 | 52 | type ExternGenericExternalFoo = ExternalFoo; 53 | 54 | impl From> for ExternalFoo { 55 | fn from(native: cddl_lib::ExternGeneric) -> Self { 56 | native.0.into() 57 | } 58 | } 59 | 60 | impl From for cddl_lib::ExternGeneric { 61 | fn from(wasm: ExternalFoo) -> Self { 62 | cddl_lib::ExternGeneric::new(wasm.0) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/comment-dsl/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | 5 | #[test] 6 | fn group_type() { 7 | let addr_bytes = vec![]; 8 | let checksum = 5; 9 | 10 | let addr = Address::new(addr_bytes.clone(), checksum); 11 | assert_eq!(addr_bytes, addr.address); 12 | assert_eq!(checksum, addr.checksum); 13 | } 14 | 15 | // TODO: find a case that triggers this path 16 | // #[test] 17 | // fn no_key_group() { 18 | 19 | // } 20 | 21 | #[test] 22 | fn with_key_group() { 23 | let minfee_a = 1; 24 | let minfee_b = 2; 25 | 26 | let addr = ProtocolParamUpdate::new(minfee_a, minfee_b); 27 | assert_eq!(minfee_a, addr.minfee_a); 28 | assert_eq!(minfee_b, addr.minfee_b); 29 | } 30 | 31 | #[test] 32 | fn group_choice() { 33 | // just checking these fields exist with the expected name 34 | let ebb_block = Block::new_ebb_block_wrapper(vec![]); 35 | match &ebb_block { 36 | Block::EbbBlockWrapper{ ebb_block_cbor, .. } => { ebb_block_cbor.clone(); }, 37 | _ => {} 38 | }; 39 | let main_block = Block::new_main_block_wrapper(vec![]); 40 | match &main_block { 41 | Block::MainBlockWrapper{ main_block_cbor, .. } => { main_block_cbor.clone(); }, 42 | _ => {} 43 | }; 44 | assert!(true); 45 | } 46 | 47 | #[test] 48 | fn type_choice() { 49 | // just checking these fields exist with the expected name 50 | Typechoice::new_case1(Case1::new(vec![])); 51 | Typechoice::new_case2(Case2::new(vec![])); 52 | assert!(true); 53 | } 54 | 55 | #[test] 56 | fn type_choice_variants() { 57 | // just checking these fields exist with the expected name 58 | TypechoiceVariants::new_case1("".to_string()); 59 | TypechoiceVariants::new_case2(vec!["".to_string()]); 60 | assert!(true); 61 | } 62 | 63 | #[test] 64 | fn newtype() { 65 | let pm = ProtocolMagic::new(5); 66 | assert_eq!(pm.get(), 5); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/rust-wasm-split/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | use cbor_event::de::Deserializer; 5 | use serialization::Deserialize; 6 | 7 | fn deser_test(orig: &T) { 8 | print_cbor_types("orig", &orig.to_cbor_bytes()); 9 | let deser = T::deserialize(&mut Deserializer::from(std::io::Cursor::new(orig.to_cbor_bytes()))).unwrap(); 10 | print_cbor_types("deser", &deser.to_cbor_bytes()); 11 | assert_eq!(orig.to_cbor_bytes(), deser.to_cbor_bytes()); 12 | } 13 | 14 | #[test] 15 | fn foo() { 16 | deser_test(&Foo::new(436, String::from("jfkdsjfd"), vec![1, 1, 1])); 17 | } 18 | 19 | #[test] 20 | fn foo2_some() { 21 | deser_test(&Foo2::new(143546, Some(String::from("afdjfkjsiefefe")))); 22 | } 23 | 24 | #[test] 25 | fn foo2_none() { 26 | deser_test(&Foo2::new(143546, None)); 27 | } 28 | 29 | #[test] 30 | fn bar() { 31 | deser_test(&Bar::new(Foo::new(436, String::from("jfkdf"), vec![6, 4]), None)); 32 | } 33 | 34 | #[test] 35 | fn plain() { 36 | deser_test(&Plain::new(7576, String::from("wiorurri34h"))); 37 | } 38 | 39 | #[test] 40 | fn outer() { 41 | deser_test(&Outer::new(2143254, Plain::new(7576, String::from("wiorurri34h")))); 42 | } 43 | 44 | #[test] 45 | fn type_choice_0() { 46 | deser_test(&TypeChoice::I0); 47 | } 48 | 49 | #[test] 50 | fn type_choice_hello_world() { 51 | deser_test(&TypeChoice::Helloworld); 52 | } 53 | 54 | #[test] 55 | fn type_choice_uint() { 56 | deser_test(&TypeChoice::U64(53435364)); 57 | } 58 | 59 | #[test] 60 | fn type_choice_text() { 61 | deser_test(&TypeChoice::Text(String::from("jdfidsf83j3 jkrjefdfk !!"))); 62 | } 63 | 64 | #[test] 65 | fn type_choice_bytes() { 66 | deser_test(&TypeChoice::Bytes(vec![0x00, 0x01, 0xF7, 0xFF])); 67 | } 68 | 69 | #[test] 70 | fn type_choice_tagged_u64() { 71 | deser_test(&TypeChoice::U64(5)); 72 | } 73 | 74 | #[test] 75 | fn group_choice_foo() { 76 | deser_test(&GroupChoice::Foo(Foo::new(0, String::new(), vec![]))); 77 | } 78 | 79 | #[test] 80 | fn group_choice_0() { 81 | deser_test(&GroupChoice::GroupChoice1(37)); 82 | } 83 | 84 | #[test] 85 | fn group_choice_plain() { 86 | deser_test(&GroupChoice::Plain(Plain::new(354545, String::from("fdsfdsfdg")))); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | @tailwind base; 9 | @tailwind components; 10 | @tailwind utilities; 11 | 12 | @font-face { 13 | font-family: 'Manrope'; 14 | src: url('../fonts/Manrope-Regular.ttf'); 15 | } 16 | 17 | :root { 18 | --ifm-color-primary: #356ddb; 19 | --ifm-color-primary-dark: #356ddb; 20 | --ifm-color-primary-darker: #356ddb; 21 | --ifm-color-primary-darkest: #356ddb; 22 | --ifm-color-primary-light: #356ddb; 23 | --ifm-color-primary-lighter: #356ddb; 24 | --ifm-color-primary-lightest: #356ddb; 25 | --ifm-code-font-size: 95%; 26 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 27 | --ifm-spacing-horizontal: 40px; 28 | --ifm-font-color-base: #3b454e; 29 | --ifm-blockquote-border-left-width: 4px; 30 | --ifm-blockquote-color: #5c6975; 31 | --ifm-blockquote-padding-horizontal: 20px; 32 | --ifm-alert-padding-horizontal: 20px; 33 | --ifm-navbar-padding-horizontal: 16px; 34 | --tabs-bg-active: white; 35 | --tabs-text-inactive: rgb(156 163 175); 36 | --ifm-font-family-base: 'Manrope'; 37 | } 38 | 39 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 40 | [data-theme="dark"] { 41 | --ifm-color-primary: #ff9828; 42 | --ifm-color-primary-dark: #ff9828; 43 | --ifm-color-primary-darker: #ff9828; 44 | --ifm-color-primary-darkest: #ff9828; 45 | --ifm-color-primary-light: #ff9828; 46 | --ifm-color-primary-lighter: #ff9828; 47 | --ifm-color-primary-lightest: #ff9828; 48 | --ifm-font-color-base: #fff; 49 | --ifm-blockquote-color: #c8c8c8; 50 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 51 | --tabs-bg-active: #1c1c1d; 52 | --tabs-text-inactive: #bab0b0; 53 | } 54 | 55 | img { 56 | @apply max-w-[300px] mb-3; 57 | } 58 | 59 | .img-full { 60 | @apply w-full max-w-full; 61 | } 62 | 63 | .img-card { 64 | @apply max-w-[300px] mb-4; 65 | } 66 | .img-icon { 67 | @apply w-5 mb-0; 68 | } 69 | 70 | .tabs-container [role="tab"] { 71 | @apply rounded-t-md rounded-b-none border-[1px] border-solid border-gray-300 py-2 bg-[color:var(--tabs-bg-active)] border-b-0; 72 | } 73 | .tabs-container [role="tab"][aria-selected="false"] { 74 | @apply bg-[color:var(--tabs-bg)] text-gray-400; 75 | } 76 | .tabs-container [role="tabpanel"] { 77 | @apply border-[1px] border-solid border-gray-300 p-5 -mt-[17px]; 78 | } 79 | -------------------------------------------------------------------------------- /tests/custom_serialization: -------------------------------------------------------------------------------- 1 | // writes bytes using indefinite encoding chunked into 1-byte parts 2 | pub fn custom_serialize_bytes<'se, W: std::io::Write>( 3 | serializer: &'se mut cbor_event::se::Serializer, 4 | bytes: &[u8], 5 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 6 | serializer.write_raw_bytes(&[0x5f])?; 7 | for byte in bytes { 8 | serializer.write_bytes(&[*byte])?; 9 | } 10 | serializer.write_special(cbor_event::Special::Break) 11 | } 12 | 13 | // read bytes and verify the 1-byte chunking of custom_serialize_bytes() 14 | pub fn custom_deserialize_bytes( 15 | raw: &mut cbor_event::de::Deserializer, 16 | ) -> Result, DeserializeError> { 17 | let (bytes, bytes_enc) = raw.bytes_sz()?; 18 | match bytes_enc { 19 | cbor_event::StringLenSz::Len(_sz) => Err(DeserializeFailure::CBOR(cbor_event::Error::CustomError("custom_deserialize_bytes(): needs indefinite chunking".to_owned())).into()), 20 | cbor_event::StringLenSz::Indefinite(chunks) => { 21 | for (chunk_len, _chunk_sz) in chunks.iter() { 22 | if *chunk_len != 1 { 23 | return Err(DeserializeFailure::CBOR(cbor_event::Error::CustomError(format!("custom_deserialize_bytes(): chunks need to be 1-len, found: {:?}", chunks))).into()); 24 | } 25 | } 26 | Ok(bytes) 27 | } 28 | } 29 | } 30 | 31 | // writes as hex text 32 | pub fn write_hex_string<'se, W: std::io::Write>( 33 | serializer: &'se mut cbor_event::se::Serializer, 34 | bytes: &[u8], 35 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 36 | serializer.write_text(hex::encode(bytes)) 37 | } 38 | 39 | // reads hex text to bytes 40 | pub fn read_hex_string( 41 | raw: &mut cbor_event::de::Deserializer, 42 | ) -> Result, DeserializeError> { 43 | let text = raw.text()?; 44 | hex::decode(text).map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()) 45 | } 46 | 47 | // must include the tag since @custom_serialize at field-level overrides everything 48 | pub fn write_tagged_uint_str<'se, W: std::io::Write>( 49 | serializer: &'se mut cbor_event::se::Serializer, 50 | uint: &u64, 51 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 52 | serializer 53 | .write_tag(9)? 54 | .write_text(uint.to_string()) 55 | } 56 | 57 | pub fn read_tagged_uint_str( 58 | raw: &mut cbor_event::de::Deserializer, 59 | ) -> Result { 60 | use std::str::FromStr; 61 | let tag = raw.tag()?; 62 | let text = raw.text()?; 63 | u64::from_str(&text).map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()) 64 | } 65 | -------------------------------------------------------------------------------- /docs/docs/current_capacities.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Current capacities 6 | 7 | ## Types 8 | 9 | * Primitives - `bytes`, `bstr`, `tstr`, `text`, `uint`, `nint` 10 | * Fixed values - `null`, `nil`, `true`, `false` 11 | * Array values - `[uint]` 12 | * Table types as members - `foo = ( x: { * a => b } )` 13 | * Inline groups at root level - `foo = ( a: uint, b: uint)` 14 | * Array groups - `foo = [uint, tstr, 0, bytes]` 15 | * Map groups (both struct-type and table-type) - `foo = { a: uint, b: tstr }` or `bar = { * uint => tstr }` 16 | * Embedding groups in other groups - `foo = (0, bstr) bar = [uint, foo, foo]` 17 | * Group choices - `foo = [ 0, uint // 1, tstr, uint // tstr }` 18 | * Tagged major types - `rational = #6.30([ numerator : uint, denominator : uint])` 19 | * Optional fields - `foo = { ? 0 : bytes }` 20 | * Type aliases - `foo = bar` 21 | * Type choices - `foo = uint / tstr` 22 | * Serialization for all supported types. 23 | * Deserialization for almost all supported types (see limitations section). 24 | * CDDL Generics - `foo = [T]`, `bar = foo` 25 | * Length bounds - `foo = bytes .size (0..32)` 26 | * cbor in bytes - `foo_bytes = bytes .cbor foo` 27 | * Support for the CDDL standard prelude (using raw CDDL from the RFC) - `biguint`, etc 28 | * default values - `? key : uint .default 0` 29 | 30 | We generate getters for all fields, and setters for optional fields. Mandatory fields are set via the generated constructor. All wasm-facing functions are set to take references for non-primitives and clone when needed. Returns are also cloned. This helps make usage from wasm more memory safe. 31 | 32 | Identifiers and fields are also changed to rust style. ie `foo_bar = { Field-Name: text }` gets converted into `struct FooBar { field_name: String }` 33 | 34 | ## Group choices 35 | 36 | Group choices are handled as an enum with each choice being a variant. This enum is then wrapped around a wasm-exposed struct as `wasm_bindgen` does not support rust enums with members/values. 37 | Group choices that have only a single non-fixed-value field use just that field as the enum variant, otherwise we create a `GroupN` for the `Nth` variant enum with the fields of that group choice. Any fixed values are resolved purely in serialization code, so `0, "hello", uint` puts the `uint` in the enum variant directly instead of creating a new struct. 38 | ## Type choices 39 | 40 | Type choices are handled via enums as well with the name defaulting to `AOrBOrC` for `A / B / C` when inlined as a field/etc, and will take on the type identifier if provided ie `foo = A / B / C` would be `Foo`. 41 | Any field that is `T / null` is transformed as a special case into `Option` rather than creating a `TOrNull` enum. 42 | 43 | A special case for this is when all types are fixed values e.g. `foo = 0 / 1 / "hello"`, in which case we generate a special c-style enum in the rust. This will have wasm_bindgen tags so it can be directly used in the wasm crate. Encoding variables (for `--preserve-encodings=true`) are stored where the enum is used like with other primitives. 44 | -------------------------------------------------------------------------------- /static/run-json2ts.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const json2ts = require('json-schema-to-typescript'); 3 | const path = require('path'); 4 | 5 | const schemasDir = path.join('rust', 'json-gen', 'schemas'); 6 | const schemaFiles = fs.readdirSync(schemasDir).filter(file => path.extname(file) === '.json'); 7 | 8 | function replaceRef(obj) { 9 | if (obj['$ref'] != null && typeof obj['$ref'] === 'string' && obj['$ref'].startsWith('#/definitions/')) { 10 | obj['$ref'] = obj['$ref'].replace(/^(#\/definitions\/)/, '') + '.json'; 11 | console.log(`replacing: ${obj['$ref']}`); 12 | } 13 | } 14 | 15 | function replaceRefs(node) { 16 | Object.entries(node).forEach(([k, v]) => { 17 | if (typeof v === 'object') { 18 | replaceRef(v); 19 | replaceRefs(v); 20 | } 21 | }); 22 | } 23 | 24 | Promise.all(schemaFiles.map(schemaFile => { 25 | const completeName = path.join(schemasDir, schemaFile); 26 | const originalFile = fs.readFileSync(completeName, 'utf8'); 27 | let schemaObj = JSON.parse(originalFile); 28 | 29 | // this gets rid of [k: string]: unknown in generated .ts 30 | // but we shouldn't do this if it already exists in the case 31 | // of map types 32 | if (typeof schemaObj.additionalProperties !== 'object') { 33 | schemaObj.additionalProperties = false; 34 | } 35 | return json2ts.compile(schemaObj, schemaFile, { 36 | declareExternallyReferenced: false, 37 | cwd: schemasDir,//path.join(process.cwd(), schemasDir), 38 | bannerComment: '' 39 | }).catch(e => { console.error(`${schemaFile}: ${e}`); }); 40 | 41 | })).then(tsDefs => { 42 | fs.mkdirSync(path.join('rust', 'json-gen', 'output'), { recursive: true }); 43 | const defs = tsDefs.join('').split(/\r?\n/); 44 | let dedupedDefs = []; 45 | let start = null; 46 | let added = new Set(); 47 | const addDef = (cur) => { 48 | if (start != null) { 49 | let defName = defs[start].match(/export\s+(type|interface)\s+(\w+).*/); 50 | let defKey = null; 51 | if (defName != null && defName.length > 2) { 52 | defKey = defName[2]; 53 | } else { 54 | console.error(`run-json2ts.js could not find name for de-dup(${defName != null}): "${defs[start]}"`); 55 | } 56 | if (defKey == null || !added.has(defKey)) { 57 | for (let j = start; j < cur; ++j) { 58 | dedupedDefs.push(defs[j]); 59 | } 60 | if (defKey != null) { 61 | added.add(defKey); 62 | } 63 | } 64 | } 65 | start = cur; 66 | }; 67 | for (let i = 0; i < defs.length; ++i) { 68 | if (defs[i].startsWith('export')) { 69 | addDef(i); 70 | } 71 | } 72 | addDef(defs.length); 73 | // prepend 'JSON' to all identifiers here so they don't conflict with main .ts types 74 | for (let i = 0; i < dedupedDefs.length; ++i) { 75 | for (let id of added) { 76 | dedupedDefs[i] = dedupedDefs[i].replace(new RegExp(`\\b${id}\\b`), id + 'JSON'); 77 | } 78 | } 79 | return fs.writeFileSync(path.join('rust', 'json-gen', 'output', 'json-types.d.ts'), dedupedDefs.join('\n')); 80 | }); 81 | 82 | -------------------------------------------------------------------------------- /tests/external_rust_defs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub struct ExternalFoo { 3 | pub index_0: u64, 4 | pub index_1: String, 5 | pub index_2: Vec, 6 | } 7 | 8 | impl ExternalFoo { 9 | pub fn new(index_0: u64, index_1: String, index_2: Vec) -> Self { 10 | Self { 11 | index_0, 12 | index_1, 13 | index_2, 14 | } 15 | } 16 | } 17 | 18 | impl cbor_event::se::Serialize for ExternalFoo { 19 | fn serialize<'se, W: std::io::Write>( 20 | &self, 21 | serializer: &'se mut cbor_event::se::Serializer, 22 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 23 | serializer.write_array(cbor_event::Len::Len(3))?; 24 | serializer.write_unsigned_integer(self.index_0)?; 25 | serializer.write_text(&self.index_1)?; 26 | serializer.write_bytes(&self.index_2)?; 27 | Ok(serializer) 28 | } 29 | } 30 | 31 | impl serialization::Deserialize for ExternalFoo { 32 | fn deserialize(raw: &mut cbor_event::de::Deserializer) -> Result { 33 | let len = raw.array()?; 34 | let mut read_len = CBORReadLen::new(len); 35 | read_len.read_elems(3)?; 36 | (|| -> Result<_, error::DeserializeError> { 37 | let index_0 = Ok(raw.unsigned_integer()? as u64) 38 | .map_err(|e: error::DeserializeError| e.annotate("index_0"))?; 39 | let index_1 = 40 | Ok(raw.text()? as String).map_err(|e: error::DeserializeError| e.annotate("index_1"))?; 41 | let index_2 = 42 | Ok(raw.bytes()? as Vec).map_err(|e: error::DeserializeError| e.annotate("index_2"))?; 43 | match len { 44 | cbor_event::Len::Len(_) => (), 45 | cbor_event::Len::Indefinite => match raw.special()? { 46 | cbor_event::Special::Break => (), 47 | _ => return Err(error::DeserializeFailure::EndingBreakMissing.into()), 48 | }, 49 | } 50 | Ok(ExternalFoo { 51 | index_0, 52 | index_1, 53 | index_2, 54 | }) 55 | })() 56 | .map_err(|e| e.annotate("ExternalFoo")) 57 | } 58 | } 59 | 60 | #[derive(Clone, Debug)] 61 | pub struct ExternGeneric(pub T); 62 | 63 | impl ExternGeneric { 64 | pub fn new(x: T) -> Self { 65 | Self(x) 66 | } 67 | } 68 | 69 | impl cbor_event::se::Serialize for ExternGeneric { 70 | fn serialize<'se, W: std::io::Write>( 71 | &self, 72 | serializer: &'se mut cbor_event::se::Serializer, 73 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 74 | self.0.serialize(serializer) 75 | } 76 | } 77 | 78 | impl serialization::Deserialize for ExternGeneric { 79 | fn deserialize(raw: &mut cbor_event::de::Deserializer) -> Result { 80 | T::deserialize(raw).map(Self) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /static/serialization_preserve_force_canonical.rs: -------------------------------------------------------------------------------- 1 | #[inline] 2 | pub fn fit_sz(len: u64, sz: Option, force_canonical: bool) -> cbor_event::Sz { 3 | match sz { 4 | Some(sz) => if !force_canonical && len <= sz_max(sz) { 5 | sz 6 | } else { 7 | cbor_event::Sz::canonical(len) 8 | }, 9 | None => cbor_event::Sz::canonical(len), 10 | } 11 | } 12 | 13 | impl LenEncoding { 14 | pub fn to_len_sz(&self, len: u64, force_canonical: bool) -> cbor_event::LenSz { 15 | if force_canonical { 16 | cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)) 17 | } else { 18 | match self { 19 | Self::Canonical => cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)), 20 | Self::Definite(sz) => if sz_max(*sz) >= len { 21 | cbor_event::LenSz::Len(len, *sz) 22 | } else { 23 | cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)) 24 | }, 25 | Self::Indefinite => cbor_event::LenSz::Indefinite, 26 | } 27 | } 28 | } 29 | 30 | pub fn end<'a, W: Write + Sized>(&self, serializer: &'a mut Serializer, force_canonical: bool) -> cbor_event::Result<&'a mut Serializer> { 31 | if !force_canonical && *self == Self::Indefinite { 32 | serializer.write_special(cbor_event::Special::Break)?; 33 | } 34 | Ok(serializer) 35 | } 36 | } 37 | 38 | impl StringEncoding { 39 | pub fn to_str_len_sz(&self, len: u64, force_canonical: bool) -> cbor_event::StringLenSz { 40 | if force_canonical { 41 | cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)) 42 | } else { 43 | match self { 44 | Self::Canonical => cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)), 45 | Self::Definite(sz) => if sz_max(*sz) >= len { 46 | cbor_event::StringLenSz::Len(*sz) 47 | } else { 48 | cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)) 49 | }, 50 | Self::Indefinite(lens) => cbor_event::StringLenSz::Indefinite(lens.clone()), 51 | } 52 | } 53 | } 54 | } 55 | 56 | pub trait Serialize { 57 | fn to_canonical_cbor_bytes(&self) -> Vec { 58 | let mut buf = Serializer::new_vec(); 59 | self.serialize(&mut buf, true).unwrap(); 60 | buf.finalize() 61 | } 62 | 63 | fn to_cbor_bytes(&self) -> Vec { 64 | let mut buf = Serializer::new_vec(); 65 | self.serialize(&mut buf, false).unwrap(); 66 | buf.finalize() 67 | } 68 | 69 | fn serialize<'a, W: Write + Sized>( 70 | &self, 71 | serializer: &'a mut Serializer, 72 | force_canonical: bool, 73 | ) -> cbor_event::Result<&'a mut Serializer>; 74 | } 75 | 76 | pub trait SerializeEmbeddedGroup { 77 | fn serialize_as_embedded_group<'a, W: Write + Sized>( 78 | &self, 79 | serializer: &'a mut Serializer, 80 | force_canonical: bool, 81 | ) -> cbor_event::Result<&'a mut Serializer>; 82 | } 83 | -------------------------------------------------------------------------------- /docs/docs/integration-other.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | import Tabs from '@theme/Tabs'; 5 | import TabItem from '@theme/TabItem'; 6 | 7 | 8 | # Integration with other cddl-codegen libraries 9 | 10 | 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. 11 | 12 | ## Common cddl-codegen traits 13 | 14 | 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. 15 | 16 | ## CML macros 17 | 18 | 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. 19 | 20 | ## Externally defined types 21 | 22 | ### `_CDDL_CODEGEN_EXTERN_TYPE_` vs `_CDDL_CODEGEN_RAW_BYTES_TYPE_` 23 | 24 | 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_`. 25 | 26 | ### Import pathing 27 | 28 | 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. 29 | 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`](https://github.com/dcSpark/cardano-multiplatform-lib/tree/develop/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. 30 | 31 | ### Non-black-box types 32 | 33 | 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. 34 | -------------------------------------------------------------------------------- /tests/external_rust_defs_compiles_with_json_preserve: -------------------------------------------------------------------------------- 1 | // same as external_rust_defs, but with JSON traits + serialization code compiles with --preserve-encodings=true 2 | // as this changes the API for CBORReadLen. This code does NOT support preserving encodings - just compiles with it. 3 | 4 | #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)] 5 | pub struct ExternalFoo { 6 | pub index_0: u64, 7 | pub index_1: String, 8 | pub index_2: Vec, 9 | } 10 | 11 | impl ExternalFoo { 12 | pub fn new(index_0: u64, index_1: String, index_2: Vec) -> Self { 13 | Self { 14 | index_0, 15 | index_1, 16 | index_2, 17 | } 18 | } 19 | } 20 | 21 | impl cbor_event::se::Serialize for ExternalFoo { 22 | fn serialize<'se, W: std::io::Write>( 23 | &self, 24 | serializer: &'se mut cbor_event::se::Serializer, 25 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 26 | serializer.write_array(cbor_event::Len::Len(3))?; 27 | serializer.write_unsigned_integer(self.index_0)?; 28 | serializer.write_text(&self.index_1)?; 29 | serializer.write_bytes(&self.index_2)?; 30 | Ok(serializer) 31 | } 32 | } 33 | 34 | impl serialization::Deserialize for ExternalFoo { 35 | fn deserialize(raw: &mut cbor_event::de::Deserializer) -> Result { 36 | let len = raw.array()?; 37 | let mut read_len = CBORReadLen::new(cbor_event::LenSz::Indefinite); 38 | read_len.read_elems(3)?; 39 | (|| -> Result<_, error::DeserializeError> { 40 | let index_0 = Ok(raw.unsigned_integer()? as u64) 41 | .map_err(|e: error::DeserializeError| e.annotate("index_0"))?; 42 | let index_1 = 43 | Ok(raw.text()? as String).map_err(|e: error::DeserializeError| e.annotate("index_1"))?; 44 | let index_2 = 45 | Ok(raw.bytes()? as Vec).map_err(|e: error::DeserializeError| e.annotate("index_2"))?; 46 | match len { 47 | cbor_event::Len::Len(_) => (), 48 | cbor_event::Len::Indefinite => match raw.special()? { 49 | cbor_event::Special::Break => (), 50 | _ => return Err(error::DeserializeFailure::EndingBreakMissing.into()), 51 | }, 52 | } 53 | Ok(ExternalFoo { 54 | index_0, 55 | index_1, 56 | index_2, 57 | }) 58 | })() 59 | .map_err(|e| e.annotate("ExternalFoo")) 60 | } 61 | } 62 | 63 | #[derive(Clone, Debug)] 64 | pub struct ExternGeneric(pub T); 65 | 66 | impl ExternGeneric { 67 | pub fn new(x: T) -> Self { 68 | Self(x) 69 | } 70 | } 71 | 72 | impl cbor_event::se::Serialize for ExternGeneric { 73 | fn serialize<'se, W: std::io::Write>( 74 | &self, 75 | serializer: &'se mut cbor_event::se::Serializer, 76 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 77 | self.0.serialize(serializer) 78 | } 79 | } 80 | 81 | impl serialization::Deserialize for ExternGeneric { 82 | fn deserialize(raw: &mut cbor_event::de::Deserializer) -> Result { 83 | T::deserialize(raw).map(Self) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /static/serialization_preserve.rs: -------------------------------------------------------------------------------- 1 | pub struct CBORReadLen { 2 | deser_len: cbor_event::LenSz, 3 | read: u64, 4 | } 5 | 6 | impl CBORReadLen { 7 | pub fn new(len: cbor_event::LenSz) -> Self { 8 | Self { 9 | deser_len: len, 10 | read: 0, 11 | } 12 | } 13 | 14 | pub fn read(&self) -> u64 { 15 | self.read 16 | } 17 | 18 | // Marks {n} values as being read, and if we go past the available definite length 19 | // given by the CBOR, we return an error. 20 | pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> { 21 | match self.deser_len { 22 | cbor_event::LenSz::Len(n, _) => { 23 | self.read += count as u64; 24 | if self.read > n { 25 | Err(DeserializeFailure::DefiniteLenMismatch(n, None)) 26 | } else { 27 | Ok(()) 28 | } 29 | }, 30 | cbor_event::LenSz::Indefinite => Ok(()), 31 | } 32 | } 33 | 34 | pub fn finish(&self) -> Result<(), DeserializeFailure> { 35 | match self.deser_len { 36 | cbor_event::LenSz::Len(n, _) => { 37 | if self.read == n { 38 | Ok(()) 39 | } else { 40 | Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read))) 41 | } 42 | }, 43 | cbor_event::LenSz::Indefinite => Ok(()), 44 | } 45 | } 46 | } 47 | 48 | pub trait DeserializeEmbeddedGroup { 49 | fn deserialize_as_embedded_group( 50 | raw: &mut Deserializer, 51 | read_len: &mut CBORReadLen, 52 | len: cbor_event::LenSz, 53 | ) -> Result where Self: Sized; 54 | } 55 | 56 | #[inline] 57 | pub fn sz_max(sz: cbor_event::Sz) -> u64 { 58 | match sz { 59 | cbor_event::Sz::Inline => 23u64, 60 | cbor_event::Sz::One => u8::MAX as u64, 61 | cbor_event::Sz::Two => u16::MAX as u64, 62 | cbor_event::Sz::Four => u32::MAX as u64, 63 | cbor_event::Sz::Eight => u64::MAX, 64 | } 65 | } 66 | 67 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 68 | pub enum LenEncoding { 69 | Canonical, 70 | Definite(cbor_event::Sz), 71 | Indefinite, 72 | } 73 | 74 | impl Default for LenEncoding { 75 | fn default() -> Self { 76 | Self::Canonical 77 | } 78 | } 79 | 80 | impl From for LenEncoding { 81 | fn from(len_sz: cbor_event::LenSz) -> Self { 82 | match len_sz { 83 | cbor_event::LenSz::Len(len, sz) => if cbor_event::Sz::canonical(len) == sz { 84 | Self::Canonical 85 | } else { 86 | Self::Definite(sz) 87 | }, 88 | cbor_event::LenSz::Indefinite => Self::Indefinite, 89 | } 90 | } 91 | } 92 | 93 | #[derive(Debug, PartialEq, Eq, Clone)] 94 | pub enum StringEncoding { 95 | Canonical, 96 | Indefinite(Vec<(u64, cbor_event::Sz)>), 97 | Definite(cbor_event::Sz), 98 | } 99 | 100 | impl Default for StringEncoding { 101 | fn default() -> Self { 102 | Self::Canonical 103 | } 104 | } 105 | 106 | impl From for StringEncoding { 107 | fn from(len_sz: cbor_event::StringLenSz) -> Self { 108 | match len_sz { 109 | cbor_event::StringLenSz::Len(sz) => Self::Definite(sz), 110 | cbor_event::StringLenSz::Indefinite(lens) => Self::Indefinite(lens), 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/json/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | use cbor_event::de::Deserializer; 5 | use serialization::Deserialize; 6 | 7 | #[test] 8 | fn bytes_wrapper() { 9 | let bytes = vec![0xBA, 0xAD, 0xF0, 0x0D]; 10 | let hex = format!("\"{}\"", hex::encode(&bytes)); 11 | let from_bytes = BytesWrapper::new(bytes.clone()); 12 | let from_hex: BytesWrapper = serde_json::from_str(&hex).unwrap(); 13 | assert_eq!(hex, serde_json::to_string_pretty(&from_bytes).unwrap()); 14 | assert_eq!(hex, serde_json::to_string_pretty(&from_hex).unwrap()); 15 | } 16 | 17 | #[test] 18 | fn str_wrapper() { 19 | let text = "hello, world"; 20 | let json_str = format!("\"{text}\""); 21 | let from_str = StrWrapper::new(text.to_owned()); 22 | let from_json: StrWrapper = serde_json::from_str(&json_str).unwrap(); 23 | assert_eq!(json_str, serde_json::to_string_pretty(&from_str).unwrap()); 24 | assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); 25 | } 26 | 27 | fn json_wrapper_test(value: V) 28 | where W: TryFrom + serde::Serialize + for <'de> serde::Deserialize<'de>, 29 | V: std::fmt::Display, 30 | >::Error: std::fmt::Debug 31 | { 32 | let json_str = value.to_string(); 33 | let from_value = W::try_from(value).unwrap(); 34 | let from_json: W = serde_json::from_str(&json_str).unwrap(); 35 | assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); 36 | assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); 37 | } 38 | 39 | #[test] 40 | fn u8_wrapper() { 41 | json_wrapper_test::(u8::MIN); 42 | json_wrapper_test::(u8::MAX); 43 | } 44 | 45 | #[test] 46 | fn u64_wrapper() { 47 | json_wrapper_test::(u64::MIN); 48 | json_wrapper_test::(u64::MAX); 49 | } 50 | 51 | #[test] 52 | fn i16_wrapper() { 53 | json_wrapper_test::(i16::MIN); 54 | json_wrapper_test::(i16::MAX); 55 | } 56 | 57 | #[test] 58 | fn i64_wrapper() { 59 | json_wrapper_test::(i64::MIN); 60 | json_wrapper_test::(i64::MAX); 61 | } 62 | 63 | #[test] 64 | fn nint_wrapper() { 65 | json_wrapper_test::(u64::MIN); 66 | json_wrapper_test::(u64::MAX); 67 | } 68 | 69 | // #[test] 70 | // fn bool_wrapper() { 71 | // json_wrapper_test::(false); 72 | // json_wrapper_test::(true); 73 | // } 74 | 75 | #[test] 76 | fn struct_wrapper() { 77 | let json_str = u64::MAX.to_string(); 78 | let from_value = StructWrapper::from(U64Wrapper::from(u64::MAX)); 79 | let from_json: StructWrapper = serde_json::from_str(&json_str).unwrap(); 80 | assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); 81 | assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); 82 | } 83 | 84 | #[test] 85 | fn custom_wrapper() { 86 | let json_str = "\"1234\""; 87 | let from_value = CustomWrapper::from(1234u64); 88 | let from_json: CustomWrapper = serde_json::from_str(&json_str).unwrap(); 89 | assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); 90 | assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /supported.cddl: -------------------------------------------------------------------------------- 1 | unit_interval = #6.30([uint, uint]) 2 | 3 | coin = uint 4 | epoch = uint 5 | 6 | vkey = bytes 7 | signature = bytes 8 | 9 | hash28 = [uint] 10 | hash32 = [uint] 11 | 12 | addr_keyhash = hash28 13 | scripthash = hash28 14 | 15 | genesis_delegate_hash = hash32 16 | pool_keyhash = hash32 17 | genesishash = hash32 18 | metadata_hash = hash32 19 | vrf_keyhash = hash32 20 | 21 | vkeywitness = [ vkey, signature ] 22 | 23 | msig_pubkey = [0, addr_keyhash] 24 | msig_all = [1, [ * multisig_script ]] 25 | msig_any = [2, [ * multisig_script ]] 26 | msig_n_of_k = [3, n: uint, [ * multisig_script ]] 27 | 28 | multisig_script = 29 | [ msig_pubkey 30 | // msig_all 31 | // msig_any 32 | // msig_n_of_k 33 | ] 34 | 35 | transaction_index = uint 36 | 37 | 38 | transaction_input = [ transaction_id : $hash32 39 | , index : uint 40 | ] 41 | 42 | transaction_output = [address, amount : uint] 43 | 44 | stake_credential = 45 | [ 0, addr_keyhash 46 | // 1, scripthash 47 | ] 48 | 49 | 50 | withdrawals = { * stake_credential => coin } 51 | 52 | 53 | ; we'll remove this later 54 | address = bytes 55 | 56 | move_instantaneous_reward = { * stake_credential => coin } 57 | 58 | relay = 59 | [ single_host_addr 60 | // single_host_name 61 | // multi_host_name 62 | ] 63 | 64 | pool_params = ( operator: pool_keyhash 65 | , vrf_keyhash: vrf_keyhash 66 | , pledge: coin 67 | , cost: coin 68 | , margin: unit_interval 69 | , reward_account: stake_credential 70 | , pool_owners: [addr_keyhash] 71 | , relays: [* relay] 72 | , pool_metadata: pool_metadata / null 73 | ) 74 | 75 | port = uint .le 65535 76 | ipv4 = bytes .size 4 77 | ipv6 = bytes .size 16 78 | dns_name = tstr .size (0..64) 79 | 80 | single_host_addr = ( 0 81 | , port / null 82 | , ipv4 / null 83 | , ipv6 / null 84 | ) 85 | single_host_name = ( 1 86 | , port / null 87 | , dns_name ; An A or AAAA DNS record 88 | ) 89 | multi_host_name = ( 2 90 | , port / null 91 | , dns_name ; A SRV DNS record 92 | ) 93 | 94 | pool_metadata = [url, metadata_hash] 95 | url = tstr .size (0..64) 96 | 97 | stake_registration = [0, stake_credential] 98 | stake_deregistration = [1, stake_credential] 99 | stake_delegation = [2, stake_credential, pool_keyhash] 100 | pool_registration = [3, pool_params] 101 | pool_retirement = [4, pool_keyhash, epoch] 102 | genesis_key_delegation = [5, genesishash, genesis_delegate_hash] 103 | move_instantaneous_rewards_cert = [6, move_instantaneous_reward] 104 | 105 | certificate = 106 | [ stake_registration 107 | // stake_deregistration 108 | // stake_delegation 109 | // pool_registration 110 | // pool_retirement 111 | // genesis_key_delegation 112 | // move_instantaneous_rewards_cert 113 | ] 114 | 115 | transaction_body = 116 | { 0 : [transaction_input] 117 | , 1 : [* transaction_output] 118 | , 2 : coin ; fee 119 | , 3 : uint ; ttl 120 | , ? 4 : [* certificate] 121 | , ? 5 : withdrawals 122 | ; , ? 6 : update 123 | ; , ? 7 : metadata_hash 124 | } 125 | 126 | transaction_witness_set = 127 | { ?0 => [* vkeywitness ] 128 | , ?1 => [* multisig_script ] 129 | ; In the future, new kinds of witnesses can be added like this: 130 | ; , ?2 => [* monetary_policy_script ] 131 | ; , ?3 => [* plutus_script ] 132 | } 133 | 134 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const lightCodeTheme = require("prism-react-renderer/themes/github"); 5 | const darkCodeTheme = require("prism-react-renderer/themes/dracula"); 6 | 7 | /** @type {import('@docusaurus/types').Config} */ 8 | const config = { 9 | title: "cddl-codegen", 10 | tagline: 'Getting started', 11 | url: "https://dcspark.github.io", 12 | baseUrl: "/cddl-codegen/", 13 | onBrokenLinks: "throw", 14 | onBrokenMarkdownLinks: "warn", 15 | favicon: "img/favicon.svg", 16 | 17 | // GitHub pages deployment config. 18 | // If you aren't using GitHub pages, you don't need these. 19 | organizationName: "dcSpark", // Usually your GitHub org/user name. 20 | projectName: "cddl-codegen", // Usually your repo name. 21 | 22 | // Even if you don't use internalization, you can use this field to set useful 23 | // metadata like html lang. For example, if your site is Chinese, you may want 24 | // to replace "en" with "zh-Hans". 25 | // i18n: { 26 | // defaultLocale: "en", 27 | // locales: ["en"], 28 | // localeConfigs: { 29 | // en: { 30 | // label: "English" 31 | // } 32 | // } 33 | // }, 34 | themes: [ 35 | // ... Your other themes. 36 | [ 37 | require.resolve("@easyops-cn/docusaurus-search-local"), 38 | { 39 | docsRouteBasePath: '/', 40 | // ... Your options. 41 | // `hashed` is recommended as long-term-cache of index file is possible. 42 | hashed: true, 43 | // For Docs using Chinese, The `language` is recommended to set to: 44 | // ``` 45 | // language: ["en", "zh"], 46 | // ``` 47 | }, 48 | ], 49 | ], 50 | 51 | presets: [ 52 | [ 53 | "classic", 54 | /** @type {import('@docusaurus/preset-classic').Options} */ 55 | ({ 56 | docs: { 57 | breadcrumbs: false, 58 | sidebarPath: require.resolve("./sidebars.js"), 59 | routeBasePath: "/", // Serve the docs at the site's root 60 | // Please change this to your repo. 61 | // Remove this to remove the "edit this page" links. 62 | // editUrl: "https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/", 63 | }, 64 | blog: false, 65 | // blog: { 66 | // showReadingTime: true, 67 | // // Please change this to your repo. 68 | // // Remove this to remove the "edit this page" links. 69 | // // editUrl: "https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/", 70 | // }, 71 | theme: { 72 | customCss: require.resolve("./src/css/custom.css"), 73 | }, 74 | }), 75 | ], 76 | ], 77 | plugins: [ 78 | async function myPlugin(context, options) { 79 | return { 80 | name: "docusaurus-tailwindcss", 81 | configurePostCss(postcssOptions) { 82 | // Appends TailwindCSS and AutoPrefixer. 83 | postcssOptions.plugins.push(require("tailwindcss")); 84 | postcssOptions.plugins.push(require("autoprefixer")); 85 | return postcssOptions; 86 | }, 87 | }; 88 | }, 89 | ], 90 | themeConfig: 91 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 92 | ({ 93 | navbar: { 94 | title: "cddl-codegen", 95 | logo: { 96 | alt: "dcSpark logo", 97 | src: "img/favicon.svg", 98 | }, 99 | items: [ 100 | { 101 | type: "localeDropdown", 102 | position: "right", 103 | }, 104 | { 105 | href: "https://github.com/dcSpark/cddl-codegen", 106 | label: "GitHub", 107 | position: "right", 108 | }, 109 | ], 110 | }, 111 | footer: { 112 | style: "dark", 113 | copyright: `Copyright © ${new Date().getFullYear()} dcSpark. Built with Docusaurus.`, 114 | }, 115 | prism: { 116 | theme: lightCodeTheme, 117 | darkTheme: darkCodeTheme, 118 | additionalLanguages: ['rust'], 119 | }, 120 | }), 121 | }; 122 | 123 | module.exports = config; 124 | -------------------------------------------------------------------------------- /tests/custom_serialization_preserve: -------------------------------------------------------------------------------- 1 | // writes bytes using indefinite encoding chunked into 1-byte parts 2 | pub fn custom_serialize_bytes<'se, W: std::io::Write>( 3 | serializer: &'se mut cbor_event::se::Serializer, 4 | bytes: &[u8], 5 | enc: &StringEncoding, 6 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 7 | let szs = match enc { 8 | StringEncoding::Indefinite(encs) => { 9 | encs.iter().map(|(_l, e)| *e).chain(std::iter::repeat(cbor_event::Sz::Inline)).take(bytes.len()).collect::>() 10 | } 11 | _ => std::iter::repeat(cbor_event::Sz::Inline).take(bytes.len()).collect::>() 12 | }; 13 | serializer.write_raw_bytes(&[0x5f])?; 14 | for (sz, byte) in szs.iter().zip(bytes.iter()) { 15 | serializer.write_bytes_sz(&[*byte], cbor_event::StringLenSz::Len(*sz))?; 16 | } 17 | serializer.write_special(cbor_event::Special::Break) 18 | } 19 | 20 | // read bytes and verify the 1-byte chunking of custom_serialize_bytes() 21 | pub fn custom_deserialize_bytes( 22 | raw: &mut cbor_event::de::Deserializer, 23 | ) -> Result<(Vec, StringEncoding), DeserializeError> { 24 | let (bytes, bytes_enc) = raw.bytes_sz()?; 25 | match &bytes_enc { 26 | cbor_event::StringLenSz::Len(_sz) => Err(DeserializeFailure::CBOR(cbor_event::Error::CustomError("custom_deserialize_bytes(): needs indefinite chunking".to_owned())).into()), 27 | cbor_event::StringLenSz::Indefinite(chunks) => { 28 | for (chunk_len, _chunk_sz) in chunks.iter() { 29 | if *chunk_len != 1 { 30 | return Err(DeserializeFailure::CBOR(cbor_event::Error::CustomError(format!("custom_deserialize_bytes(): chunks need to be 1-len, found: {:?}", chunks))).into()); 31 | } 32 | } 33 | Ok((bytes, bytes_enc.into())) 34 | } 35 | } 36 | } 37 | 38 | // writes as hex text 39 | pub fn write_hex_string<'se, W: std::io::Write>( 40 | serializer: &'se mut cbor_event::se::Serializer, 41 | bytes: &[u8], 42 | enc: &StringEncoding, 43 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 44 | serializer.write_text_sz(hex::encode(bytes), enc.to_str_len_sz(bytes.len() as u64)) 45 | } 46 | 47 | // reads hex text to bytes 48 | pub fn read_hex_string( 49 | raw: &mut cbor_event::de::Deserializer, 50 | ) -> Result<(Vec, StringEncoding), DeserializeError> { 51 | let (text, text_enc) = raw.text_sz()?; 52 | hex::decode(text) 53 | .map(|bytes| (bytes, text_enc.into())) 54 | .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()) 55 | } 56 | 57 | // must include the tag since @custom_serialize at field-level overrides everything 58 | pub fn write_tagged_uint_str<'se, W: std::io::Write>( 59 | serializer: &'se mut cbor_event::se::Serializer, 60 | uint: &u64, 61 | tag_encoding: Option, 62 | text_encoding: Option, 63 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> { 64 | let uint_string = uint.to_string(); 65 | let text_encoding = text_encoding 66 | .map(|enc| crate::serialization::StringEncoding::Definite(enc)) 67 | .unwrap_or(crate::serialization::StringEncoding::Canonical); 68 | let uint_string_encoding = text_encoding.to_str_len_sz(uint_string.len() as u64); 69 | serializer 70 | .write_tag_sz(9, fit_sz(9, tag_encoding))? 71 | .write_text_sz(uint_string, uint_string_encoding) 72 | } 73 | 74 | pub fn read_tagged_uint_str( 75 | raw: &mut cbor_event::de::Deserializer, 76 | ) -> Result<(u64, Option, Option), DeserializeError> { 77 | use std::str::FromStr; 78 | let (tag, tag_encoding) = raw.tag_sz()?; 79 | let (text, text_encoding) = raw.text_sz()?; 80 | match text_encoding { 81 | cbor_event::StringLenSz::Indefinite(_) => Err(DeserializeFailure::CBOR(cbor_event::Error::CustomError(format!("We only support definite encodings in order to use the uint one"))).into()), 82 | cbor_event::StringLenSz::Len(text_encoding_sz) => u64::from_str(&text) 83 | .map(|uint| (uint, Some(tag_encoding), Some(text_encoding_sz))) 84 | .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()), 85 | } 86 | } -------------------------------------------------------------------------------- /example/serialization_unit_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use super::*; 4 | use serialization::*; 5 | 6 | fn print_cbor_types(obj_name: &str, vec: &Vec) { 7 | use cbor_event::Type; 8 | let mut raw = Deserializer::from(std::io::Cursor::new(vec)); 9 | println!("{} = {{", obj_name); 10 | loop { 11 | match raw.cbor_type() { 12 | Err(_) => break, 13 | Ok(Type::UnsignedInteger) => println!("UINT({})", raw.unsigned_integer().unwrap()), 14 | Ok(Type::NegativeInteger) => println!("NINT({})", raw.negative_integer().unwrap()), 15 | Ok(Type::Bytes) => println!("BYTES({:?})", raw.bytes().unwrap()), 16 | Ok(Type::Text) => println!("TEXT({})", raw.text().unwrap()), 17 | Ok(Type::Array) => println!("ARRAY({:?})", raw.array().unwrap()), 18 | Ok(Type::Map) => println!("MAP({:?})", raw.map().unwrap()), 19 | Ok(Type::Tag) => println!("TAG({})", raw.tag().unwrap()), 20 | Ok(Type::Special) => println!("SPECIAL({:?})", raw.special().unwrap()), 21 | } 22 | } 23 | println!("}}"); 24 | } 25 | 26 | fn deser_test(orig: T) { 27 | print_cbor_types("orig", &orig.to_bytes()); 28 | let deser = T::deserialize(&mut Deserializer::from(std::io::Cursor::new(orig.to_bytes()))).unwrap(); 29 | print_cbor_types("deser", &deser.to_bytes()); 30 | assert_eq!(orig.to_bytes(), deser.to_bytes()); 31 | } 32 | 33 | #[test] 34 | fn foo() { 35 | deser_test(Foo::new(436, String::from("jfkdsjfd"), vec![1, 1, 1])); 36 | } 37 | 38 | #[test] 39 | fn foo2_some() { 40 | deser_test(Foo2::new(143546, Some(TaggedText::new(String::from("afdjfkjsiefefe"))))); 41 | } 42 | 43 | #[test] 44 | fn foo2_none() { 45 | deser_test(Foo2::new(143546, None)); 46 | } 47 | 48 | #[test] 49 | fn bar() { 50 | deser_test(Bar::new(&Foo::new(436, String::from("jfkdf"), vec![6, 4]), None)); 51 | } 52 | 53 | #[test] 54 | fn plain() { 55 | deser_test(Plain::new(7576, &TaggedText::new(String::from("wiorurri34h")))); 56 | } 57 | 58 | #[test] 59 | fn outer() { 60 | deser_test(Outer::new(2143254, &Plain::new(7576, &TaggedText::new(String::from("wiorurri34h"))))); 61 | } 62 | 63 | #[test] 64 | fn table() { 65 | let mut orig = Table::new(); 66 | orig.insert(8, String::from("Eight")); 67 | orig.insert(16, String::from("Sixteen")); 68 | orig.insert(32, String::from("Thirty Two")); 69 | deser_test(orig); 70 | } 71 | 72 | #[test] 73 | fn table_arr_members() { 74 | let mut tab = Mapu64ToString::new(); 75 | tab.insert(43266556, String::from("2k2j343")); 76 | tab.insert(213543254546565, String::from("!!fjdj")); 77 | let mut foos = Foos::new(); 78 | foos.add(&Foo::new(0, String::from("Zero"), vec![])); 79 | foos.add(&Foo::new(2, String::from("Two"), vec![2, 2])); 80 | deser_test(TableArrMembers::new(&tab, vec![0, 1, 2, 3, 4, 5], &foos)); 81 | } 82 | 83 | #[test] 84 | fn type_choice_0() { 85 | deser_test(TypeChoice::new_i0()); 86 | } 87 | 88 | #[test] 89 | fn type_choice_hello_world() { 90 | deser_test(TypeChoice::new_helloworld()); 91 | } 92 | 93 | #[test] 94 | fn type_choice_uint() { 95 | deser_test(TypeChoice::new_u64(53435364)); 96 | } 97 | 98 | #[test] 99 | fn type_choice_text() { 100 | deser_test(TypeChoice::new_text(String::from("jdfidsf83j3 jkrjefdfk !!"))); 101 | } 102 | 103 | #[test] 104 | fn type_choice_bytes() { 105 | deser_test(TypeChoice::new_bytes(vec![0x00, 0x01, 0xF7, 0xFF])); 106 | } 107 | 108 | #[test] 109 | fn type_choice_tagged_arr() { 110 | deser_test(TypeChoice::new_arr_u64(vec![1, 2, 3, 4])); 111 | } 112 | 113 | #[test] 114 | fn group_choice_foo() { 115 | deser_test(GroupChoice::new_foo(&Foo::new(0, String::new(), vec![]))); 116 | } 117 | 118 | #[test] 119 | fn group_choice_0() { 120 | deser_test(GroupChoice::new_group_choice1(37)); 121 | } 122 | 123 | #[test] 124 | fn group_choice_plain() { 125 | deser_test(GroupChoice::new_plain(&Plain::new(354545, &TaggedText::new(String::from("fdsfdsfdg"))))); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /example/supported.cddl: -------------------------------------------------------------------------------- 1 | ; this is a minimal example that tries to show all supported features 2 | 3 | ; pay attention to the @name comment placement as it can be finicky 4 | 5 | ; type alias 6 | hash = bytes 7 | ; create a newtype around another type instead of an alias 8 | special_hash = bytes ; @newtype 9 | ; or don't generate either and directly use the aliased type instead 10 | hidden_hash = bytes ; @no_alias 11 | 12 | hashes = [ 13 | hash, 14 | special_hash, 15 | hidden_hash, 16 | ] 17 | 18 | ; size/length requirements on primitives 19 | limitations = [ 20 | ; integer restrictions that map to rust types are directly translated 21 | ; u8 in rust 22 | u_8: uint .size 1, 23 | ; u16 in rust 24 | u_16: uint .le 65535, 25 | ; u32, etc... 26 | u_32: 0..4294967295, 27 | u_64: uint .size 8, 28 | i_8: -128..127, 29 | i_64: int .size 8, 30 | ; you can also limit strings (text or bytes) to a specific length 31 | hash32: bytes .size 32, 32 | ; or to a range e.g. between 10 and 20 bytes 33 | bounded: text .size (10..20), 34 | ] 35 | 36 | ; array struct 37 | foo = [ 38 | ; all primitives are supported: 39 | ; e.g. uint, nint and int supported. int generates special code as no rust equivalent 40 | ; unnamed array fields try to derive name from type if possible 41 | int, 42 | ; text / bytes supported too 43 | ; or you can give them an explicit name like this 44 | name: text, 45 | ; as well as floats (without --preserve-encodings=true) 46 | fp: float64, 47 | ] 48 | 49 | ; mark as externally defined. user has to insert/import code for this type after generation 50 | ; so this file as a whole won't compile until you provide an ExternFoo that implements Serialize/Deserialize 51 | extern_foo = _CDDL_CODEGEN_EXTERN_TYPE_ 52 | 53 | ; map struct + tagged fields + .cbor + optional fields + constants + .default 54 | bar = { 55 | ; fields can be tagged and this remains a serialization detail (hidden from API) 56 | foo: #6.1337(foo), 57 | ; they can also be encoded as CBOR bytes which remains a serialization detail (hidden from API) 58 | ; this can be combined with tags as well i.e. #6.42(bytes .cbor extern_foo) 59 | extern_foo: bytes .cbor extern_foo 60 | ; optional field (generates as Option) 61 | ? derp: uint, 62 | ; type choice with null will result in Option too for the API 63 | ; also, you can give explicit names that differ from the key value for maps like this: 64 | 1 : uint / null, ; @name explicitly_named_1 65 | ; string constant (no field generated) 66 | 5: "five", 67 | ; integer constant (no field generated) 68 | five: 5, 69 | ; this will not be an optional field in rust, as when it is not present, it will be set to 0 70 | ? 100: uint .default 0, 71 | } 72 | 73 | ; basic groups are supported and have their own type 74 | basic = ( 75 | b: #6.23(uint), 76 | c: text, 77 | ) 78 | 79 | ; basic groups fully supported in array groups 80 | outer = [ 81 | a: uint, 82 | ; basic groups can be put into an array struct directly i.e. embed their fields into outer 83 | ; which is only a serialization detail. this field will be of type Basic 84 | embedded: basic, 85 | ; or you can embed them into a repeatable homogeneous array 86 | homogeneous_array: [* basic], 87 | ] 88 | 89 | other_basic = ( 90 | b: uint, 91 | c: uint, 92 | ) 93 | 94 | ; you can embed basic groups in maps, BUT deserialization will not be generated due to technical limitations 95 | ; LIMITATION: a single basic group cannot be put into both a map and an array group for serialization which is 96 | ; why we had to define a separate one other_basic instead of just using basic 97 | outer_map = { 98 | a: uint, 99 | embedded: other_basic, 100 | } 101 | 102 | table_arr_members = { 103 | ; you can directly define homogeneous maps as fields (or define them at top-level) 104 | tab: { * text => text }, 105 | ; you can also define homogenous arrays as fields (or define them at top-level) 106 | arr: [*uint], 107 | } 108 | 109 | type_choice = 110 | 0 ; @name you 111 | / "hello world" ; @name can 112 | / uint ; @name name 113 | / text ; @name variants 114 | / bytes ; @name like 115 | / #6.64([*uint]) ; @name this 116 | / foo ; otherwise they try and take the variant name from the type e.g. Foo here 117 | 118 | ; if a type choice only has constants it will generate as a c-style enum (directly wasm-exposable) 119 | c_style_enum = 120 | 0 ; @name foo 121 | / 1 ; @name bar 122 | / 2 ; @name baz 123 | 124 | group_choice = [ 125 | ; @name these 126 | foo // 127 | ; if there is only one non-constant field in the inlined group then that will be inlined in the enum 128 | ; @name are 129 | 0, x: uint // 130 | ; but if there are multiple then a new struct will be generated from this variant 131 | ; @name also 132 | 1, x: uint, y: text // 133 | ; basic groups can be embedded into group choices, taking on the format of the outer group 134 | ; @name nameable 135 | basic 136 | ] 137 | 138 | choices = [ 139 | type_choice, 140 | c_style_enum, 141 | group_choice, 142 | ] -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | // TODO: make non-annotation generate different DeserializeError that is simpler 3 | // and works with From only 4 | 5 | #[derive(Debug, Default, Parser)] 6 | #[clap()] 7 | pub struct Cli { 8 | /// Input .cddl file to generate from. If this is a directory then it will read all *.cddl files and generate one output for each. 9 | #[clap(short, long, value_parser, value_name = "INPUT_FILE/INPUT_DIR")] 10 | pub input: std::path::PathBuf, 11 | 12 | /// Output directory for the generated code. 13 | #[clap(short, long, value_parser, value_name = "OUTPUT_DIR")] 14 | pub output: std::path::PathBuf, 15 | 16 | /// Change the directory of the static files 17 | #[clap(short, long, value_parser, value_name = "STATIC_DIR", default_value_os_t = std::path::PathBuf::from("static"))] 18 | pub static_dir: std::path::PathBuf, 19 | 20 | /// Name to use for exported library. 21 | /// Will be used directly for rust lib and will have -wasm appended for the wasm bindings. 22 | /// This will appear EXACTLY as-is in the Cargo.toml's. use Cli::lib_name_code() for use in rust code 23 | #[clap( 24 | long, 25 | value_parser, 26 | value_name = "EXPORT_LIB_NAME", 27 | default_value = "cddl-lib" 28 | )] 29 | pub lib_name: String, 30 | 31 | /// Include additional information about where deserialization errors are encountered. This will slightly increase code size. 32 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = true)] 33 | pub annotate_fields: bool, 34 | 35 | /// Generate to_bytes() / from_bytes() methods on all types 36 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = true)] 37 | pub to_from_bytes_methods: bool, 38 | 39 | /// Generate byte string definitions as new rust types (TODO: look into this or remove it) 40 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 41 | pub binary_wrappers: bool, 42 | 43 | /// Preserves CBOR encoding upon deserialization e.g. definite vs indefinite, map ordering 44 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 45 | pub preserve_encodings: bool, 46 | 47 | /// Allows serialization to canonical CBOR. if preserve-encodings is enabled, this will be as a toggle on serialization functions 48 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 49 | pub canonical_form: bool, 50 | 51 | /// Generates a wasm_bindgen crate for wasm bindings 52 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = true)] 53 | pub wasm: bool, 54 | 55 | /// Derives serde::Serialize/serde::Deserialize for types to allow to/from JSON 56 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 57 | pub json_serde_derives: bool, 58 | 59 | /// Tags types with sonSchema derives and generates a crate to export them 60 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 61 | pub json_schema_export: bool, 62 | 63 | /// Generates a npm package.json along with build scripts 64 | #[clap(long, value_parser, action = clap::ArgAction::Set, default_value_t = false)] 65 | pub package_json: bool, 66 | 67 | /// Location override for default common types (error, serialization, etc) 68 | /// This is useful for integrating into an exisitng project that is based on 69 | /// these types. 70 | #[clap(long, value_parser, value_name = "COMMON_IMPORT_OVERRIDE")] 71 | pub common_import_override: Option, 72 | 73 | /// An external macro to be called instead of manually emitting functions for 74 | /// conversions to/from CBOR bytes or JSON. 75 | /// If the macro is scoped it will be imported using the supplied path. 76 | /// e.g. foo::bar::qux will result in importing foo::bar::qux and then 77 | /// calling qux!(A); for every struct A with a CBOR/JSON API 78 | #[clap(long, value_parser)] 79 | pub wasm_cbor_json_api_macro: Option, 80 | 81 | /// An external macro to be called instead of manually emitting traits for 82 | /// WASM conversions to/from the inner rust type + AsRef. 83 | /// If the macro is scoped it will be imported using the supplied path. 84 | /// e.g. foo::bar::qux will result in importing foo::bar::qux and then 85 | /// calling qux!(rust::path::A, A); for every struct A with a CBOR/JSON API 86 | #[clap(long, value_parser)] 87 | pub wasm_conversions_macro: Option, 88 | } 89 | 90 | impl Cli { 91 | /// lib name from code i.e. with underscores 92 | pub fn lib_name_code(&self) -> String { 93 | self.lib_name.replace('-', "_") 94 | } 95 | 96 | /// If someone override the common imports, we don't want to export them 97 | pub fn export_static_files(&self) -> bool { 98 | self.common_import_override.is_none() 99 | } 100 | 101 | pub fn common_import_rust(&self) -> &str { 102 | self.common_import_override.as_deref().unwrap_or("crate") 103 | } 104 | 105 | pub fn common_import_wasm(&self) -> String { 106 | self.common_import_override 107 | .clone() 108 | .unwrap_or_else(|| self.lib_name_code()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/preserve-encodings/input.cddl: -------------------------------------------------------------------------------- 1 | foo = #6.11([uint, text, bytes]) ; @used_as_key 2 | 3 | bar = { 4 | foo: #6.13(foo), 5 | ? derp: uint, 6 | 1 : uint / null, 7 | ? 5: text, 8 | five: 5, 9 | } 10 | 11 | table = { * uint => text } 12 | 13 | u32 = uint .size 4 ; 4 bytes 14 | 15 | table_arr_members = { 16 | arr: [*u32], 17 | arr2: [*foo], 18 | table: table, 19 | } 20 | 21 | data = { * #6.14(bytes) => { * uint => { * #6.9(uint) => [* #6.18([* text]) ] } } } 22 | 23 | deeply_nested = [ data: data ] 24 | 25 | string64 = text .size (0..64) 26 | 27 | tagged_text = #6.9(text) 28 | 29 | string_16_32 = #6.7(text .size (16..32)) 30 | 31 | type_choice = 0 / "hello world" / uint / text / #6.16([*uint]) 32 | 33 | non_overlapping_type_choice_all = uint / nint / text / bytes / #6.13("hello world") / [* uint] / { *text => uint } 34 | 35 | non_overlapping_type_choice_some = uint / nint / text ; @used_as_key 36 | 37 | overlap_basic_embed = [ 38 | ; @name identity 39 | tag: 0 // 40 | ; @name x 41 | tag: 1, hash: bytes .size 32 42 | ] 43 | 44 | non_overlap_basic_embed = [ 45 | ; @name first 46 | x: uint, tag: 0 // 47 | ; @name second 48 | y: text, tag: 1 49 | ] 50 | 51 | non_overlap_basic_embed_multi_fields = [ 52 | ; @name first 53 | x: uint, z: uint // 54 | ; @name second 55 | y: text, z: uint 56 | ] 57 | 58 | non_overlap_basic_embed_mixed = [ 59 | ; @name first 60 | x: uint, tag: 0 // 61 | ; @name second 62 | y: text, z: uint 63 | ] 64 | 65 | bytes_uint = (bytes, uint) 66 | 67 | non_overlap_basic_embed_mixed_explicit = [ 68 | ; @name first 69 | x: uint, tag: 0 // 70 | ; @name second 71 | y: text, z: uint // 72 | ; @name third 73 | bytes_uint 74 | ] 75 | 76 | basic = (uint, text) 77 | 78 | basic_arr = [basic] 79 | 80 | ; not overlap since double array for second 81 | non_overlap_basic_not_basic = [ 82 | ; @name group 83 | basic // 84 | ; @name group_arr 85 | basic_arr // 86 | ; @name group_tagged 87 | #6.11(basic) // 88 | ; @name group_bytes 89 | bytes .cbor basic_arr 90 | ] 91 | 92 | c_enum = 3 / 1 / 4 93 | 94 | enums = [ 95 | c_enum, 96 | type_choice, 97 | ] 98 | 99 | plain = (d: #6.13(uint), e: tagged_text) 100 | 101 | plain_arrays = [ 102 | embedded: plain, 103 | single: [plain], 104 | multi: [*plain], 105 | ] 106 | 107 | group_choice = [ 3 // #6.10(2) // foo // 0, x: uint // plain ] 108 | 109 | foo_bytes = bytes .cbor foo 110 | 111 | ; since we don't generate code for definitions like the above (should we if no one refers to it?) 112 | cbor_in_cbor = [foo_bytes, uint_bytes: bytes .cbor uint, tagged_foo_bytes] 113 | 114 | tagged_foo_bytes = #6.20(bytes .cbor foo) 115 | 116 | signed_ints = [ 117 | u_8: uint .size 1, 118 | u_16: uint .size 2, 119 | u_32: uint .size 4, 120 | u_64: uint .size 8, 121 | i_8: int .size 1, 122 | i_16: int .size 2, 123 | i_32: int .size 4, 124 | i_64: int .size 8, 125 | n_64: nint 126 | u64_max: 18446744073709551615, 127 | ; this test assumes i64::BITS == isize::BITS (i.e. 64-bit programs) or else the cddl parsing lib would mess up 128 | ; if this test fails on your platform we might need to either remove this part 129 | ; or make a fix for the cddl library. 130 | ; The fix would be ideal as even though the true min in CBOR would be -u64::MAX 131 | ; we can't test that since isize::BITS is never > 64 in any normal system and likely never will be 132 | i64_min: -9223372036854775808 133 | ] 134 | 135 | default_uint = uint .default 1337 136 | 137 | map_with_defaults = { 138 | ? 1 : default_uint 139 | ? 2 : text .default "two" 140 | } 141 | 142 | ; TODO: preserve-encodings remembering optional fixed values. Issue: https://github.com/dcSpark/cddl-codegen/issues/205 143 | array_opt_fields = [ 144 | ; ? x: null, 145 | ? a: uint, 146 | ? b: text, 147 | c: nint, 148 | ? d: text, 149 | y: #6.10(1), 150 | ? e: non_overlapping_type_choice_some 151 | ; ? z: null, 152 | ] 153 | 154 | hash = bytes .size (0..8) 155 | 156 | bounds = [ 157 | w: -1000 .. 1000 158 | x: uint .le 7, 159 | y: nint .ge -5, 160 | z: text .size (3..14), 161 | a: [* uint] .size (1..3), 162 | b: { * uint => uint } .le 3 163 | ] 164 | 165 | bounds_type_choice = bytes .size (0..64) 166 | / text .size (0..64) 167 | 168 | bounds_group_choice = [ 169 | ; @name a 170 | a: uint, b: text .le 4 // 171 | ; @name b 172 | hash // 173 | ; @name c 174 | 1, x: hash, y: hash 175 | ] 176 | 177 | overlapping_inlined = [ 178 | ; @name one 179 | 0 // 180 | ; @name two 181 | 0, uint // 182 | ; @name three 183 | 0, uint, text 184 | ] 185 | 186 | enum_opt_embed_fields = [ 187 | ; @name ea 188 | 1 // 189 | ; @name eb 190 | 1, ?text, 5 // 191 | ; @name ec 192 | 1, uint, 7 // 193 | ; doesn't parse but would result in triple nesting so worth testing if we can ever parse it 194 | ; 1, ? (text / null), #6.9(9) 195 | ; @name ed 196 | 1, uint, ?text // 197 | ; @name ee 198 | 1, uint, ?bytes, uint // 199 | ; @name ef 200 | 1, ? non_overlapping_type_choice_some, #6.11(11) // 201 | ; @name eg 202 | 1, ? overlapping_inlined, #6.13(13) 203 | ] 204 | 205 | custom_bytes = bytes ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 206 | 207 | struct_with_custom_serialization = [ 208 | custom_bytes, 209 | field: bytes, ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 210 | overridden: custom_bytes, ; @custom_serialize write_hex_string @custom_deserialize read_hex_string 211 | tagged1: #6.9(custom_bytes), 212 | tagged2: #6.9(uint), ; @custom_serialize write_tagged_uint_str @custom_deserialize read_tagged_uint_str 213 | ] 214 | 215 | wrapper_table = { * uint => uint } ; @newtype 216 | wrapper_list = [ * uint ] ; @newtype 217 | -------------------------------------------------------------------------------- /src/dep_graph.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{BTreeMap, BTreeSet}; 2 | 3 | use cddl::ast::*; 4 | 5 | pub fn topological_rule_order<'a>(rules: &'a [&'a Rule<'a>]) -> Vec<&'a Rule<'a>> { 6 | let mut adj_list = BTreeMap::new(); 7 | for cddl_rule in rules.iter() { 8 | let (ident, refs) = find_references(cddl_rule); 9 | adj_list.insert(ident.ident, (*cddl_rule, refs)); 10 | } 11 | let mut unvisited = adj_list.keys().copied().collect::>(); 12 | let mut topo_order = Vec::new(); 13 | let mut processing: BTreeSet<&'a str> = BTreeSet::new(); 14 | while let Some(u) = unvisited.iter().next().copied() { 15 | dfs_visit( 16 | &mut topo_order, 17 | &mut unvisited, 18 | &mut processing, 19 | &adj_list, 20 | u, 21 | ); 22 | } 23 | topo_order 24 | } 25 | 26 | fn dfs_visit<'a>( 27 | topo_order: &mut Vec<&'a Rule<'a>>, 28 | unvisited: &mut BTreeSet<&'a str>, 29 | processing: &mut BTreeSet<&'a str>, 30 | adj_list: &BTreeMap<&'a str, (&'a cddl::ast::Rule<'a>, Vec<&'a Identifier<'a>>)>, 31 | u: &'a str, 32 | ) { 33 | processing.insert(u); 34 | let (rule, neighbors) = adj_list.get(u).unwrap(); 35 | for v in neighbors.iter() { 36 | if processing.contains(v.ident) { 37 | eprintln!("Recursive type: '{u}' / '{v}' - code will possibly need to be edited by hand to use Box/etc"); 38 | continue; 39 | } 40 | if unvisited.contains(v.ident) { 41 | dfs_visit(topo_order, unvisited, processing, adj_list, v.ident); 42 | } 43 | } 44 | processing.remove(u); 45 | unvisited.remove(u); 46 | topo_order.push(rule); 47 | } 48 | 49 | fn find_references<'a>(cddl_rule: &'a Rule<'a>) -> (&'a Identifier, Vec<&'a Identifier<'a>>) { 50 | let mut refs = Vec::new(); 51 | let ident = match cddl_rule { 52 | Rule::Type { rule, .. } => { 53 | rule.value 54 | .type_choices 55 | .iter() 56 | .for_each(|tc| find_refs_type1(&mut refs, &tc.type1)); 57 | &rule.name 58 | } 59 | Rule::Group { rule, .. } => { 60 | assert_eq!( 61 | rule.generic_params, None, 62 | "{}: Generics not supported on plain groups", 63 | rule.name 64 | ); 65 | match &rule.entry { 66 | cddl::ast::GroupEntry::InlineGroup { group, .. } => { 67 | find_refs_group(&mut refs, group) 68 | } 69 | x => panic!("Group rule with non-inline group? {:?}", x), 70 | } 71 | &rule.name 72 | } 73 | }; 74 | (ident, refs) 75 | } 76 | 77 | fn find_refs_type1<'a>(refs: &mut Vec<&'a Identifier<'a>>, type1: &'a Type1<'a>) { 78 | match &type1.type2 { 79 | Type2::Typename { 80 | ident, 81 | generic_args, 82 | .. 83 | } => { 84 | refs.push(ident); 85 | find_refs_generic_args(refs, generic_args); 86 | } 87 | Type2::ParenthesizedType { pt, .. } => pt 88 | .type_choices 89 | .iter() 90 | .for_each(|tc| find_refs_type1(refs, &tc.type1)), 91 | Type2::Map { group, .. } => find_refs_group(refs, group), 92 | Type2::Array { group, .. } => find_refs_group(refs, group), 93 | Type2::Unwrap { 94 | ident, 95 | generic_args, 96 | .. 97 | } => { 98 | refs.push(ident); 99 | find_refs_generic_args(refs, generic_args); 100 | } 101 | Type2::ChoiceFromInlineGroup { group, .. } => find_refs_group(refs, group), 102 | Type2::ChoiceFromGroup { 103 | ident, 104 | generic_args, 105 | .. 106 | } => { 107 | refs.push(ident); 108 | find_refs_generic_args(refs, generic_args); 109 | } 110 | Type2::TaggedData { t, .. } => t 111 | .type_choices 112 | .iter() 113 | .for_each(|tc| find_refs_type1(refs, &tc.type1)), 114 | _ => (), 115 | } 116 | } 117 | 118 | fn find_refs_group<'a>(refs: &mut Vec<&'a Identifier<'a>>, group: &'a Group<'a>) { 119 | for group_choice in group.group_choices.iter() { 120 | for (group_entry, _) in group_choice.group_entries.iter() { 121 | match group_entry { 122 | GroupEntry::InlineGroup { group, .. } => find_refs_group(refs, group), 123 | GroupEntry::TypeGroupname { ge, .. } => { 124 | refs.push(&ge.name); 125 | find_refs_generic_args(refs, &ge.generic_args); 126 | } 127 | GroupEntry::ValueMemberKey { ge, .. } => { 128 | ge.entry_type 129 | .type_choices 130 | .iter() 131 | .for_each(|tc| find_refs_type1(refs, &tc.type1)); 132 | match &ge.member_key { 133 | Some(MemberKey::Type1 { t1, .. }) => find_refs_type1(refs, t1), 134 | Some(MemberKey::NonMemberKey { .. }) => { 135 | unimplemented!("Please open a github issue with repro steps") 136 | } 137 | _ => (), 138 | } 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | fn find_refs_generic_args<'a>( 146 | refs: &mut Vec<&'a Identifier<'a>>, 147 | generic_arg: &'a Option>, 148 | ) { 149 | if let Some(arg) = generic_arg { 150 | arg.args 151 | .iter() 152 | .for_each(|arg| find_refs_type1(refs, arg.arg.as_ref())); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /static/error.rs: -------------------------------------------------------------------------------- 1 | use cbor_event::{self, de::Deserializer}; 2 | use std::io::{BufRead, Seek}; 3 | 4 | #[derive(Debug)] 5 | pub enum Key { 6 | Str(String), 7 | Uint(u64), 8 | Float(f64), 9 | } 10 | 11 | impl std::fmt::Display for Key { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | Key::Str(x) => write!(f, "\"{}\"", x), 15 | Key::Uint(x) => write!(f, "{}", x), 16 | Key::Float(x) => write!(f, "{}", x), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum DeserializeFailure { 23 | BreakInDefiniteLen, 24 | CBOR(cbor_event::Error), 25 | DefiniteLenMismatch(u64, Option), 26 | DuplicateKey(Key), 27 | EndingBreakMissing, 28 | ExpectedNull, 29 | FixedValueMismatch{ 30 | found: Key, 31 | expected: Key, 32 | }, 33 | /// Invalid internal structure imposed on top of the CBOR format 34 | InvalidStructure(Box), 35 | MandatoryFieldMissing(Key), 36 | NoVariantMatched, 37 | NoVariantMatchedWithCauses(Vec), 38 | RangeCheck{ 39 | found: isize, 40 | min: Option, 41 | max: Option, 42 | }, 43 | TagMismatch{ 44 | found: u64, 45 | expected: u64, 46 | }, 47 | UnknownKey(Key), 48 | UnexpectedKeyType(cbor_event::Type), 49 | } 50 | 51 | // we might want to add more info like which field, 52 | #[derive(Debug)] 53 | pub struct DeserializeError { 54 | location: Option, 55 | failure: DeserializeFailure, 56 | } 57 | 58 | impl DeserializeError { 59 | pub fn new>(location: T, failure: DeserializeFailure) -> Self { 60 | Self { 61 | location: Some(location.into()), 62 | failure, 63 | } 64 | } 65 | 66 | pub fn annotate>(self, location: T) -> Self { 67 | match self.location { 68 | Some(loc) => Self::new(format!("{}.{}", location.into(), loc), self.failure), 69 | None => Self::new(location, self.failure), 70 | } 71 | } 72 | 73 | fn fmt_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: u32) -> std::fmt::Result { 74 | use std::fmt::Display; 75 | for _ in 0..indent { 76 | write!(f, "\t")?; 77 | } 78 | match &self.location { 79 | Some(loc) => write!(f, "Deserialization failed in {} because: ", loc), 80 | None => write!(f, "Deserialization: "), 81 | }?; 82 | match &self.failure { 83 | DeserializeFailure::BreakInDefiniteLen => write!(f, "Encountered CBOR Break while reading definite length sequence"), 84 | DeserializeFailure::CBOR(e) => e.fmt(f), 85 | DeserializeFailure::DefiniteLenMismatch(found, expected) => { 86 | write!(f, "Definite length mismatch: found {}", found)?; 87 | if let Some(expected_elems) = expected { 88 | write!(f, ", expected: {}", expected_elems)?; 89 | } 90 | Ok(()) 91 | }, 92 | DeserializeFailure::DuplicateKey(key) => write!(f, "Duplicate key: {}", key), 93 | DeserializeFailure::EndingBreakMissing => write!(f, "Missing ending CBOR Break"), 94 | DeserializeFailure::ExpectedNull => write!(f, "Expected null, found other type"), 95 | DeserializeFailure::FixedValueMismatch{ found, expected } => write!(f, "Expected fixed value {} found {}", expected, found), 96 | DeserializeFailure::InvalidStructure(e) => { 97 | write!(f, "Invalid internal structure: {}", e) 98 | } 99 | DeserializeFailure::MandatoryFieldMissing(key) => write!(f, "Mandatory field {} not found", key), 100 | DeserializeFailure::NoVariantMatched => write!(f, "No variant matched"), 101 | DeserializeFailure::NoVariantMatchedWithCauses(errs) => { 102 | write!(f, "No variant matched. Failures:\n")?; 103 | for e in errs { 104 | e.fmt_indent(f, indent + 1)?; 105 | write!(f, "\n")?; 106 | } 107 | Ok(()) 108 | }, 109 | DeserializeFailure::RangeCheck{ found, min, max } => match (min, max) { 110 | (Some(min), Some(max)) => write!(f, "{} not in range {} - {}", found, min, max), 111 | (Some(min), None) => write!(f, "{} not at least {}", found, min), 112 | (None, Some(max)) => write!(f, "{} not at most {}", found, max), 113 | (None, None) => write!(f, "invalid range (no min nor max specified)"), 114 | }, 115 | DeserializeFailure::TagMismatch{ found, expected } => write!(f, "Expected tag {}, found {}", expected, found), 116 | DeserializeFailure::UnknownKey(key) => write!(f, "Found unexpected key {}", key), 117 | DeserializeFailure::UnexpectedKeyType(ty) => write!(f, "Found unexpected key of CBOR type {:?}", ty), 118 | } 119 | } 120 | } 121 | 122 | impl std::error::Error for DeserializeError {} 123 | 124 | impl std::fmt::Display for DeserializeError { 125 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 126 | self.fmt_indent(f, 0) 127 | } 128 | } 129 | 130 | impl From for DeserializeError { 131 | fn from(failure: DeserializeFailure) -> DeserializeError { 132 | DeserializeError { 133 | location: None, 134 | failure, 135 | } 136 | } 137 | } 138 | 139 | impl From for DeserializeError { 140 | fn from(err: cbor_event::Error) -> DeserializeError { 141 | DeserializeError { 142 | location: None, 143 | failure: DeserializeFailure::CBOR(err), 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/error.rs: -------------------------------------------------------------------------------- 1 | use cbor_event::{self, de::Deserializer}; 2 | use std::io::{BufRead, Seek}; 3 | 4 | #[derive(Debug)] 5 | pub enum Key { 6 | Str(String), 7 | Uint(u64), 8 | Float(f64), 9 | } 10 | 11 | impl std::fmt::Display for Key { 12 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | Key::Str(x) => write!(f, "\"{}\"", x), 15 | Key::Uint(x) => write!(f, "{}", x), 16 | Key::Float(x) => write!(f, "{}", x), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub enum DeserializeFailure { 23 | BreakInDefiniteLen, 24 | CBOR(cbor_event::Error), 25 | DefiniteLenMismatch(u64, Option), 26 | DuplicateKey(Key), 27 | EndingBreakMissing, 28 | ExpectedNull, 29 | FixedValueMismatch{ 30 | found: Key, 31 | expected: Key, 32 | }, 33 | /// Invalid internal structure imposed on top of the CBOR format 34 | InvalidStructure(Box), 35 | MandatoryFieldMissing(Key), 36 | NoVariantMatched, 37 | NoVariantMatchedWithCauses(Vec), 38 | RangeCheck{ 39 | found: isize, 40 | min: Option, 41 | max: Option, 42 | }, 43 | TagMismatch{ 44 | found: u64, 45 | expected: u64, 46 | }, 47 | UnknownKey(Key), 48 | UnexpectedKeyType(cbor_event::Type), 49 | } 50 | 51 | // we might want to add more info like which field, 52 | #[derive(Debug)] 53 | pub struct DeserializeError { 54 | location: Option, 55 | failure: DeserializeFailure, 56 | } 57 | 58 | impl DeserializeError { 59 | pub fn new>(location: T, failure: DeserializeFailure) -> Self { 60 | Self { 61 | location: Some(location.into()), 62 | failure, 63 | } 64 | } 65 | 66 | pub fn annotate>(self, location: T) -> Self { 67 | match self.location { 68 | Some(loc) => Self::new(format!("{}.{}", location.into(), loc), self.failure), 69 | None => Self::new(location, self.failure), 70 | } 71 | } 72 | 73 | fn fmt_indent(&self, f: &mut std::fmt::Formatter<'_>, indent: u32) -> std::fmt::Result { 74 | use std::fmt::Display; 75 | for _ in 0..indent { 76 | write!(f, "\t")?; 77 | } 78 | match &self.location { 79 | Some(loc) => write!(f, "Deserialization failed in {} because: ", loc), 80 | None => write!(f, "Deserialization: "), 81 | }?; 82 | match &self.failure { 83 | DeserializeFailure::BreakInDefiniteLen => write!(f, "Encountered CBOR Break while reading definite length sequence"), 84 | DeserializeFailure::CBOR(e) => e.fmt(f), 85 | DeserializeFailure::DefiniteLenMismatch(found, expected) => { 86 | write!(f, "Definite length mismatch: found {}", found)?; 87 | if let Some(expected_elems) = expected { 88 | write!(f, ", expected: {}", expected_elems)?; 89 | } 90 | Ok(()) 91 | }, 92 | DeserializeFailure::DuplicateKey(key) => write!(f, "Duplicate key: {}", key), 93 | DeserializeFailure::EndingBreakMissing => write!(f, "Missing ending CBOR Break"), 94 | DeserializeFailure::ExpectedNull => write!(f, "Expected null, found other type"), 95 | DeserializeFailure::FixedValueMismatch{ found, expected } => write!(f, "Expected fixed value {} found {}", expected, found), 96 | DeserializeFailure::InvalidStructure(e) => { 97 | write!(f, "Invalid internal structure: {}", e) 98 | } 99 | DeserializeFailure::MandatoryFieldMissing(key) => write!(f, "Mandatory field {} not found", key), 100 | DeserializeFailure::NoVariantMatched => write!(f, "No variant matched"), 101 | DeserializeFailure::NoVariantMatchedWithCauses(errs) => { 102 | write!(f, "No variant matched. Failures:\n")?; 103 | for e in errs { 104 | e.fmt_indent(f, indent + 1)?; 105 | write!(f, "\n")?; 106 | } 107 | Ok(()) 108 | }, 109 | DeserializeFailure::RangeCheck{ found, min, max } => match (min, max) { 110 | (Some(min), Some(max)) => write!(f, "{} not in range {} - {}", found, min, max), 111 | (Some(min), None) => write!(f, "{} not at least {}", found, min), 112 | (None, Some(max)) => write!(f, "{} not at most {}", found, max), 113 | (None, None) => write!(f, "invalid range (no min nor max specified)"), 114 | }, 115 | DeserializeFailure::TagMismatch{ found, expected } => write!(f, "Expected tag {}, found {}", expected, found), 116 | DeserializeFailure::UnknownKey(key) => write!(f, "Found unexpected key {}", key), 117 | DeserializeFailure::UnexpectedKeyType(ty) => write!(f, "Found unexpected key of CBOR type {:?}", ty), 118 | } 119 | } 120 | } 121 | 122 | impl std::error::Error for DeserializeError {} 123 | 124 | impl std::fmt::Display for DeserializeError { 125 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 126 | self.fmt_indent(f, 0) 127 | } 128 | } 129 | 130 | impl From for DeserializeError { 131 | fn from(failure: DeserializeFailure) -> DeserializeError { 132 | DeserializeError { 133 | location: None, 134 | failure, 135 | } 136 | } 137 | } 138 | 139 | impl From for DeserializeError { 140 | fn from(err: cbor_event::Error) -> DeserializeError { 141 | DeserializeError { 142 | location: None, 143 | failure: DeserializeFailure::CBOR(err), 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /docs/docs/command_line_flags.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | 6 | # Command line flags 7 | 8 | :::info `--input` 9 | Specifies the input CDDL file(s). 10 | 11 | For a single file: 12 | ```bash 13 | cddl-codegen --input examples/test.cddl --output export 14 | ``` 15 | 16 | 17 | If a directory is specified e.g. `--input=some_dir` then it will read all files in this directory (non-recursively). 18 | The output format changes here. If there's a `lib.cddl` the types contained there are the standard [output](output_format.mdx) , and any other file e.g. `foo.cddl` will have its own module `foo/mod.rs` with its own `foo/serialization.rs`, etc. 19 | 20 | ```bash 21 | cddl-codegen --input examples --output export 22 | ``` 23 | ::: 24 | 25 |

26 | 27 | 28 | :::info `--output` 29 | Specifies the output directory. 30 | 31 | ```bash 32 | cddl-codegen --input examples --output export 33 | ``` 34 | 35 | 36 | ::: 37 | 38 |

39 | 40 | 41 | :::info `--lib-name` 42 | Specify the rust crate name for the output library. The wasm crate will have `-wasm` appended. 43 | 44 | ```bash 45 | cddl-codegen --input=example --output=export --lib-name some-crate-name 46 | ``` 47 | ::: 48 | 49 |

50 | 51 | 52 | :::info `--to-from-bytes-methods` 53 | Generates `to_cbor_bytes()` / `from_cbor_bytes()` methods on all WASM objects. On by default. 54 | 55 | (The rust code doesn't need this as you can directly use the `Serialize`/`Deserialize` traits on them.) 56 | 57 | Possible values: true, false 58 | ```bash 59 | cddl-codegen --input=example --output=export --to-from-bytes-methods true 60 | ``` 61 | ::: 62 | 63 |

64 | 65 | 66 | :::info `--wasm` 67 | Whether to output a wasm crate. On by default. 68 | 69 | Possible values: true, false 70 | ```bash 71 | cddl-codegen --input=example --output=export --wasm false 72 | ``` 73 | ::: 74 | 75 |

76 | 77 | :::info `--preserve-encodings` 78 | 79 | Preserves CBOR encoding upon deserialization e.g. definite vs indefinite, map ordering. For each module this will also create a `cbor_encodings.rs` file to potentially store any structs for storing these encodings. This option is useful if you need to preserve the deserialized format for round-tripping (e.g. hashes) or if you want to modify the format to coincide with a specific tool for hashing. 80 | 81 | Possible values: true, false 82 | ```bash 83 | cddl-codegen --input=example --output=export --preserve-encodings true 84 | ``` 85 | ::: 86 | 87 |

88 | 89 | 90 | :::info `--canonical-form` 91 | Used primarily with `--preserve-encodings` to provide a way to override the specific deserialization format and to instead output canonical CBOR. This will have `Serialize`'s trait have an extra `to_canonical_cbor_bytes()` method. Likewise the wasm wrappers (with `--to-from-bytes-methods`) will contain one too. 92 | 93 | Possible values: true, false 94 | ```bash 95 | cddl-codegen --input=example --output=export --canonical-form true 96 | ``` 97 | ::: 98 | 99 |

100 | 101 | :::info `--json-serde-derives` 102 | Derives serde::Serialize/serde::Deserialize for types to allow to/from JSON 103 | 104 | Possible values: true, false 105 | ```bash 106 | cddl-codegen --input=example --output=export --json-serde-derives true 107 | ``` 108 | ::: 109 | 110 |

111 | 112 | :::info `--json-schema-export` 113 | Tags types with sonSchema derives and generates a crate (in wasm/json-gen) to export them. This requires `--json-serde-derives`. 114 | 115 | **Possible values:** true, false

116 | **Default:** true 117 | 118 | **Example:** 119 | ```bash 120 | cddl-codegen --input=example --output=export --json-schema-export true 121 | ``` 122 | ::: 123 | 124 |

125 | 126 | 127 | :::info `--package-json` 128 | Generates a npm package.json along with build scripts (some of these scripts require `--json-serde-derives`/`--json-schema-export` to work). 129 | 130 | **Possible values:** true, false

131 | **Default:** false 132 | ```bash 133 | cddl-codegen --input=example --output=export --package-json true --json-schema-export true 134 | ``` 135 | ::: 136 | 137 |

138 | 139 | :::info `--common-import-override` 140 | Overrides the location of the static exports (e.g. error.rs, serialization.rs, etc). 141 | 142 | This is particularly useful for combining multiple crates each generated using cddl-codegen where they all share a shared core directory where the static files are located. 143 | 144 | **Default:** crate 145 | ```bash 146 | cddl-codegen --input=example --output=export --common-import-override=cml_core 147 | ``` 148 | ::: 149 | 150 |

151 | 152 | :::info `--wasm-cbor-json-api-macro` 153 | If it is passed in, it will call the supplied externally defined macro on each exported type, instead of manually exporting the functions for to/from CBOR bytes + to/from JSON API. 154 | 155 | The external macro is assumed to exist at the specified path and will be imported if there are module prefixes. 156 | 157 | The macro must take the wasm wrapper type as the only parameter. 158 | 159 | This macro will be called regardless of the values of to-from-bytes-methods / json-serde-derives / etc, so it is assumed that whatever logic your macros have is consistent with the other CLI flag values. 160 | 161 | ```bash 162 | cddl-codegen --input=example --output=export --wasm-cbor-json-api-macro=cml_core_wasm::impl_wasm_cbor_json_api 163 | ``` 164 | ::: 165 | 166 |

167 | 168 | :::info `--wasm-conversion-macro` 169 | If it is passed in, it will call the supplied externally defined macro on each exported type, instead of manually exporting the rust/wasm conversion traits. 170 | 171 | The external macro is assumed to exist at the specified path and will be imported if there are module prefixes. 172 | 173 | The macro must take the rust type as the first parameter and the wasm wrapper type as the second one. 174 | 175 | ```bash 176 | cddl-codegen --input=example --output=export --wasm-conversion-macro=cml_core_wasm::impl_wasm_conversions 177 | ``` 178 | ::: 179 | -------------------------------------------------------------------------------- /GENERATING_SERIALIZATION_LIB.md: -------------------------------------------------------------------------------- 1 | # Generating/updating cardano-serialization-lib using this library 2 | 3 | We generated the bulk of the base CBOR struct code for our [cardano-serialization-lib](https://github.com/Emurgo/cardano-serialization-lib/). However, there are things that are not supported that IOHK have used, and some things are supported but might need editing post-generation. This document is useful mostly for people maintaining `cardano-serialization-lib`, but parts can be useful for anyone who wants to use this codegen tool on more complex CDDL types. 4 | 5 | 6 | 7 | ## pre-processing 8 | 9 | 10 | Remember that we don't (yet) support multi-file CDDL so everything will have to be inlined into one file. The order technically doesn't matter as we do multi-phase parsing + code generation. 11 | 12 | 13 | ### Specific to IOHK's CDDL 14 | 15 | Before we can generate using for example `alonzo.cddl` we must realize that this is not the complete CDDL. Inside of that directory's `mock/` we can find `crypto.cddl` and `extras.cddl` which contain important types. The crypto ones are partially incorrect and are just mocked crypto testing sizes, so care must be taken to ensure that they are of the appropriate size. We mostly do not directly generate these types but instead have some macros inside of the serialization lib's `crypto.rs` + `util.rs` to implement some of them for us. Some other types such as `positive_interval = #6.30([1, 2])` are purely mocked out as I believe IOHK tests against/using the CDDL directly and this made it easier for them. 16 | 17 | ### General tips 18 | 19 | As noted in the readme, not every aspect of CDDL is fully supported by this codegen tool. To work around this it might be necessary to edit the CDDL beforehand. You can define these complex types as something simple like `foo == [0]` and it will generate that base struct which you can then fill in the complex details later. If it is just a single field that is not supported, changing just that one and hand-writing it later is a good approach. Before generics were implemented we used to just inline the types directly into the generic implementation. This could still be done for more complex generics as we only support the normal cases. 20 | 21 | 22 | ### Integer precision 23 | 24 | CDDL gives us the `uint` type which can be up to 64 bits, but especially for wasm/js interop, this isn't very easy to use. We provide our own rust-specific types: `u32`, `i32`, `u64`, `i64` which are automatically use if you specify the correct control operator (ex: `.lt`). This can be a problem as `u64` for example converts to `bigint` in JS when built via `wasm_bindgen`, but your environment might not support this yet. Note that the structs will fail to deserialize if an integer out of these smaller bounds is encountered even if it's valid according to the CDDL, as it won't fit in the `u32` or whichever you selected. 25 | 26 | 27 | 28 | ## post-processing 29 | 30 | ### Deserialization not supported? 31 | 32 | Some types such as array-represented records with optional types do not support deserialization but will still generate the rest of the code, including serialization. It might be useful to generate the cddl of these specific types with the optional fields removed so that the library can output a deserialize trait that can help you work to implement the rest. This can be non-trivial, especially if there are multiple optional types, or there is some overalap of types for example the type `foo = [uint, uint?, uint, text? text]` would be non-trivial as we can't just check the next type, as if upon reading the 2nd `uint` we can't know if it was meant for the 2nd or 3rd field until we've parsed more. We also can't always rely on a length check since that is harder for indefinite encoded types causing us to need to read more to figure that out, as well as the case where there are multiple optional types. This non-triviality is precisely why a 100% general solution was not implemented in cddl-codegen. 33 | 34 | ### Extra checks? 35 | 36 | While the tool now supports the `.size` specifier, i.e. `.size (0..32)`, we don't support other modifiers such as `n*m`, `*`, `+`, etc, so these checks will need to be hand-written after-the-fact. Regular `.size` ones are now done for you. 37 | 38 | ### Constant enums? 39 | 40 | cddl-codegen can generate arbitrary type choices, which encompass enums, but this generality leads to extra code generation when we just need a simple constant-valued enumeration. In these cases you might get both a `FooEnum` and `FooKind` from `foo = 0 / 1 / 2` where only one would do the job. This will hopefully be implemented in the tool as a special case, but in the meantime just get rid of `FooKind` and try and edit the code so that you can use `FooEnum` for both, which requires a bit of editing. For a reference see `NetworkId` in `cardano-serialization-lib`. Single-value enums are also ambiguous as is the case for `language = 0 ; Plutus v1` which will generate a function like `pub fn language() -> u32 { return 0; }` whereas what we wanted was an enum with 1 value. To generate that just do `language = 0 / 1` then remove the other variant by hand later, or cddl-codegen will see it as a single isolated constant. 41 | 42 | ### wasm_bindgen issues? 43 | 44 | Type aliases for primitives can potentially lead to the generator to generate `&T` instead of `T` for a parameter even when `T` is a primitive. We should investigate this at some point if it's not fixed by now. `wasm_bindgen` also does not properly support `Option<&T>` so if you have a field like `foo = { ? 0 : &T }` then you will likely want to rewrite the accessors/ctor for that field, or change it to a `Option` and do some cloning. It's unfortunate but until `wasm_bindgen` is improved, there will be a lot of needless cloning to make a memory-safe library. 45 | 46 | ### to_bytes() / from_bytes() 47 | 48 | This is only specific to `cardano-serialization-lib` - We don't use those `ToBytes`/`FromBytes` traits from this codegen tool as we need to provide good errors to both JS consumers of wasm builds, as well as people using the rust code (e.g. mobile bindings). Instead we have a `to_from_bytes!()` macro which we call on every type we wish to have those functions which auto-converts to appropriate error types. 49 | -------------------------------------------------------------------------------- /tests/deser_test: -------------------------------------------------------------------------------- 1 | static NULL: u8 = 0xf6; 2 | static BREAK: u8 = 0xff; 3 | static ARR_INDEF: u8 = 0x9f; 4 | static MAP_INDEF: u8 = 0xbf; 5 | 6 | fn arr_def(len: u8) -> Vec { 7 | assert!(len <= 0x17); 8 | vec![0x80u8 + len] 9 | } 10 | 11 | fn map_def(len: u8) -> Vec { 12 | assert!(len <= 0x17); 13 | vec![0xa0 + len] 14 | } 15 | 16 | fn arr_sz(len: u64, sz: cbor_event::Sz) -> Vec { 17 | let mut buf = cbor_event::se::Serializer::new_vec(); 18 | buf.write_array_sz(cbor_event::LenSz::Len(len, sz)).unwrap(); 19 | buf.finalize() 20 | } 21 | 22 | fn map_sz(len: u64, sz: cbor_event::Sz) -> Vec { 23 | let mut buf = cbor_event::se::Serializer::new_vec(); 24 | buf.write_map_sz(cbor_event::LenSz::Len(len, sz)).unwrap(); 25 | buf.finalize() 26 | } 27 | 28 | fn cbor_string(s: &str) -> Vec { 29 | // our keys are short so we don't handle bigger ones 30 | assert!(s.len() <= 0x17); 31 | let mut bytes = Vec::with_capacity(s.len() + 1); 32 | bytes.push(0x60u8 + s.len() as u8); 33 | bytes.extend_from_slice(s.as_bytes()); 34 | bytes 35 | } 36 | 37 | fn cbor_tag(t: u8) -> Vec { 38 | assert!(t <= 0xd7 - 0xc0); 39 | vec![0xc0u8 + t] 40 | } 41 | 42 | fn cbor_int(x: i128, sz: cbor_event::Sz) -> Vec { 43 | let mut buf = cbor_event::se::Serializer::new_vec(); 44 | if x >= 0 { 45 | buf.write_unsigned_integer_sz(x as u64, sz).unwrap(); 46 | } else { 47 | buf.write_negative_integer_sz(x, sz).unwrap(); 48 | } 49 | buf.finalize() 50 | } 51 | 52 | fn cbor_tag_sz(tag: u64, sz: cbor_event::Sz) -> Vec { 53 | let mut buf = cbor_event::se::Serializer::new_vec(); 54 | buf.write_tag_sz(tag, sz).unwrap(); 55 | buf.finalize() 56 | } 57 | 58 | fn cbor_str_sz(s: &str, sz: cbor_event::StringLenSz) -> Vec { 59 | let mut buf = cbor_event::se::Serializer::new_vec(); 60 | buf.write_text_sz(s, sz).unwrap(); 61 | buf.finalize() 62 | } 63 | 64 | fn cbor_bytes_sz(bytes: Vec, sz: cbor_event::StringLenSz) -> Vec { 65 | let mut buf = cbor_event::se::Serializer::new_vec(); 66 | buf.write_bytes_sz(bytes, sz).unwrap(); 67 | buf.finalize() 68 | } 69 | 70 | fn cbor_float(f: f64) -> Vec { 71 | let mut buf = cbor_event::se::Serializer::new_vec(); 72 | buf.write_special(cbor_event::Special::Float(f)).unwrap(); 73 | buf.finalize() 74 | } 75 | 76 | fn print_cbor_types(obj_name: &str, vec: &Vec) { 77 | use cbor_event::Type; 78 | let mut raw = cbor_event::de::Deserializer::from(std::io::Cursor::new(vec)); 79 | let mut lens = Vec::new(); 80 | let consume_elem = |lens: &mut Vec| { 81 | if let Some(len) = lens.last_mut() { 82 | if let cbor_event::LenSz::Len(n, _) = len { 83 | *n -= 1; 84 | } 85 | } 86 | }; 87 | let reduce_depth = |lens: &mut Vec| { 88 | while let Some(cbor_event::LenSz::Len(0, _)) = lens.last() { 89 | lens.pop(); 90 | println!("{}}}", "\t".repeat(lens.len())); 91 | } 92 | }; 93 | println!("{} = {{", obj_name); 94 | loop { 95 | print!("{}", "\t".repeat(lens.len())); 96 | match raw.cbor_type() { 97 | Err(_) => break, 98 | Ok(Type::UnsignedInteger) => { 99 | let (x, sz) = raw.unsigned_integer_sz().unwrap(); 100 | println!("UINT({}, {:?})", x, sz); 101 | consume_elem(&mut lens); 102 | reduce_depth(&mut lens); 103 | }, 104 | Ok(Type::NegativeInteger) => { 105 | let (x, sz) = raw.negative_integer_sz().unwrap(); 106 | println!("NINT({}, {:?})", x, sz); 107 | consume_elem(&mut lens); 108 | reduce_depth(&mut lens); 109 | }, 110 | Ok(Type::Bytes) => { 111 | let (x, sz) = raw.bytes_sz().unwrap(); 112 | println!("BYTES({:?}, {:?})", x, sz); 113 | consume_elem(&mut lens); 114 | reduce_depth(&mut lens); 115 | }, 116 | Ok(Type::Text) => { 117 | let (x, sz) = raw.text_sz().unwrap(); 118 | println!("TEXT(\"{}\", {:?})", x, sz); 119 | consume_elem(&mut lens); 120 | reduce_depth(&mut lens); 121 | }, 122 | Ok(Type::Array) => { 123 | let len = raw.array_sz().unwrap(); 124 | println!("ARRAY({:?}) {{", len); 125 | consume_elem(&mut lens); 126 | lens.push(len); 127 | if let cbor_event::LenSz::Len(0, _sz) = len { 128 | reduce_depth(&mut lens); 129 | } 130 | }, 131 | Ok(Type::Map) => { 132 | let len = raw.map_sz().unwrap(); 133 | println!("MAP({:?}) {{", len); 134 | consume_elem(&mut lens); 135 | lens.push(match len { 136 | cbor_event::LenSz::Len(n, sz) => cbor_event::LenSz::Len(2 * n, sz), 137 | cbor_event::LenSz::Indefinite => cbor_event::LenSz::Indefinite, 138 | }); 139 | if let cbor_event::LenSz::Len(0, _sz) = len { 140 | reduce_depth(&mut lens); 141 | } 142 | }, 143 | Ok(Type::Tag) => { 144 | let (tag, sz) = raw.tag_sz().unwrap(); 145 | println!("TAG({}, {:?})", tag, sz); 146 | }, 147 | Ok(Type::Special) => { 148 | let special = raw.special().unwrap(); 149 | println!("SPECIAL({:?})", special); 150 | if special == cbor_event::Special::Break { 151 | if let Some(cbor_event::LenSz::Indefinite) = lens.last() { 152 | lens.pop(); 153 | reduce_depth(&mut lens); 154 | } else { 155 | panic!("unexpected break"); 156 | } 157 | } else { 158 | consume_elem(&mut lens); 159 | reduce_depth(&mut lens); 160 | } 161 | }, 162 | } 163 | } 164 | println!("}}"); 165 | } 166 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeMap; 2 | 3 | pub fn cbor_type_code_str(cbor_type: cbor_event::Type) -> &'static str { 4 | match cbor_type { 5 | cbor_event::Type::UnsignedInteger => "cbor_event::Type::UnsignedInteger", 6 | cbor_event::Type::NegativeInteger => "cbor_event::Type::NegativeInteger", 7 | cbor_event::Type::Bytes => "cbor_event::Type::Bytes", 8 | cbor_event::Type::Text => "cbor_event::Type::Text", 9 | cbor_event::Type::Array => "cbor_event::Type::Array", 10 | cbor_event::Type::Map => "cbor_event::Type::Map", 11 | cbor_event::Type::Tag => "cbor_event::Type::Tag", 12 | cbor_event::Type::Special => "cbor_event::Type::Special", 13 | } 14 | } 15 | 16 | pub fn convert_to_snake_case(ident: &str) -> String { 17 | let mut snake_case = String::new(); 18 | let mut in_uppercase_run = false; 19 | let mut iter = ident.chars().peekable(); 20 | while let Some(c) = iter.next() { 21 | match c { 22 | '-' => { 23 | snake_case.push('_'); 24 | } 25 | '$' | '@' => { 26 | // ignored 27 | } 28 | c => { 29 | // NFT -> nft 30 | // IPAddress -> ip_address 31 | // shelley_MA -> shelley_ma 32 | if in_uppercase_run { 33 | if c.is_ascii_uppercase() { 34 | if let Some(next) = iter.peek() { 35 | if next.is_ascii_lowercase() { 36 | if !snake_case.is_empty() { 37 | snake_case.push('_'); 38 | } 39 | in_uppercase_run = false; 40 | } 41 | } 42 | } else { 43 | in_uppercase_run = false; 44 | } 45 | } else if c.is_ascii_uppercase() { 46 | if !snake_case.is_empty() { 47 | snake_case.push('_'); 48 | } 49 | in_uppercase_run = true; 50 | } 51 | snake_case.push(c.to_ascii_lowercase()); 52 | } 53 | } 54 | } 55 | snake_case 56 | } 57 | 58 | pub fn convert_to_camel_case(ident: &str) -> String { 59 | let mut camel_case = String::new(); 60 | let mut uppercase = true; 61 | for c in ident.chars() { 62 | match c { 63 | '_' | '-' => { 64 | uppercase = true; 65 | } 66 | '$' | '@' => { 67 | // ignored 68 | } 69 | c => { 70 | if uppercase { 71 | camel_case.push(c.to_ascii_uppercase()); 72 | uppercase = false; 73 | } else { 74 | camel_case.push(c); 75 | } 76 | } 77 | } 78 | } 79 | camel_case 80 | } 81 | 82 | pub fn cddl_prelude(name: &str) -> Option<&str> { 83 | match name { 84 | // custom implemented types like uint, bool, etc 85 | // are handled in the alias system and shouldn't reach here 86 | "uint" | "nint" | "int" | "bool" | "tstr" | "text" | 87 | "bstr" | "bytes" | "null" | "nil" | "true" | "false" | 88 | "float16" | // #7.25 89 | "float32" | // #7.26 90 | "float64" | // #7.27 91 | "float16-32" | // float16 / float32 92 | "float32-64" | // float32 / float64 93 | "float" => unreachable!("{} should be handled by the alias system instead", name), 94 | "tdate" => Some("#6.0(tstr)"), 95 | "time" => Some("#6.1(number)"), 96 | "number" => Some("int / float"), 97 | "biguint" => Some("#6.2(bstr)"), 98 | "bignint" => Some("#6.3(bstr)"), 99 | "bigint" => Some("biguint / bignint"), 100 | "integer" => Some("int / bigint"), 101 | "unsigned" => Some("uint / biguint"), 102 | "decfrac" => Some("#6.4([e10: int), m: integer])"), 103 | "bigfloat" => Some("#6.5([e2: int), m: integer])"), 104 | "encoded-cbor" => Some("#6.24(bstr)"), 105 | "uri" => Some("#6.32(tstr)"), 106 | "b64url" => Some("#6.33(tstr)"), 107 | "b64legacy" => Some("#6.34(tstr)"), 108 | "regexp" => Some("#6.35(tstr)"), 109 | "mime-message" => Some("#6.36(tstr)"), 110 | // TODO: we don't support any (yet) - could we use message-signing's code? 111 | "any" | // # 112 | "cbor-any" | // #6.55799(any) 113 | "eb64url" | // #6.21(any) 114 | "eb64legacy" | // #6.22(any) 115 | "eb16" | // #6.23(any)"), 116 | // TODO: nor undefined (yet) 117 | "undefined" => panic!("unsupported cddl prelude type: {}", name), // #7.23 118 | _ => None, 119 | } 120 | } 121 | 122 | #[rustfmt::skip] 123 | pub fn is_identifier_reserved(name: &str) -> bool { 124 | match name { 125 | // These are all possible reserved identifiers, even if we don't support them 126 | "uint" | 127 | "int" | 128 | "nint" | 129 | "text" | 130 | "tstr" | 131 | "bytes" | 132 | "bstr" | 133 | "bool" | 134 | "float" | 135 | "float16" | 136 | "float32" | 137 | "float64" | 138 | "float16-32" | 139 | "float32-64" | 140 | "tdate" | 141 | "time" | 142 | "number" | 143 | "biguint" | 144 | "bignint" | 145 | "bigint" | 146 | "integer" | 147 | "unsigned" | 148 | "decfrac" | 149 | "bigfloat" | 150 | "eb64url" | 151 | "eb64legacy" | 152 | "eb16" | 153 | "encoded-cbor" | 154 | "uri" | 155 | "b64url" | 156 | "b64legacy" | 157 | "regexp" | 158 | "mime-messag e" | 159 | "cbor-any" | 160 | "null" | 161 | "nil" | 162 | "undefined" | 163 | "true" | 164 | "false" => true, 165 | _ => false, 166 | } 167 | } 168 | 169 | // as we also support our own identifiers for selecting integer precision, we need this too 170 | #[rustfmt::skip] 171 | #[allow(unused)] 172 | pub fn is_identifier_in_our_prelude(name: &str) -> bool { 173 | matches!(name, 174 | "u8" | 175 | "i8" | 176 | "u16" | 177 | "i16" | 178 | "u32" | 179 | "i32" | 180 | "f32" | 181 | "u64" | 182 | "i64" | 183 | "f64" 184 | ) 185 | } 186 | 187 | pub fn is_identifier_user_defined(name: &str) -> bool { 188 | !is_identifier_reserved(name) 189 | } 190 | 191 | pub fn append_number_if_duplicate(used_names: &mut BTreeMap, name: String) -> String { 192 | let entry = used_names.entry(name.clone()).or_default(); 193 | *entry += 1; 194 | if *entry > 1 { 195 | format!("{}{}", name, *entry) 196 | } else { 197 | name 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod cli; 2 | pub(crate) mod comment_ast; 3 | pub(crate) mod dep_graph; 4 | pub(crate) mod generation; 5 | pub(crate) mod intermediate; 6 | pub(crate) mod parsing; 7 | pub(crate) mod rust_reserved; 8 | pub(crate) mod utils; 9 | 10 | use clap::Parser; 11 | use cli::Cli; 12 | use comment_ast::RuleMetadata; 13 | use generation::GenerationScope; 14 | use intermediate::{CDDLIdent, IntermediateTypes, PlainGroupInfo, RustIdent}; 15 | use once_cell::sync::Lazy; 16 | use parsing::{parse_rule, rule_ident, rule_is_scope_marker}; 17 | 18 | pub static CLI_ARGS: Lazy = Lazy::new(Cli::parse); 19 | 20 | use crate::intermediate::ROOT_SCOPE; 21 | 22 | fn cddl_paths( 23 | output: &mut Vec, 24 | cd: &std::path::PathBuf, 25 | ) -> std::io::Result<()> { 26 | for dir_entry in std::fs::read_dir(cd)? { 27 | let path = dir_entry?.path(); 28 | if path.is_dir() { 29 | cddl_paths(output, &path)?; 30 | } else if path.as_path().extension().unwrap() == "cddl" { 31 | output.push(path); 32 | } else { 33 | println!("Skipping file: {}", path.as_path().to_str().unwrap()); 34 | } 35 | } 36 | Ok(()) 37 | } 38 | 39 | fn main() -> Result<(), Box> { 40 | // Pre-processing files for multi-file support 41 | let input_files = if CLI_ARGS.input.is_dir() { 42 | let mut cddl_paths_buf = Vec::new(); 43 | cddl_paths(&mut cddl_paths_buf, &CLI_ARGS.input)?; 44 | cddl_paths_buf 45 | } else { 46 | vec![CLI_ARGS.input.clone()] 47 | }; 48 | // To get around an issue with cddl where you can't parse a partial cddl fragment 49 | // we must group all files together. To mark scope we insert string constants with 50 | // a specific, unlikely to ever be used, prefix. The names contain a number after 51 | // to avoid a parsing error (rule with same identifier already defined). 52 | // This approach was chosen over comments as those were finicky when not attached 53 | // to specific structs, and the existing comment parsing ast was not suited for this. 54 | // If, in the future, cddl released a feature flag to allow partial cddl we can just 55 | // remove all this and revert back the commit before this one for scope handling. 56 | let mut input_files_content = input_files 57 | .iter() 58 | .enumerate() 59 | .map(|(i, input_file)| { 60 | let scope = if input_files.len() > 1 { 61 | use std::path::Component; 62 | let relative = pathdiff::diff_paths(input_file, &CLI_ARGS.input).unwrap(); 63 | let mut components = relative 64 | .components() 65 | .filter_map(|p| match p { 66 | Component::Normal(part) => Some( 67 | std::path::Path::new(part) 68 | .file_stem() 69 | .unwrap() 70 | .to_str() 71 | .unwrap() 72 | .to_owned(), 73 | ), 74 | _ => None, 75 | }) 76 | .collect::>(); 77 | if let Some(c) = components.last() { 78 | if *c == "mod" { 79 | components.pop(); 80 | } 81 | } 82 | components.join("::") 83 | } else { 84 | ROOT_SCOPE.to_string() 85 | }; 86 | std::fs::read_to_string(input_file).map(|raw| { 87 | format!( 88 | "\n{}{} = \"{}\"\n{}\n", 89 | parsing::SCOPE_MARKER, 90 | i, 91 | scope, 92 | raw 93 | ) 94 | }) 95 | }) 96 | .collect::>()?; 97 | let export_raw_bytes_encoding_trait = input_files_content.contains(parsing::RAW_BYTES_MARKER); 98 | // we also need to mark the extern marker to a placeholder struct that won't get codegened 99 | input_files_content.push_str(&format!("{} = [0]", parsing::EXTERN_MARKER)); 100 | // and a raw bytes one too 101 | input_files_content.push_str(&format!("{} = [1]", parsing::RAW_BYTES_MARKER)); 102 | 103 | // Plain group / scope marking 104 | let cddl = cddl::parser::cddl_from_str(&input_files_content, true)?; 105 | //panic!("cddl: {:#?}", cddl); 106 | let pv = cddl::ast::parent::ParentVisitor::new(&cddl).unwrap(); 107 | let mut types = IntermediateTypes::new(); 108 | // mark scope and filter scope markers 109 | let mut scope = ROOT_SCOPE.clone(); 110 | let cddl_rules = cddl 111 | .rules 112 | .iter() 113 | .filter(|cddl_rule| { 114 | // We inserted string constants with specific prefixes earlier to mark scope 115 | if let Some(new_scope) = rule_is_scope_marker(cddl_rule) { 116 | println!("Switching from scope '{scope}' to '{new_scope}'"); 117 | scope = new_scope; 118 | false 119 | } else { 120 | let ident = rule_ident(cddl_rule); 121 | types.mark_scope(ident, scope.clone()); 122 | true 123 | } 124 | }) 125 | .collect::>(); 126 | // We need to know beforehand which are plain groups so we can serialize them properly 127 | // e.g. x = (3, 4), y = [1, x, 2] should be [1, 3, 4, 2] instead of [1, [3, 4], 2] 128 | for cddl_rule in cddl_rules.iter() { 129 | if let cddl::ast::Rule::Group { rule, .. } = cddl_rule { 130 | // Freely defined group - no need to generate anything outside of group module 131 | match &rule.entry { 132 | cddl::ast::GroupEntry::InlineGroup { 133 | group, 134 | comments_after_group, 135 | .. 136 | } => { 137 | assert_eq!(group.group_choices.len(), 1); 138 | let rule_metadata = RuleMetadata::from(comments_after_group.as_ref()); 139 | types.mark_plain_group( 140 | RustIdent::new(CDDLIdent::new(rule.name.to_string())), 141 | PlainGroupInfo::new(Some(group.clone()), rule_metadata), 142 | ); 143 | } 144 | x => panic!("Group rule with non-inline group? {:?}", x), 145 | } 146 | } 147 | } 148 | 149 | // Creating intermediate form from the CDDL 150 | for cddl_rule in dep_graph::topological_rule_order(&cddl_rules) { 151 | println!("\n\n------------------------------------------\n- Handling rule: {}:{}\n------------------------------------", scope, cddl_rule.name()); 152 | parse_rule(&mut types, &pv, cddl_rule, &CLI_ARGS); 153 | } 154 | types.finalize(&pv, &CLI_ARGS); 155 | 156 | // Generating code from intermediate form 157 | println!("\n-----------------------------------------\n- Generating code...\n------------------------------------"); 158 | let mut gen_scope = GenerationScope::new(); 159 | gen_scope.generate(&types, &CLI_ARGS); 160 | gen_scope.export(&types, export_raw_bytes_encoding_trait, &CLI_ARGS)?; 161 | types.print_info(); 162 | 163 | gen_scope.print_structs_without_deserialize(); 164 | 165 | Ok(()) 166 | } 167 | 168 | #[cfg(test)] 169 | mod test; 170 | -------------------------------------------------------------------------------- /tests/core/input.cddl: -------------------------------------------------------------------------------- 1 | hash = bytes .size (0..8) 2 | 3 | foo = [uint, text, bytes] 4 | 5 | tagged_text = #6.42(text) 6 | 7 | opt_text = tagged_text / null 8 | 9 | foo2 = #6.23([uint, opt_text]) 10 | 11 | bar = { 12 | foo: #6.1337(foo), 13 | ? derp: uint, 14 | 1 : uint / null, ; @name one 15 | ? 5: text, 16 | five: 5, 17 | float: float64, 18 | } 19 | 20 | plain = (d: #6.23(uint), e: tagged_text) 21 | outer = [a: uint, b: plain, c: "some text"] ; @used_as_key 22 | plain_arrays = [ 23 | embedded: plain, 24 | single: [plain], 25 | multi: [*plain], 26 | ] 27 | 28 | table = { * uint => text } 29 | table_arr_members = { 30 | tab: { * text => text }, 31 | arr: [*uint], 32 | arr2: [*foo], 33 | } 34 | 35 | c_enum = 3 / 1 / 4 36 | 37 | type_choice = 0 / "hello world" / uint / text / bytes / #6.64([*uint]) ; @used_as_key 38 | 39 | non_overlapping_type_choice_all = uint / nint / text / bytes / #6.30("hello world") / [* uint] / { *text => uint } 40 | 41 | non_overlapping_type_choice_some = uint / nint / text 42 | 43 | overlap_basic_embed = [ 44 | ; @name identity 45 | tag: 0 // 46 | ; @name x 47 | tag: 1, hash: bytes .size 32 48 | ] 49 | 50 | non_overlap_basic_embed = [ 51 | ; @name first 52 | x: uint, tag: 0 // 53 | ; @name second 54 | y: text, tag: 1 55 | ] 56 | 57 | non_overlap_basic_embed_multi_fields = [ 58 | ; @name first 59 | x: uint, z: uint // 60 | ; @name second 61 | y: text, z: uint 62 | ] 63 | 64 | non_overlap_basic_embed_mixed = [ 65 | ; @name first 66 | x: uint, tag: 0 // 67 | ; @name second 68 | y: text, z: uint 69 | ] 70 | 71 | bytes_uint = (bytes, uint) 72 | 73 | non_overlap_basic_embed_mixed_explicit = [ 74 | ; @name first 75 | x: uint, tag: 0 // 76 | ; @name second 77 | y: text, z: uint // 78 | ; @name third 79 | bytes_uint 80 | ] 81 | 82 | basic = (uint, text) 83 | 84 | basic_arr = [basic] 85 | 86 | ; not overlap since double array for second 87 | non_overlap_basic_not_basic = [ 88 | ; @name group 89 | basic // 90 | ; @name group_arr 91 | basic_arr // 92 | ; @name group_tagged 93 | #6.11(basic) // 94 | ; @name group_bytes 95 | bytes .cbor basic 96 | ] 97 | 98 | enums = [ 99 | c_enum, 100 | type_choice, 101 | ] 102 | 103 | group_choice = [ foo // 0, x: uint // plain ] ; @used_as_key 104 | 105 | foo_bytes = bytes .cbor foo 106 | 107 | ; since we don't generate code for definitions like the above (should we if no one refers to it?) 108 | cbor_in_cbor = [foo_bytes, uint_bytes: bytes .cbor uint, tagged_foo_bytes] 109 | 110 | tagged_foo_bytes = #6.20(bytes .cbor foo) 111 | 112 | u8 = uint .size 1 113 | u16 = uint .le 65535 114 | u32 = 0..4294967295 115 | u64 = uint .size 8 ; 8 bytes 116 | i8 = -128 .. 127 117 | i64 = int .size 8 ; 8 bytes 118 | 119 | signed_ints = [ 120 | u_8: 0 .. 255, 121 | u_16: uint .size 2, 122 | u_32: uint .size 4, 123 | u_64: uint .le 18446744073709551615, 124 | i_8: int .size 1, 125 | i_16: int .size 2, 126 | i_32: -2147483648 .. 2147483647, 127 | i_64: int .size 8, 128 | n_64: nint, 129 | u64_max: 18446744073709551615, 130 | ; this test assumes i64::BITS == isize::BITS (i.e. 64-bit programs) or else the cddl parsing lib would mess up 131 | ; if this test fails on your platform we might need to either remove this part 132 | ; or make a fix for the cddl library. 133 | ; The fix would be ideal as even though the true min in CBOR would be -u64::MAX 134 | ; we can't test that since isize::BITS is never > 64 in any normal system and likely never will be 135 | i64_min: -9223372036854775808, 136 | ] 137 | 138 | default_uint = uint .default 1337 139 | 140 | map_with_defaults = { 141 | ? 1 : default_uint 142 | ? 2 : text .default "two" 143 | } 144 | 145 | paren_size = uint .size (1) 146 | paren_cbor = bytes .cbor (text) 147 | 148 | no_alias_u32 = 0..4294967295 ; @no_alias 149 | no_alias_u64 = uint .size 8 ; @no_alias 150 | 151 | no_alias = [ 152 | no_alias_u32, 153 | no_alias_u64 154 | ] 155 | 156 | external_foo = _CDDL_CODEGEN_EXTERN_TYPE_ 157 | 158 | externs = { 159 | req: external_foo, 160 | ? opt: external_foo, 161 | } 162 | 163 | extern_generic = _CDDL_CODEGEN_EXTERN_TYPE_ 164 | 165 | using_extern_generic = [ 166 | foo: extern_generic, 167 | ] 168 | 169 | ; types below test codegen_table_type 170 | 171 | standalone_table = { * uint => text } 172 | standalone_text = { * text => text } 173 | 174 | embedded_table = { * uint => text } 175 | embedded_text = { * text => text } 176 | 177 | table_array_wrapper = [embedded_table] 178 | table_map_wrapper = { 721: embedded_table } 179 | 180 | text_array_wrapper = [embedded_text] 181 | text_map_wrapper = { 721: embedded_text } 182 | 183 | inline_wrapper = [{ * text => text }] 184 | 185 | top_level_array = [* uint] 186 | top_level_single_elem = [uint] 187 | 188 | wrapper_table = { * uint => uint } ; @newtype 189 | wrapper_list = [ * uint ] ; @newtype 190 | wrapper_int = uint ; @newtype custom_getter 191 | 192 | overlapping_inlined = [ 193 | ; @name one 194 | 0 // 195 | ; @name two 196 | 0, uint // 197 | ; @name three 198 | 0, uint, text 199 | ] 200 | 201 | overlapping0 = [0] 202 | overlapping1 = [0, uint] 203 | overlapping2 = [0, uint, text] 204 | 205 | overlapping = overlapping0 ; @name A 206 | / overlapping1 ; @name B 207 | / overlapping2 ; @name C 208 | 209 | array_opt_fields = [ 210 | ? x: 1.010101 211 | ? a: uint, 212 | ? b: text, 213 | c: nint, 214 | ? d: text, 215 | y: 3.14159265 216 | ? e: non_overlapping_type_choice_some 217 | ? z: 2.71828 218 | ] 219 | 220 | bounds = [ 221 | w: -1000 .. 1000 222 | x: uint .le 7, 223 | y: nint .ge -5, 224 | z: text .size (3..14), 225 | a: [* uint] .size (1..3), 226 | b: { * uint => uint } .le 3 227 | ] 228 | 229 | bounds_type_choice = bytes .size (0..64) 230 | / text .size (0..64) 231 | 232 | bounds_group_choice = [ 233 | ; @name a 234 | a: uint, b: text .le 4 // 235 | ; @name b 236 | hash // 237 | ; @name c 238 | 1, x: hash, y: hash 239 | ] 240 | 241 | enum_opt_embed_fields = [ 242 | ; @name ea 243 | 1 // 244 | ; @name eb 245 | 1, ?text, 5 // 246 | ; @name ec 247 | 1, uint, 7 // 248 | ; doesn't parse but would result in triple nesting so worth testing if we can ever parse it 249 | ; 1, ? (text / null), #6.9(9) 250 | ; @name ed 251 | 1, uint, ?text // 252 | ; @name ee 253 | 1, uint, ?bytes, uint // 254 | ; @name ef 255 | 1, ? non_overlapping_type_choice_some, #6.11(11) // 256 | ; @name eg 257 | 1, ? overlapping_inlined, #6.13(13) 258 | ] 259 | 260 | casing_test = [ 261 | ; @name NFT 262 | 1 // 263 | ; @name IPAddress 264 | 2 // 265 | ; @name ShelleyMA 266 | 3 // 267 | ; @name VRF_vkey 268 | 4 269 | ] 270 | 271 | custom_bytes = bytes ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 272 | 273 | struct_with_custom_serialization = [ 274 | custom_bytes, 275 | field: bytes, ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 276 | overridden: custom_bytes, ; @custom_serialize write_hex_string @custom_deserialize read_hex_string 277 | tagged1: #6.9(custom_bytes), 278 | tagged2: #6.9(uint), ; @custom_serialize write_tagged_uint_str @custom_deserialize read_tagged_uint_str 279 | ] 280 | 281 | docs = [ 282 | foo: text, ; @doc this is a field-level comment 283 | bar: uint, ; @doc bar is a u64 284 | ] ; @doc struct documentation here 285 | 286 | docs_groupchoice = [ 287 | ; @name first @doc comment-about-first 288 | 0, uint // 289 | ; @doc comments about second @name second 290 | text 291 | ] ; @doc type-level comment -------------------------------------------------------------------------------- /docs/docs/comment_dsl.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | 6 | # Comment DSL 7 | 8 | We have a comment DSL to help annotate the output code beyond what is possible just with CDDL. 9 | 10 | ## @name 11 | 12 | For example in an array-encoded group you can give explicit names just by the keys e.g.: 13 | ```cddl 14 | foo = [ 15 | bar: uint, 16 | baz: text 17 | ] 18 | ``` 19 | but with map-encoded structs the keys are stored and for things like integer keys this isn't very helpful e.g.: 20 | ```cddl 21 | tx = { 22 | ? 0: [* input], 23 | ? 1: [* outputs], 24 | } 25 | ``` 26 | we would end up with two fields: `key_0` and `key_1`. We can instead end up with fields named `inputs` and `outputs` by doing: 27 | ```cddl 28 | tx = { 29 | ? 0: [* input], ; @name inputs 30 | ? 1: [* outputs], ; @name outputs 31 | } 32 | ``` 33 | Note: the parsing can be finicky. For struct fields you must put the comment AFTER the comma, and the comma must exist even for the last field in a struct. 34 | 35 | It is also possible to use `@name` with type choices: 36 | ```cddl 37 | foo = 0 ; @name mainnet 38 | / 1 ; @name testnet 39 | ``` 40 | and also for group choices: 41 | ```cddl 42 | script = [ 43 | ; @name native 44 | tag: 0, script: native_script // 45 | ; @name plutus_v1 46 | tag: 1, script: plutus_v1_script // 47 | ; @name plutus_v2 48 | tag: 2, script: plutus_v2_script 49 | ] 50 | ``` 51 | 52 | ## @newtype 53 | 54 | With code like `foo = uint` this creates an alias e.g. `pub type Foo = u64;` in rust. When we use `foo = uint ; @newtype` it instead creates a `pub struct Foo(u64);`. 55 | 56 | `@newtype` can also optionally specify a getter function e.g. `foo = uint ; @newtype custom_getter` will generate: 57 | 58 | ```rust 59 | impl Foo { 60 | pub fn custom_getter(&self) -> u64 { 61 | self.0 62 | } 63 | } 64 | ``` 65 | 66 | ## @no_alias 67 | 68 | ```cddl 69 | foo = uint 70 | bar = [ 71 | field: foo 72 | ] 73 | ``` 74 | This would normally result in: 75 | ```rust 76 | pub type Foo = u64; 77 | pub struct Bar { 78 | field: Foo, 79 | } 80 | ``` 81 | but if we use `@no_alias` it skips generating an alias and uses it directly e.g.: 82 | ```cddl 83 | foo = uint ; @no_alias 84 | bar = [ 85 | field: foo 86 | ] 87 | ``` 88 | to 89 | ```rust 90 | pub struct Bar { 91 | field: u64, 92 | } 93 | ``` 94 | 95 | ## @used_as_key 96 | 97 | ```cddl 98 | foo = [ 99 | x: uint, 100 | y: uint, 101 | ] ; @used_as_Key 102 | ``` 103 | 104 | cddl-codegen automatically derives `Ord`/`PartialOrd` or `Hash` for any types used within as a key in another type. 105 | Putting this comment on a type forces that type to derive those traits even if it weren't used in a key in the cddl spec. 106 | This is useful for when you are writing utility code that would put them in a map and want the generated code to have it already, 107 | which is particularly useful for re-generating as it lets your `mod.rs` files remain untouched. 108 | 109 | ## @custom_json 110 | 111 | ```cddl 112 | foo = uint ; @newtype @custom_json 113 | ``` 114 | 115 | Avoids generating and/or deriving json-related traits under the assumption that the user will supply their own implementation to be used in the generated library. 116 | 117 | ## @custom_serialize / @custom_deserialize 118 | 119 | ```cddl 120 | custom_bytes = bytes ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 121 | 122 | struct_with_custom_serialization = [ 123 | custom_bytes, 124 | field: bytes, ; @custom_serialize custom_serialize_bytes @custom_deserialize custom_deserialize_bytes 125 | overridden: custom_bytes, ; @custom_serialize write_hex_string @custom_deserialize read_hex_string 126 | tagged1: #6.9(custom_bytes), 127 | tagged2: #6.9(uint), ; @custom_serialize write_tagged_uint_str @custom_deserialize read_tagged_uint_str 128 | ] 129 | ``` 130 | 131 | This allows the overriding of serialization and/or deserialization for when a specific format must be maintained. This works even with primitives where _CDDL_CODEGEN_EXTERN_TYPE_ would require making a wrapper type to use. 132 | 133 | The string after `@custom_serialize`/`@custom_deserialize` will be directly called as a function in place of regular serialization/deserialization code. As such it must either be specified using fully qualified paths e.g. `@custom_serialize crate::utils::custom_serialize_function`, or post-generation it will need to be imported into the serialization code by hand e.g. adding `import crate::utils::custom_serialize_function;`. 134 | 135 | With `--preserve-encodings=true` the encoding variables must be passed in in the order they are used in cddl-codegen with regular serialization. They are passed in as `Option` for integers/tags, `LenEncoding` for lengths and `StringEncoding` for text/bytes. These are the same types as are stored in the `*Encoding` structs generated. The same must be returned for deserialization. When there are no encoding variables the deserialized value should be directly returned, and if not a tuple with the value and its encoding variables should be returned. 136 | 137 | There are two ways to use this comment DSL: 138 | 139 | * Type level: e.g. `custom_bytes`. This will replace the (de)serialization everywhere you use this type. 140 | * Field level: e.g. `struct_with_custom_serialization.field`. This will entirely replace the (de)serialization logic for the entire field, including other encoding operations like tags, `.cbor`, etc. 141 | 142 | Example function signatures for `--preserve-encodings=false` for `custom_serialize_bytes` / `custom_deserialize_bytes` above: 143 | 144 | ```rust 145 | pub fn custom_serialize_bytes<'se, W: std::io::Write>( 146 | serializer: &'se mut cbor_event::se::Serializer, 147 | bytes: &[u8], 148 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> 149 | 150 | pub fn custom_deserialize_bytes( 151 | raw: &mut cbor_event::de::Deserializer, 152 | ) -> Result, DeserializeError> 153 | ``` 154 | 155 | Example function signatures for `--preserve-encodings=true` for `write_tagged_uint_str` / `read_tagged_uint_str` above: 156 | 157 | ```rust 158 | pub fn write_tagged_uint_str<'se, W: std::io::Write>( 159 | serializer: &'se mut cbor_event::se::Serializer, 160 | uint: &u64, 161 | tag_encoding: Option, 162 | text_encoding: Option, 163 | ) -> cbor_event::Result<&'se mut cbor_event::se::Serializer> 164 | 165 | pub fn read_tagged_uint_str( 166 | raw: &mut cbor_event::de::Deserializer, 167 | ) -> Result<(u64, Option, Option), DeserializeError> 168 | ``` 169 | 170 | Note that as this is at the field-level it must handle the tag as well as the `uint`. 171 | 172 | For more examples see `tests/custom_serialization` (used in the `core` and `core_no_wasm` tests) and `tests/custom_serialization_preserve` (used in the `preserve-encodings` test). 173 | 174 | ## @doc 175 | 176 | This can be placed at field-level, struct-level or variant-level to specify a comment to be placed as a rust doc-comment. 177 | 178 | ```cddl 179 | docs = [ 180 | foo: text, ; @doc this is a field-level comment 181 | bar: uint, ; @doc bar is a u64 182 | ] ; @doc struct documentation here 183 | 184 | docs_groupchoice = [ 185 | ; @name first @doc comment-about-first 186 | 0, uint // 187 | ; @doc comments about second @name second 188 | text 189 | ] ; @doc type-level comment 190 | ``` 191 | 192 | Will generate: 193 | ```rust 194 | /// struct documentation here 195 | #[derive(Clone, Debug)] 196 | pub struct Docs { 197 | /// this is a field-level comment 198 | pub foo: String, 199 | /// bar is a u64 200 | pub bar: u64, 201 | } 202 | 203 | impl Docs { 204 | /// * `foo` - this is a field-level comment 205 | /// * `bar` - bar is a u64 206 | pub fn new(foo: String, bar: u64) -> Self { 207 | Self { foo, bar } 208 | } 209 | } 210 | 211 | /// type-level comment 212 | #[derive(Clone, Debug)] 213 | pub enum DocsGroupchoice { 214 | /// comment-about-first 215 | First(u64), 216 | /// comments about second 217 | Second(String), 218 | } 219 | ``` 220 | 221 | Due to the comment dsl parsing this doc comment cannot contain the character `@`. 222 | 223 | 224 | ## _CDDL_CODEGEN_EXTERN_TYPE_ 225 | 226 | While not as a comment, this allows you to compose in hand-written structs into a cddl spec. 227 | ```cddl 228 | foo = _CDDL_CODEGEN_EXTERN_TYPE_ 229 | bar = [ 230 | x: uint, 231 | y: foo, 232 | ] 233 | ``` 234 | This will treat `Foo` as a type that will exist and that has implemented the `Serialize` and `Deserialize` traits, so the (de)serialization logic in `Bar` here will call `Foo::serialize()` and `Foo::deserialize()`. 235 | This can also be useful when you have a spec that is either very awkward to use (so you hand-write or hand-modify after generation) in some type so you don't generate those types and instead manually merge those hand-written/hand-modified structs back in to the code afterwards. This saves you from having to manually remove all code that is generated regarding `Foo` first before merging in your own. 236 | 237 | This can also be useful when you have a spec that is either very awkward to use (so you hand-write or hand-modify after generation) in some type so you don't generate those types and instead manually merge those hand-written/hand-modified structs back in to the code afterwards. This saves you from having to manually remove all code that is generated regarding `Foo` first before merging in your own. 238 | 239 | This also works with generics e.g. you can refer to `foo`. As with other generics this will create a `pub type FooT = Foo;` definition in rust to work with wasm-bindgen's restrictions (no generics) as on the wasm side there will be references to a `FooT` in wasm. The wasm type definition is not emitted as that will be implementation-dependent. For an example see `extern_generic` in the `core` unit test. 240 | 241 | ## _CDDL_CODEGEN_RAW_BYTES_TYPE_ 242 | 243 | Allows encoding as `bytes` but imposing hand-written constraints defined elsewhere. 244 | ```cddl 245 | foo = _CDDL_CODEGEN_RAW_BYTES_TYPE_ 246 | bar = [ 247 | foo, 248 | ] 249 | ``` 250 | This will treat `foo` as some external type called `Foo`. This type must implement the exported (in `serialization.rs`) trait `RawBytesEncoding`. 251 | This can be useful for example when working with cryptographic primitives e.g. a hash or pubkey, as it allows users to have those crypto structs be from a crypto library then they only need to implement the trait for them and they will be able to be directly used without needing any useless generated wrapper struct for the in between. 252 | -------------------------------------------------------------------------------- /tests/extern-dep-crate/src/serialization.rs: -------------------------------------------------------------------------------- 1 | // same as cbor_event::de::Deserialize but with our DeserializeError 2 | pub trait Deserialize { 3 | fn from_cbor_bytes(data: &[u8]) -> Result 4 | where 5 | Self: Sized, 6 | { 7 | let mut raw = Deserializer::from(std::io::Cursor::new(data)); 8 | Self::deserialize(&mut raw) 9 | } 10 | 11 | fn deserialize(raw: &mut Deserializer) -> Result 12 | where 13 | Self: Sized; 14 | } 15 | 16 | impl Deserialize for T { 17 | fn deserialize(raw: &mut Deserializer) -> Result { 18 | T::deserialize(raw).map_err(DeserializeError::from) 19 | } 20 | } 21 | pub struct CBORReadLen { 22 | deser_len: cbor_event::LenSz, 23 | read: u64, 24 | } 25 | 26 | impl CBORReadLen { 27 | pub fn new(len: cbor_event::LenSz) -> Self { 28 | Self { 29 | deser_len: len, 30 | read: 0, 31 | } 32 | } 33 | 34 | pub fn read(&self) -> u64 { 35 | self.read 36 | } 37 | 38 | // Marks {n} values as being read, and if we go past the available definite length 39 | // given by the CBOR, we return an error. 40 | pub fn read_elems(&mut self, count: usize) -> Result<(), DeserializeFailure> { 41 | match self.deser_len { 42 | cbor_event::LenSz::Len(n, _) => { 43 | self.read += count as u64; 44 | if self.read > n { 45 | Err(DeserializeFailure::DefiniteLenMismatch(n, None)) 46 | } else { 47 | Ok(()) 48 | } 49 | } 50 | cbor_event::LenSz::Indefinite => Ok(()), 51 | } 52 | } 53 | 54 | pub fn finish(&self) -> Result<(), DeserializeFailure> { 55 | match self.deser_len { 56 | cbor_event::LenSz::Len(n, _) => { 57 | if self.read == n { 58 | Ok(()) 59 | } else { 60 | Err(DeserializeFailure::DefiniteLenMismatch(n, Some(self.read))) 61 | } 62 | } 63 | cbor_event::LenSz::Indefinite => Ok(()), 64 | } 65 | } 66 | } 67 | 68 | pub trait DeserializeEmbeddedGroup { 69 | fn deserialize_as_embedded_group( 70 | raw: &mut Deserializer, 71 | read_len: &mut CBORReadLen, 72 | len: cbor_event::LenSz, 73 | ) -> Result 74 | where 75 | Self: Sized; 76 | } 77 | 78 | #[inline] 79 | pub(crate) fn sz_max(sz: cbor_event::Sz) -> u64 { 80 | match sz { 81 | cbor_event::Sz::Inline => 23u64, 82 | cbor_event::Sz::One => u8::MAX as u64, 83 | cbor_event::Sz::Two => u16::MAX as u64, 84 | cbor_event::Sz::Four => u32::MAX as u64, 85 | cbor_event::Sz::Eight => u64::MAX, 86 | } 87 | } 88 | 89 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 90 | pub enum LenEncoding { 91 | Canonical, 92 | Definite(cbor_event::Sz), 93 | Indefinite, 94 | } 95 | 96 | impl Default for LenEncoding { 97 | fn default() -> Self { 98 | Self::Canonical 99 | } 100 | } 101 | 102 | impl From for LenEncoding { 103 | fn from(len_sz: cbor_event::LenSz) -> Self { 104 | match len_sz { 105 | cbor_event::LenSz::Len(len, sz) => { 106 | if cbor_event::Sz::canonical(len) == sz { 107 | Self::Canonical 108 | } else { 109 | Self::Definite(sz) 110 | } 111 | } 112 | cbor_event::LenSz::Indefinite => Self::Indefinite, 113 | } 114 | } 115 | } 116 | 117 | #[derive(Debug, PartialEq, Eq, Clone)] 118 | pub enum StringEncoding { 119 | Canonical, 120 | Indefinite(Vec<(u64, cbor_event::Sz)>), 121 | Definite(cbor_event::Sz), 122 | } 123 | 124 | impl Default for StringEncoding { 125 | fn default() -> Self { 126 | Self::Canonical 127 | } 128 | } 129 | 130 | impl From for StringEncoding { 131 | fn from(len_sz: cbor_event::StringLenSz) -> Self { 132 | match len_sz { 133 | cbor_event::StringLenSz::Len(sz) => Self::Definite(sz), 134 | cbor_event::StringLenSz::Indefinite(lens) => Self::Indefinite(lens), 135 | } 136 | } 137 | } 138 | #[inline] 139 | pub fn fit_sz(len: u64, sz: Option) -> cbor_event::Sz { 140 | match sz { 141 | Some(sz) => { 142 | if len <= sz_max(sz) { 143 | sz 144 | } else { 145 | cbor_event::Sz::canonical(len) 146 | } 147 | } 148 | None => cbor_event::Sz::canonical(len), 149 | } 150 | } 151 | 152 | impl LenEncoding { 153 | pub fn to_len_sz(&self, len: u64) -> cbor_event::LenSz { 154 | match self { 155 | Self::Canonical => cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)), 156 | Self::Definite(sz) => { 157 | if sz_max(*sz) >= len { 158 | cbor_event::LenSz::Len(len, *sz) 159 | } else { 160 | cbor_event::LenSz::Len(len, cbor_event::Sz::canonical(len)) 161 | } 162 | } 163 | Self::Indefinite => cbor_event::LenSz::Indefinite, 164 | } 165 | } 166 | 167 | pub fn end<'a, W: Write + Sized>( 168 | &self, 169 | serializer: &'a mut Serializer, 170 | ) -> cbor_event::Result<&'a mut Serializer> { 171 | if *self == Self::Indefinite { 172 | serializer.write_special(cbor_event::Special::Break)?; 173 | } 174 | Ok(serializer) 175 | } 176 | } 177 | 178 | impl StringEncoding { 179 | pub fn to_str_len_sz(&self, len: u64) -> cbor_event::StringLenSz { 180 | match self { 181 | Self::Canonical => cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)), 182 | Self::Definite(sz) => { 183 | if sz_max(*sz) >= len { 184 | cbor_event::StringLenSz::Len(*sz) 185 | } else { 186 | cbor_event::StringLenSz::Len(cbor_event::Sz::canonical(len)) 187 | } 188 | } 189 | Self::Indefinite(lens) => cbor_event::StringLenSz::Indefinite(lens.clone()), 190 | } 191 | } 192 | } 193 | pub trait SerializeEmbeddedGroup { 194 | fn serialize_as_embedded_group<'a, W: Write + Sized>( 195 | &self, 196 | serializer: &'a mut Serializer, 197 | ) -> cbor_event::Result<&'a mut Serializer>; 198 | } 199 | 200 | pub trait ToCBORBytes { 201 | fn to_cbor_bytes(&self) -> Vec; 202 | } 203 | 204 | impl ToCBORBytes for T { 205 | fn to_cbor_bytes(&self) -> Vec { 206 | let mut buf = Serializer::new_vec(); 207 | self.serialize(&mut buf).unwrap(); 208 | buf.finalize() 209 | } 210 | } 211 | 212 | // This file was code-generated using an experimental CDDL to rust tool: 213 | // https://github.com/dcSpark/cddl-codegen 214 | 215 | use super::cbor_encodings::*; 216 | use super::*; 217 | use crate::error::*; 218 | use cbor_event::de::Deserializer; 219 | use cbor_event::se::{Serialize, Serializer}; 220 | use std::io::{BufRead, Seek, SeekFrom, Write}; 221 | 222 | impl cbor_event::se::Serialize for ExternCrateFoo { 223 | fn serialize<'se, W: Write>( 224 | &self, 225 | serializer: &'se mut Serializer, 226 | ) -> cbor_event::Result<&'se mut Serializer> { 227 | serializer.write_tag_sz( 228 | 11u64, 229 | fit_sz( 230 | 11u64, 231 | self.encodings 232 | .as_ref() 233 | .map(|encs| encs.tag_encoding) 234 | .unwrap_or_default(), 235 | ), 236 | )?; 237 | serializer.write_array_sz( 238 | self.encodings 239 | .as_ref() 240 | .map(|encs| encs.len_encoding) 241 | .unwrap_or_default() 242 | .to_len_sz(3), 243 | )?; 244 | serializer.write_unsigned_integer_sz( 245 | self.index_0, 246 | fit_sz( 247 | self.index_0, 248 | self.encodings 249 | .as_ref() 250 | .map(|encs| encs.index_0_encoding) 251 | .unwrap_or_default(), 252 | ), 253 | )?; 254 | serializer.write_text_sz( 255 | &self.index_1, 256 | self.encodings 257 | .as_ref() 258 | .map(|encs| encs.index_1_encoding.clone()) 259 | .unwrap_or_default() 260 | .to_str_len_sz(self.index_1.len() as u64), 261 | )?; 262 | serializer.write_bytes_sz( 263 | &self.index_2, 264 | self.encodings 265 | .as_ref() 266 | .map(|encs| encs.index_2_encoding.clone()) 267 | .unwrap_or_default() 268 | .to_str_len_sz(self.index_2.len() as u64), 269 | )?; 270 | self.encodings 271 | .as_ref() 272 | .map(|encs| encs.len_encoding) 273 | .unwrap_or_default() 274 | .end(serializer) 275 | } 276 | } 277 | 278 | impl Deserialize for ExternCrateFoo { 279 | fn deserialize(raw: &mut Deserializer) -> Result { 280 | let (tag, tag_encoding) = raw.tag_sz()?; 281 | if tag != 11 { 282 | return Err(DeserializeError::new( 283 | "ExternCrateFoo", 284 | DeserializeFailure::TagMismatch { 285 | found: tag, 286 | expected: 11, 287 | }, 288 | )); 289 | } 290 | let len = raw.array_sz()?; 291 | let len_encoding: LenEncoding = len.into(); 292 | let mut read_len = CBORReadLen::new(len); 293 | read_len.read_elems(3)?; 294 | read_len.finish()?; 295 | (|| -> Result<_, DeserializeError> { 296 | let (index_0, index_0_encoding) = raw 297 | .unsigned_integer_sz() 298 | .map_err(Into::::into) 299 | .map(|(x, enc)| (x, Some(enc))) 300 | .map_err(|e: DeserializeError| e.annotate("index_0"))?; 301 | let (index_1, index_1_encoding) = raw 302 | .text_sz() 303 | .map_err(Into::::into) 304 | .map(|(s, enc)| (s, StringEncoding::from(enc))) 305 | .map_err(|e: DeserializeError| e.annotate("index_1"))?; 306 | let (index_2, index_2_encoding) = raw 307 | .bytes_sz() 308 | .map_err(Into::::into) 309 | .map(|(bytes, enc)| (bytes, StringEncoding::from(enc))) 310 | .map_err(|e: DeserializeError| e.annotate("index_2"))?; 311 | match len { 312 | cbor_event::LenSz::Len(_, _) => (), 313 | cbor_event::LenSz::Indefinite => match raw.special()? { 314 | cbor_event::Special::Break => (), 315 | _ => return Err(DeserializeFailure::EndingBreakMissing.into()), 316 | }, 317 | } 318 | Ok(ExternCrateFoo { 319 | index_0, 320 | index_1, 321 | index_2, 322 | encodings: Some(ExternCrateFooEncoding { 323 | len_encoding, 324 | tag_encoding: Some(tag_encoding), 325 | index_0_encoding, 326 | index_1_encoding, 327 | index_2_encoding, 328 | }), 329 | }) 330 | })() 331 | .map_err(|e| e.annotate("ExternCrateFoo")) 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | /// If you have multiple tests that use the same directory, please use different export_suffix 4 | /// for each one or else the tests will be flaky as they are run concurrently. 5 | fn run_test( 6 | dir: &str, 7 | options: &[&str], 8 | export_suffix: Option<&str>, 9 | external_rust_file_paths: &[std::path::PathBuf], 10 | external_wasm_file_paths: &[std::path::PathBuf], 11 | input_is_dir: bool, 12 | test_deps: &[&str], 13 | ) { 14 | use std::str::FromStr; 15 | let export_path = match export_suffix { 16 | Some(suffix) => format!("export_{suffix}"), 17 | None => "export".to_owned(), 18 | }; 19 | let test_path = std::path::PathBuf::from_str("tests").unwrap().join(dir); 20 | println!("--------- running test: {dir} ---------"); 21 | // build and run to generate code 22 | let mut cargo_run = std::process::Command::new("cargo"); 23 | cargo_run.arg("run").arg("--").arg(format!( 24 | "--output={}", 25 | test_path.join(&export_path).to_str().unwrap() 26 | )); 27 | if input_is_dir { 28 | cargo_run.arg(format!( 29 | "--input={}", 30 | test_path.join("inputs").to_str().unwrap() 31 | )); 32 | } else { 33 | cargo_run.arg(format!( 34 | "--input={}", 35 | test_path.join("input.cddl").to_str().unwrap() 36 | )); 37 | } 38 | for option in options { 39 | cargo_run.arg(option); 40 | } 41 | println!(" ------ building ------"); 42 | let cargo_run_result = cargo_run.output().unwrap(); 43 | if !cargo_run_result.status.success() { 44 | eprintln!("{}", String::from_utf8(cargo_run_result.stderr).unwrap()); 45 | } 46 | assert!(cargo_run_result.status.success()); 47 | // copy tests into generated code 48 | let mut lib_rs = std::fs::OpenOptions::new() 49 | .append(true) 50 | .open(test_path.join(format!("{export_path}/rust/src/lib.rs"))) 51 | .unwrap(); 52 | // some external files/tests pasted in might need this 53 | lib_rs 54 | .write_all("\nuse serialization::*;\n".as_bytes()) 55 | .unwrap(); 56 | // copy external files in too (if needed) too 57 | for external_rust_file_path in external_rust_file_paths { 58 | let extern_rs = std::fs::read_to_string(external_rust_file_path).unwrap(); 59 | lib_rs.write_all("\n\n".as_bytes()).unwrap(); 60 | lib_rs.write_all(extern_rs.as_bytes()).unwrap(); 61 | } 62 | let deser_test_rs = std::fs::read_to_string( 63 | std::path::PathBuf::from_str("tests") 64 | .unwrap() 65 | .join("deser_test"), 66 | ) 67 | .unwrap(); 68 | lib_rs.write_all("\n\n".as_bytes()).unwrap(); 69 | lib_rs.write_all(deser_test_rs.as_bytes()).unwrap(); 70 | let test_rs = std::fs::read_to_string(test_path.join("tests.rs")).unwrap(); 71 | lib_rs.write_all("\n\n".as_bytes()).unwrap(); 72 | lib_rs.write_all(test_rs.as_bytes()).unwrap(); 73 | std::mem::drop(lib_rs); 74 | // add extra deps used within tests 75 | if !test_deps.is_empty() { 76 | let mut cargo_toml = std::fs::OpenOptions::new() 77 | .append(true) 78 | .open(test_path.join(format!("{export_path}/rust/Cargo.toml"))) 79 | .unwrap(); 80 | for dep in test_deps { 81 | cargo_toml.write_all(dep.as_bytes()).unwrap(); 82 | } 83 | // copy test deps to wasm too in case they're used (e.g. extern deps dir crates) 84 | if let Ok(mut cargo_toml_wasm) = std::fs::OpenOptions::new() 85 | .append(true) 86 | .open(test_path.join(format!("{export_path}/wasm/Cargo.toml"))) 87 | { 88 | for dep in test_deps { 89 | cargo_toml_wasm.write_all(dep.as_bytes()).unwrap(); 90 | } 91 | } 92 | } 93 | // run tests in generated code 94 | println!(" ------ testing ------"); 95 | let cargo_test = std::process::Command::new("cargo") 96 | .arg("test") 97 | .current_dir(test_path.join(format!("{export_path}/rust"))) 98 | .output() 99 | .unwrap(); 100 | if !cargo_test.status.success() { 101 | eprintln!( 102 | "test stderr:\n{}", 103 | String::from_utf8(cargo_test.stderr).unwrap() 104 | ); 105 | } 106 | println!( 107 | "test stdout:\n{}", 108 | String::from_utf8(cargo_test.stdout).unwrap() 109 | ); 110 | assert!(cargo_test.status.success()); 111 | 112 | // wasm 113 | let wasm_export_dir = test_path.join(format!("{export_path}/wasm")); 114 | let wasm_test_dir = test_path.join("tests_wasm.rs"); 115 | // copy external wasm defs if they exist 116 | for external_wasm_file_path in external_wasm_file_paths { 117 | println!("trying to open: {external_wasm_file_path:?}"); 118 | let mut wasm_lib_rs = std::fs::OpenOptions::new() 119 | .append(true) 120 | .open(test_path.join(format!("{export_path}/wasm/src/lib.rs"))) 121 | .unwrap(); 122 | let extern_rs = std::fs::read_to_string(external_wasm_file_path).unwrap(); 123 | wasm_lib_rs.write_all("\n\n".as_bytes()).unwrap(); 124 | // we must replace the lib name if it's not the default 125 | if let Some(custom_lib_name) = options.iter().find_map(|arg: &&str| { 126 | arg.split_once("--lib-name=") 127 | .map(|(_, lib_name)| lib_name.replace('-', "_")) 128 | }) { 129 | let replaced_extern_rs = extern_rs.replace("cddl_lib", &custom_lib_name); 130 | wasm_lib_rs 131 | .write_all(replaced_extern_rs.as_bytes()) 132 | .unwrap(); 133 | } else { 134 | wasm_lib_rs.write_all(extern_rs.as_bytes()).unwrap(); 135 | } 136 | } 137 | if wasm_test_dir.exists() { 138 | println!(" ------ testing (wasm) ------"); 139 | let cargo_test_wasm = std::process::Command::new("cargo") 140 | .arg("test") 141 | .current_dir(wasm_export_dir) 142 | .output() 143 | .unwrap(); 144 | if !cargo_test_wasm.status.success() { 145 | eprintln!( 146 | "test stderr:\n{}", 147 | String::from_utf8(cargo_test_wasm.stderr).unwrap() 148 | ); 149 | } 150 | println!( 151 | "test stdout:\n{}", 152 | String::from_utf8(cargo_test_wasm.stdout).unwrap() 153 | ); 154 | assert!(cargo_test_wasm.status.success()); 155 | } else if wasm_export_dir.exists() { 156 | let cargo_build_wasm = std::process::Command::new("cargo") 157 | .arg("build") 158 | .current_dir(wasm_export_dir) 159 | .output() 160 | .unwrap(); 161 | if !cargo_build_wasm.status.success() { 162 | eprintln!( 163 | "wasm build stderr:\n{}", 164 | String::from_utf8(cargo_build_wasm.stderr).unwrap() 165 | ); 166 | } 167 | assert!(cargo_build_wasm.status.success()); 168 | } 169 | // check that the JSON schema export crate builds 170 | let json_export_dir = test_path.join(format!("{export_path}/wasm/json-gen")); 171 | if json_export_dir.exists() { 172 | let cargo_build_json = std::process::Command::new("cargo") 173 | .arg("build") 174 | .current_dir(json_export_dir) 175 | .output() 176 | .unwrap(); 177 | if !cargo_build_json.status.success() { 178 | eprintln!( 179 | "wasm build stderr:\n{}", 180 | String::from_utf8(cargo_build_json.stderr).unwrap() 181 | ); 182 | } 183 | assert!(cargo_build_json.status.success()); 184 | } 185 | } 186 | 187 | #[test] 188 | fn core_with_wasm() { 189 | use std::str::FromStr; 190 | let extern_rust_path = std::path::PathBuf::from_str("tests") 191 | .unwrap() 192 | .join("external_rust_defs"); 193 | let extern_wasm_path = std::path::PathBuf::from_str("tests") 194 | .unwrap() 195 | .join("external_wasm_defs"); 196 | let custom_ser_path = std::path::PathBuf::from_str("tests") 197 | .unwrap() 198 | .join("custom_serialization"); 199 | run_test( 200 | "core", 201 | &[], 202 | Some("wasm"), 203 | &[extern_rust_path, custom_ser_path], 204 | &[extern_wasm_path], 205 | false, 206 | &[], 207 | ); 208 | } 209 | 210 | #[test] 211 | fn core_no_wasm() { 212 | use std::str::FromStr; 213 | let extern_rust_path = std::path::PathBuf::from_str("tests") 214 | .unwrap() 215 | .join("external_rust_defs"); 216 | let custom_ser_path = std::path::PathBuf::from_str("tests") 217 | .unwrap() 218 | .join("custom_serialization"); 219 | run_test( 220 | "core", 221 | &["--wasm=false"], 222 | None, 223 | &[extern_rust_path, custom_ser_path], 224 | &[], 225 | false, 226 | &[], 227 | ); 228 | } 229 | 230 | #[test] 231 | fn comment_dsl() { 232 | run_test( 233 | "comment-dsl", 234 | &["--preserve-encodings=true"], 235 | None, 236 | &[], 237 | &[], 238 | false, 239 | &[], 240 | ); 241 | } 242 | 243 | #[test] 244 | fn preserve_encodings() { 245 | use std::str::FromStr; 246 | let custom_ser_path = std::path::PathBuf::from_str("tests") 247 | .unwrap() 248 | .join("custom_serialization_preserve"); 249 | run_test( 250 | "preserve-encodings", 251 | &["--preserve-encodings=true"], 252 | None, 253 | &[custom_ser_path], 254 | &[], 255 | false, 256 | &[], 257 | ); 258 | } 259 | 260 | #[test] 261 | fn canonical() { 262 | run_test( 263 | "canonical", 264 | &["--preserve-encodings=true", "--canonical-form=true"], 265 | None, 266 | &[], 267 | &[], 268 | false, 269 | &[], 270 | ); 271 | } 272 | 273 | #[test] 274 | fn rust_wasm_split() { 275 | run_test("rust-wasm-split", &[], None, &[], &[], false, &[]); 276 | } 277 | 278 | #[test] 279 | fn multifile() { 280 | use std::str::FromStr; 281 | let extern_rust_path = std::path::PathBuf::from_str("tests") 282 | .unwrap() 283 | .join("external_rust_defs"); 284 | let extern_wasm_path = std::path::PathBuf::from_str("tests") 285 | .unwrap() 286 | .join("external_wasm_defs"); 287 | // this tests without preserve-encodings as that can affect imports 288 | run_test( 289 | "multifile", 290 | &[], 291 | None, 292 | &[extern_rust_path], 293 | &[extern_wasm_path], 294 | true, 295 | &["hex = \"0.4.3\""], 296 | ); 297 | } 298 | 299 | #[test] 300 | fn multifile_json_preserve() { 301 | use std::str::FromStr; 302 | let extern_rust_path = std::path::PathBuf::from_str("tests") 303 | .unwrap() 304 | .join("external_rust_defs_compiles_with_json_preserve"); 305 | let extern_wasm_path = std::path::PathBuf::from_str("tests") 306 | .unwrap() 307 | .join("external_wasm_defs"); 308 | // json-schema-export / preserve-encodings to ensure that imports/scoping works in both: 309 | // 1) cbor_encodings.rs 310 | // 2) json-gen schema export crate 311 | run_test( 312 | "multifile", 313 | &[ 314 | "--lib-name=multi-chain-test", 315 | "--preserve-encodings=true", 316 | "--json-serde-derives=true", 317 | "--json-schema-export=true", 318 | ], 319 | Some("json_preserve"), 320 | &[extern_rust_path], 321 | &[extern_wasm_path], 322 | true, 323 | &[], 324 | ); 325 | } 326 | 327 | #[test] 328 | fn raw_bytes() { 329 | use std::str::FromStr; 330 | let extern_rust_path = std::path::PathBuf::from_str("tests") 331 | .unwrap() 332 | .join("external_rust_raw_bytes_def"); 333 | let extern_wasm_path = std::path::PathBuf::from_str("tests") 334 | .unwrap() 335 | .join("external_wasm_raw_bytes_def"); 336 | run_test( 337 | "raw-bytes", 338 | &[], 339 | None, 340 | &[extern_rust_path], 341 | &[extern_wasm_path], 342 | false, 343 | &[], 344 | ); 345 | } 346 | 347 | #[test] 348 | fn raw_bytes_preserve() { 349 | use std::str::FromStr; 350 | let extern_rust_path = std::path::PathBuf::from_str("tests") 351 | .unwrap() 352 | .join("external_rust_raw_bytes_def"); 353 | let extern_wasm_path = std::path::PathBuf::from_str("tests") 354 | .unwrap() 355 | .join("external_wasm_raw_bytes_def"); 356 | run_test( 357 | "raw-bytes-preserve", 358 | &["--preserve-encodings=true"], 359 | None, 360 | &[extern_rust_path], 361 | &[extern_wasm_path], 362 | false, 363 | &[], 364 | ); 365 | } 366 | 367 | #[test] 368 | fn json() { 369 | use std::str::FromStr; 370 | let extern_rust_path = std::path::PathBuf::from_str("tests") 371 | .unwrap() 372 | .join("external_json_impls"); 373 | run_test( 374 | "json", 375 | &["--json-serde-derives=true", "--json-schema-export=true"], 376 | None, 377 | &[extern_rust_path], 378 | &[], 379 | false, 380 | &[], 381 | ); 382 | } 383 | 384 | #[test] 385 | fn json_preserve() { 386 | use std::str::FromStr; 387 | let extern_rust_path = std::path::PathBuf::from_str("tests") 388 | .unwrap() 389 | .join("external_json_impls"); 390 | run_test( 391 | "json", 392 | &[ 393 | "--preserve-encodings=true", 394 | "--json-serde-derives=true", 395 | "--json-schema-export=true", 396 | ], 397 | Some("preserve"), 398 | &[extern_rust_path], 399 | &[], 400 | false, 401 | &[], 402 | ); 403 | } 404 | 405 | #[test] 406 | fn extern_deps() { 407 | run_test( 408 | "extern-deps", 409 | &[ 410 | "--preserve-encodings=true", 411 | "--common-import-override=extern_dep_crate", 412 | ], 413 | None, 414 | &[], 415 | &[], 416 | true, 417 | &["extern-dep-crate = { path = \"../../../extern-dep-crate\" }"], 418 | ); 419 | } 420 | --------------------------------------------------------------------------------