Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document _CDDL_CODEGEN_RAW_BYTES_TYPE_ + trait export #192

Merged
merged 5 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub struct Bar {

#### _CDDL_CODEGEN_EXTERN_TYPE_

While not as a comment, this allows you to compose in hand-written structs into a cddl spec.
This allows you to compose in hand-written structs into a cddl spec.
```cddl
foo = _CDDL_CODEGEN_EXTERN_TYPE_
bar = [
Expand All @@ -177,4 +177,16 @@ 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.
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change it so that we're modifying https://github.com/dcSpark/cddl-codegen/tree/master/docs instead

The master READMe for this is deprecated


#### _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.
14 changes: 13 additions & 1 deletion src/generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,7 +938,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 {
Expand Down Expand Up @@ -1020,6 +1025,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);
Expand Down Expand Up @@ -1059,6 +1068,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()
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
})
})
.collect::<Result<String, _>>()?;
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
Expand Down Expand Up @@ -126,7 +127,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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();
Expand Down
2 changes: 2 additions & 0 deletions static/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions static/raw_bytes_encoding.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
pub trait RawBytesEncoding {
fn to_raw_bytes(&self) -> &[u8];

fn from_raw_bytes(bytes: &[u8]) -> Result<Self, DeserializeError>
where
Self: Sized;

fn to_raw_hex(&self) -> String {
hex::encode(self.to_raw_bytes())
}

fn from_raw_hex(hex_str: &str) -> Result<Self, DeserializeError>
where
Self: Sized,
{
let bytes = hex::decode(hex_str)
.map_err(|e| DeserializeFailure::InvalidStructure(Box::new(e)))?;
Self::from_raw_bytes(bytes.as_ref())
}
}
2 changes: 1 addition & 1 deletion static/serialization_non_force_canonical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ impl<T: cbor_event::se::Serialize> ToCBORBytes for T {
self.serialize(&mut buf).unwrap();
buf.finalize()
}
}
}
2 changes: 1 addition & 1 deletion static/serialization_preserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@ impl From<cbor_event::StringLenSz> for StringEncoding {
cbor_event::StringLenSz::Indefinite(lens) => Self::Indefinite(lens),
}
}
}
}
28 changes: 6 additions & 22 deletions tests/external_rust_raw_bytes_def
Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
pub trait RawBytesEncoding {
fn to_raw_bytes(&self) -> &[u8];

fn from_raw_bytes(bytes: &[u8]) -> Result<Self, CryptoError>
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]);
Expand All @@ -27,8 +8,11 @@ impl RawBytesEncoding for PubKey {
&self.0
}

fn from_raw_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
fn from_raw_bytes(bytes: &[u8]) -> Result<Self, DeserializeError> {
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())
}
}