From 5e96387fc5d39a1ae99bae3b232c5a053f3f442d Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Wed, 23 Aug 2023 11:43:47 -0400 Subject: [PATCH] Docs: Integrating with other cddl-codegen gen'd libs (#208) * Docs: Integrating with other cddl-codegen gen'd libs New section with tips for generating a library that will depend on another cddl-codegen'd library e.g. CML. Also fixes minor isues in other parts of the docs. * avoid exporting static traits when common dir is overridden * don't export mod decls with common-import-override * fix for common export overrides from wasm --- docs/docs/command_line_flags.mdx | 2 +- docs/docs/comment_dsl.mdx | 2 +- docs/docs/integration-other.mdx | 32 +++++++ src/cli.rs | 24 ++++-- src/generation.rs | 138 ++++++++++++++++--------------- 5 files changed, 122 insertions(+), 76 deletions(-) create mode 100644 docs/docs/integration-other.mdx diff --git a/docs/docs/command_line_flags.mdx b/docs/docs/command_line_flags.mdx index 8c3a228..b2c5057 100644 --- a/docs/docs/command_line_flags.mdx +++ b/docs/docs/command_line_flags.mdx @@ -149,7 +149,7 @@ cddl-codegen --input=example --output=export --common-import-override=cml_core

-:::info `--package-json` +:::info `--wasm-cbor-json-api-macro` 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. The external macro is assumed to exist at the specified path and will be imported if there are module prefixes. diff --git a/docs/docs/comment_dsl.mdx b/docs/docs/comment_dsl.mdx index e5c3932..7a79d69 100644 --- a/docs/docs/comment_dsl.mdx +++ b/docs/docs/comment_dsl.mdx @@ -98,7 +98,7 @@ This can also be useful when you have a spec that is either very awkward to use 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. -#### _CDDL_CODEGEN_RAW_BYTES_TYPE_ +## _CDDL_CODEGEN_RAW_BYTES_TYPE_ Allows encoding as `bytes` but imposing hand-written constraints defined elsewhere. ```cddl diff --git a/docs/docs/integration-other.mdx b/docs/docs/integration-other.mdx new file mode 100644 index 0000000..d8efe87 --- /dev/null +++ b/docs/docs/integration-other.mdx @@ -0,0 +1,32 @@ +--- +sidebar_position: 7 +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +# Integration with other cddl-codegen libraries + +This guide is written in general for integrating with other libraries generated by cddl-codegen, but in particular references CML (cardano-multiplatform-lib) for examples. Most things referencing CML will be relevant to other common cddl-codegen generated libraries used as dependencies. + +## Common cddl-codegen traits + +When generating a library that has as a dependency another cddl-codegen-generated library you can share the common cddl-codegen types/traits like `Deserialize`, `RawBytesEncoding`, etc. Remember to pass in `--common-import-override` tag. For CML we pass in `--common-import-override=cml_core`. This is where all the common cddl-codegen traits are located so we can avoid having duplicate incompatible traits in other libraries. + +## CML macros + +In CML we have macros for implementing WASM conversions and JSON/bytes. We pass in `--wasm-cbor-json-api-macro=cml_core_wasm::impl_wasm_cbor_json_api` and `--wasm-conversions-macro=cml_core_wasm::impl_wasm_conversions` which are both located in `cml_core_wasm`. This drastically reduces WASM wrapper boilerplate. + +## Externally defined types + +### `_CDDL_CODEGEN_EXTERN_TYPE_` vs `_CDDL_CODEGEN_RAW_BYTES_TYPE_` + +There are two ways to have explicitly externally-defined types in cddl-codegen: `_CDDL_CODEGEN_EXTERN_TYPE_` and `_CDDL_CODEGEN_RAW_BYTES_TYPE_`. It is important to choose the appropriate one. If the type was defined originally as `_CDDL_CODEGEN_RAW_BYTES_TYPE_` in CML (or whatever library) then it is important to define it using this so it will be encoded correctly. If the type was either defined using `_CDDL_CODEGEN_EXTERN_TYPE_` (hand-written) or was explicitly defined normally in the dependency lib (e.g. CML) then use `_CDDL_CODEGEN_EXTERN_TYPE_`. + +### Import pathing + +In order to make imports easier it's recommended to make a directory corresponding to the dependency and put the `_CDDL_CODEGEN_RAW_BYTES_TYPE_` and `_CDDL_CODEGEN_EXTERN_TYPE_` external types inside of there and then later delete the output directories containing those modules. For an example see the `cml_chain` directory inside of the [`specs/multiera`](https://github.com/dcSpark/cardano-multiplatform-lib/tree/develop/specs/multiera). + +### Non-black-box types + +Another important detail, demonstrated in the above `multiera` CDDL spec, is that when using external types that aren't 100% self-contained (i.e. can't be treated as a black box that implements `Serialize` + `Deserialize`, nor as CBOR bytes implementing `RawBytesEncoding`) like `uint` aliases should be explicitly defined and then removed afterwards. Using the above directory/pathing tip makes this trivial to remove after. diff --git a/src/cli.rs b/src/cli.rs index 39ef23d..2bdd813 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -67,13 +67,8 @@ pub struct Cli { /// Location override for default common types (error, serialization, etc) /// This is useful for integrating into an exisitng project that is based on /// these types. - #[clap( - long, - value_parser, - value_name = "COMMON_IMPORT_OVERRIDE", - default_value = "crate" - )] - pub common_import_override: String, + #[clap(long, value_parser, value_name = "COMMON_IMPORT_OVERRIDE")] + common_import_override: Option, /// An external macro to be called instead of manually emitting functions for /// conversions to/from CBOR bytes or JSON. @@ -97,4 +92,19 @@ impl Cli { pub fn lib_name_code(&self) -> String { self.lib_name.replace('-', "_") } + + /// If someone override the common imports, we don't want to export them + pub fn export_static_files(&self) -> bool { + self.common_import_override.is_none() + } + + pub fn common_import_rust(&self) -> &str { + self.common_import_override.as_deref().unwrap_or("crate") + } + + pub fn common_import_wasm(&self) -> String { + self.common_import_override + .clone() + .unwrap_or_else(|| self.lib_name_code()) + } } diff --git a/src/generation.rs b/src/generation.rs index 50959a4..54adf1e 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -747,11 +747,14 @@ impl GenerationScope { } // declare modules (root lib specific) - self.rust_lib().raw("pub mod error;"); + if cli.export_static_files() { + self.rust_lib().raw("pub mod error;"); + if cli.preserve_encodings { + self.rust_lib().raw("pub mod ordered_hash_map;"); + } + } if cli.preserve_encodings { - self.rust_lib() - .raw("pub mod ordered_hash_map;") - .raw("extern crate derivative;"); + self.rust_lib().raw("extern crate derivative;"); } let scope_names = self .rust_scopes @@ -780,17 +783,17 @@ impl GenerationScope { // needed if there's any params that can fail content .push_import("std::convert", "TryFrom", None) - .push_import(format!("{}::error", cli.common_import_override), "*", None); + .push_import(format!("{}::error", cli.common_import_rust()), "*", None); // in case we store these in enums we're just going to dump them in everywhere if cli.preserve_encodings { content .push_import( - format!("{}::serialization", cli.common_import_override), + format!("{}::serialization", cli.common_import_rust()), "LenEncoding", None, ) .push_import( - format!("{}::serialization", cli.common_import_override), + format!("{}::serialization", cli.common_import_rust()), "StringEncoding", None, ); @@ -804,12 +807,12 @@ impl GenerationScope { content .push_import("std::collections", "BTreeMap", None) .push_import( - format!("{}::serialization", cli.common_import_override), + format!("{}::serialization", cli.common_import_rust()), "LenEncoding", None, ) .push_import( - format!("{}::serialization", cli.common_import_override), + format!("{}::serialization", cli.common_import_rust()), "StringEncoding", None, ); @@ -882,15 +885,11 @@ impl GenerationScope { // Issue (general - not just here): https://github.com/dcSpark/cddl-codegen/issues/139 content.push_import("std::collections", "BTreeMap", None); if cli.preserve_encodings { - if *scope == *ROOT_SCOPE { - content.push_import("ordered_hash_map", "OrderedHashMap", None); - } else { - content.push_import( - format!("{}::ordered_hash_map", cli.common_import_override), - "OrderedHashMap", - None, - ); - } + content.push_import( + format!("{}::ordered_hash_map", cli.common_import_rust()), + "OrderedHashMap", + None, + ); } } @@ -905,7 +904,7 @@ impl GenerationScope { .push_import("std::io", "Write", None) .push_import("cbor_event::de", "Deserializer", None) .push_import("cbor_event::se", "Serializer", None) - .push_import(format!("{}::error", cli.common_import_override), "*", None); + .push_import(format!("{}::error", cli.common_import_rust()), "*", None); if cli.preserve_encodings { content.push_import("super::cbor_encodings", "*", None); } @@ -914,7 +913,7 @@ impl GenerationScope { } if *scope != *ROOT_SCOPE { content.push_import( - format!("{}::serialization", cli.common_import_override), + format!("{}::serialization", cli.common_import_rust()), "*", None, ); @@ -964,7 +963,7 @@ impl GenerationScope { .push_import("wasm_bindgen::prelude", "JsValue", None); if cli.preserve_encodings { content.push_import( - format!("{}::ordered_hash_map", cli.lib_name_code()), + format!("{}::ordered_hash_map", cli.common_import_wasm()), "OrderedHashMap", None, ); @@ -1071,31 +1070,34 @@ impl GenerationScope { )?; // serialiation.rs / {module}/serialization.rs files (if input is a directory) - let mut serialize_paths = vec![cli.static_dir.join("serialization.rs")]; - if cli.preserve_encodings { - serialize_paths.push(cli.static_dir.join("serialization_preserve.rs")); - if cli.canonical_form { - serialize_paths.push( - cli.static_dir - .join("serialization_preserve_force_canonical.rs"), - ); + let mut merged_rust_serialize_scope = codegen::Scope::new(); + if cli.export_static_files() { + let mut serialize_paths = vec![cli.static_dir.join("serialization.rs")]; + if cli.preserve_encodings { + serialize_paths.push(cli.static_dir.join("serialization_preserve.rs")); + if cli.canonical_form { + serialize_paths.push( + cli.static_dir + .join("serialization_preserve_force_canonical.rs"), + ); + } else { + serialize_paths.push( + cli.static_dir + .join("serialization_preserve_non_force_canonical.rs"), + ); + serialize_paths + .push(cli.static_dir.join("serialization_non_force_canonical.rs")); + } } else { - serialize_paths.push( - cli.static_dir - .join("serialization_preserve_non_force_canonical.rs"), - ); + serialize_paths.push(cli.static_dir.join("serialization_non_preserve.rs")); serialize_paths.push(cli.static_dir.join("serialization_non_force_canonical.rs")); } - } else { - serialize_paths.push(cli.static_dir.join("serialization_non_preserve.rs")); - serialize_paths.push(cli.static_dir.join("serialization_non_force_canonical.rs")); - } - // raw_bytes_encoding in serialization too - if export_raw_bytes_encoding_trait { - serialize_paths.push(cli.static_dir.join("raw_bytes_encoding.rs")); + // raw_bytes_encoding in serialization too + if export_raw_bytes_encoding_trait { + serialize_paths.push(cli.static_dir.join("raw_bytes_encoding.rs")); + } + merged_rust_serialize_scope.raw(concat_files(&serialize_paths)?); } - let mut merged_rust_serialize_scope = codegen::Scope::new(); - merged_rust_serialize_scope.raw(concat_files(&serialize_paths)?); merged_rust_serialize_scope.append(&self.rust_serialize_lib_scope); merge_scopes_and_export( rust_dir.join("rust/src"), @@ -1155,30 +1157,32 @@ impl GenerationScope { rust_cargo_toml.replace("cddl-lib", &cli.lib_name), )?; - // error.rs - std::fs::copy( - cli.static_dir.join("error.rs"), - rust_dir.join("rust/src/error.rs"), - )?; + if cli.export_static_files() { + // error.rs + std::fs::copy( + cli.static_dir.join("error.rs"), + rust_dir.join("rust/src/error.rs"), + )?; - // ordered_hash_map.rs - if cli.preserve_encodings { - let mut ordered_hash_map_rs = - std::fs::read_to_string(cli.static_dir.join("ordered_hash_map.rs"))?; - if cli.json_serde_derives { - ordered_hash_map_rs.push_str(&std::fs::read_to_string( - cli.static_dir.join("ordered_hash_map_json.rs"), - )?); - } - if cli.json_schema_export { - ordered_hash_map_rs.push_str(&std::fs::read_to_string( - cli.static_dir.join("ordered_hash_map_schemars.rs"), - )?); + // ordered_hash_map.rs + if cli.preserve_encodings { + let mut ordered_hash_map_rs = + std::fs::read_to_string(cli.static_dir.join("ordered_hash_map.rs"))?; + if cli.json_serde_derives { + ordered_hash_map_rs.push_str(&std::fs::read_to_string( + cli.static_dir.join("ordered_hash_map_json.rs"), + )?); + } + if cli.json_schema_export { + ordered_hash_map_rs.push_str(&std::fs::read_to_string( + cli.static_dir.join("ordered_hash_map_schemars.rs"), + )?); + } + std::fs::write( + rust_dir.join("rust/src/ordered_hash_map.rs"), + rustfmt_generated_string(&ordered_hash_map_rs)?.as_ref(), + )?; } - std::fs::write( - rust_dir.join("rust/src/ordered_hash_map.rs"), - rustfmt_generated_string(&ordered_hash_map_rs)?.as_ref(), - )?; } // wasm crate @@ -3415,7 +3419,7 @@ fn create_base_wasm_struct<'a>( if cli.preserve_encodings && cli.canonical_form { to_bytes.line(format!( "{}::serialization::Serialize::to_cbor_bytes(&self.0)", - cli.lib_name_code() + cli.common_import_wasm() )); let mut to_canonical_bytes = codegen::Function::new("to_canonical_cbor_bytes"); @@ -3427,7 +3431,7 @@ fn create_base_wasm_struct<'a>( } else { to_bytes.line(format!( "{}::serialization::ToCBORBytes::to_cbor_bytes(&self.0)", - cli.lib_name_code() + cli.common_import_wasm() )); } s_impl.push_fn(to_bytes); @@ -3439,7 +3443,7 @@ fn create_base_wasm_struct<'a>( .vis("pub") .line(format!( "{}::serialization::Deserialize::from_cbor_bytes(cbor_bytes).map(Self).map_err(|e| JsValue::from_str(&format!(\"from_bytes: {{}}\", e)))", - cli.lib_name_code())); + cli.common_import_wasm())); } } if cli.json_serde_derives {