diff --git a/docs/docs/comment_dsl.mdx b/docs/docs/comment_dsl.mdx index 7d426a1..e5c3932 100644 --- a/docs/docs/comment_dsl.mdx +++ b/docs/docs/comment_dsl.mdx @@ -93,4 +93,19 @@ bar = [ ] ``` 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()`. -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. \ No newline at end of file +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. + +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_ + +Allows encoding as `bytes` but imposing hand-written constraints defined elsewhere. +```cddl +foo = _CDDL_CODEGEN_RAW_BYTES_TYPE_ +bar = [ + foo, +] +``` +This will treat `foo` as some external type called `Foo`. This type must implement the exported (in `serialization.rs`) trait `RawBytesEncoding`. +This can be useful for example when working with cryptographic primtivies 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. \ No newline at end of file diff --git a/src/generation.rs b/src/generation.rs index f037634..d7f5730 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -991,7 +991,12 @@ impl GenerationScope { /// Exports all already-generated state to the provided directory. /// Call generate() first to populate the generation state. - pub fn export(&self, types: &IntermediateTypes, cli: &Cli) -> std::io::Result<()> { + pub fn export( + &self, + types: &IntermediateTypes, + export_raw_bytes_encoding_trait: bool, + cli: &Cli, + ) -> std::io::Result<()> { // package.json / scripts let rust_dir = if cli.package_json { if cli.json_schema_export { @@ -1076,6 +1081,10 @@ impl GenerationScope { 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")); + } 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); @@ -1118,6 +1127,9 @@ impl GenerationScope { if cli.json_schema_export { rust_cargo_toml.push_str("schemars = \"0.8.8\"\n"); } + if export_raw_bytes_encoding_trait { + rust_cargo_toml.push_str("hex = \"0.4.3\"\n"); + } if cli.wasm && types .rust_structs() diff --git a/src/main.rs b/src/main.rs index 137aa7d..ad58865 100644 --- a/src/main.rs +++ b/src/main.rs @@ -93,6 +93,7 @@ fn main() -> Result<(), Box> { }) }) .collect::>()?; + let export_raw_bytes_encoding_trait = input_files_content.contains(parsing::RAW_BYTES_MARKER); // we also need to mark the extern marker to a placeholder struct that won't get codegened input_files_content.push_str(&format!("{} = [0]", parsing::EXTERN_MARKER)); // and a raw bytes one too @@ -149,7 +150,7 @@ fn main() -> Result<(), Box> { println!("\n-----------------------------------------\n- Generating code...\n------------------------------------"); let mut gen_scope = GenerationScope::new(); gen_scope.generate(&types, &CLI_ARGS); - gen_scope.export(&types, &CLI_ARGS)?; + gen_scope.export(&types, export_raw_bytes_encoding_trait, &CLI_ARGS)?; types.print_info(); gen_scope.print_structs_without_deserialize(); diff --git a/static/error.rs b/static/error.rs index 12aa6a0..74d64c4 100644 --- a/static/error.rs +++ b/static/error.rs @@ -70,6 +70,8 @@ impl DeserializeError { } } +impl std::error::Error for DeserializeError {} + impl std::fmt::Display for DeserializeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.location { diff --git a/static/raw_bytes_encoding.rs b/static/raw_bytes_encoding.rs new file mode 100644 index 0000000..749238a --- /dev/null +++ b/static/raw_bytes_encoding.rs @@ -0,0 +1,20 @@ +pub trait RawBytesEncoding { + fn to_raw_bytes(&self) -> &[u8]; + + fn from_raw_bytes(bytes: &[u8]) -> Result + where + Self: Sized; + + fn to_raw_hex(&self) -> String { + hex::encode(self.to_raw_bytes()) + } + + fn from_raw_hex(hex_str: &str) -> Result + where + Self: Sized, + { + let bytes = hex::decode(hex_str) + .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)))?; + Self::from_raw_bytes(bytes.as_ref()) + } +} diff --git a/static/serialization_non_force_canonical.rs b/static/serialization_non_force_canonical.rs index 391643a..ac8e899 100644 --- a/static/serialization_non_force_canonical.rs +++ b/static/serialization_non_force_canonical.rs @@ -15,4 +15,4 @@ impl ToCBORBytes for T { self.serialize(&mut buf).unwrap(); buf.finalize() } -} \ No newline at end of file +} diff --git a/static/serialization_preserve.rs b/static/serialization_preserve.rs index a0fa36e..c90a77a 100644 --- a/static/serialization_preserve.rs +++ b/static/serialization_preserve.rs @@ -106,4 +106,4 @@ impl From for StringEncoding { cbor_event::StringLenSz::Indefinite(lens) => Self::Indefinite(lens), } } -} \ No newline at end of file +} diff --git a/tests/external_rust_raw_bytes_def b/tests/external_rust_raw_bytes_def index 965ec13..ef5dad6 100644 --- a/tests/external_rust_raw_bytes_def +++ b/tests/external_rust_raw_bytes_def @@ -1,23 +1,4 @@ -pub trait RawBytesEncoding { - fn to_raw_bytes(&self) -> &[u8]; - - fn from_raw_bytes(bytes: &[u8]) -> Result - where - Self: Sized; -} - -#[derive(Debug)] -pub enum CryptoError { - WrongSize, -} - -impl std::fmt::Display for CryptoError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "CryptoError::WrongSize") - } -} - -impl std::error::Error for CryptoError {} +use crate::RawBytesEncoding; #[derive(Clone, Debug)] pub struct PubKey([u8; 32]); @@ -27,8 +8,11 @@ impl RawBytesEncoding for PubKey { &self.0 } - fn from_raw_bytes(bytes: &[u8]) -> Result { + fn from_raw_bytes(bytes: &[u8]) -> Result { use std::convert::TryInto; - bytes.try_into().map(PubKey).or(Err(CryptoError::WrongSize)) + bytes + .try_into() + .map(PubKey) + .map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)).into()) } }