From a7e9548394105764e997e3cd5e2a3227b1b4ad9a Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 08:58:03 -0800 Subject: [PATCH 1/7] Wrapper JSON Overhaul Wrapper newtypes will generate custom implementations that will defer to the inner type's JSON traits instead of deriving to get them. This gives us much nicer JSON implementations allowing directly `T` instead of `{ inner: T }`. Bytes newtypes will have a specialization that serializes to hex bytestring instead of the `[number]` array that would otherwise be used. Introduces `@custom_json` comment DSL for newtypes that tell cddl-codegen to avoid generating/deriving any JSON traits under the assumption that some custom trait impls will be provided post-generation by the user. --- .gitignore | 1 + docs/docs/comment_dsl.mdx | 8 ++ src/comment_ast.rs | 44 ++++++- src/generation.rs | 240 ++++++++++++++++++++++++++++++++------ src/intermediate.rs | 3 + src/parsing.rs | 2 + src/test.rs | 60 +++++++++- static/Cargo_rust.toml | 1 + 8 files changed, 323 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 596ac7e..28a6a61 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ book/ # Test files tests/*/export/** tests/*/export_wasm/** +tests/*/export_preserve/** diff --git a/docs/docs/comment_dsl.mdx b/docs/docs/comment_dsl.mdx index 137b033..2d40369 100644 --- a/docs/docs/comment_dsl.mdx +++ b/docs/docs/comment_dsl.mdx @@ -96,6 +96,14 @@ Putting this comment on a type forces that type to derive those traits even if i 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, which is particularly useful for re-generating as it lets your `mod.rs` files remain untouched. +## @custom_json + +```cddl +foo = uint ; @newtype @custom_json +``` + +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. + ## _CDDL_CODEGEN_EXTERN_TYPE_ While not as a comment, this allows you to compose in hand-written structs into a cddl spec. diff --git a/src/comment_ast.rs b/src/comment_ast.rs index a37fe60..aef250a 100644 --- a/src/comment_ast.rs +++ b/src/comment_ast.rs @@ -12,6 +12,7 @@ pub struct RuleMetadata { pub is_newtype: bool, pub no_alias: bool, pub used_as_key: bool, + pub custom_json: bool, } pub fn merge_metadata(r1: &RuleMetadata, r2: &RuleMetadata) -> RuleMetadata { @@ -26,6 +27,7 @@ pub fn merge_metadata(r1: &RuleMetadata, r2: &RuleMetadata) -> RuleMetadata { is_newtype: r1.is_newtype || r2.is_newtype, no_alias: r1.no_alias || r2.no_alias, used_as_key: r1.used_as_key || r2.used_as_key, + custom_json: r1.custom_json || r2.custom_json, }; merged.verify(); merged @@ -36,6 +38,7 @@ enum ParseResult { Name(String), DontGenAlias, UsedAsKey, + CustomJson, } impl RuleMetadata { @@ -61,6 +64,9 @@ impl RuleMetadata { ParseResult::UsedAsKey => { base.used_as_key = true; } + ParseResult::CustomJson => { + base.custom_json = true; + } } } base.verify(); @@ -101,9 +107,21 @@ fn tag_used_as_key(input: &str) -> IResult<&str, ParseResult> { Ok((input, ParseResult::UsedAsKey)) } +fn tag_custom_json(input: &str) -> IResult<&str, ParseResult> { + let (input, _) = tag("@custom_json")(input)?; + + Ok((input, ParseResult::CustomJson)) +} + fn whitespace_then_tag(input: &str) -> IResult<&str, ParseResult> { let (input, _) = take_while(char::is_whitespace)(input)?; - let (input, result) = alt((tag_name, tag_newtype, tag_no_alias, tag_used_as_key))(input)?; + let (input, result) = alt(( + tag_name, + tag_newtype, + tag_no_alias, + tag_used_as_key, + tag_custom_json, + ))(input)?; Ok((input, result)) } @@ -144,6 +162,7 @@ fn parse_comment_name() { is_newtype: false, no_alias: false, used_as_key: false, + custom_json: false, } )) ); @@ -160,6 +179,7 @@ fn parse_comment_newtype() { is_newtype: true, no_alias: false, used_as_key: false, + custom_json: false, } )) ); @@ -176,6 +196,7 @@ fn parse_comment_newtype_and_name() { is_newtype: true, no_alias: false, used_as_key: false, + custom_json: false, } )) ); @@ -192,6 +213,7 @@ fn parse_comment_newtype_and_name_and_used_as_key() { is_newtype: true, no_alias: false, used_as_key: true, + custom_json: false, } )) ); @@ -208,6 +230,7 @@ fn parse_comment_used_as_key() { is_newtype: false, no_alias: false, used_as_key: true, + custom_json: false, } )) ); @@ -224,6 +247,7 @@ fn parse_comment_newtype_and_name_inverse() { is_newtype: true, no_alias: false, used_as_key: false, + custom_json: false, } )) ); @@ -240,6 +264,24 @@ fn parse_comment_name_noalias() { is_newtype: false, no_alias: true, used_as_key: false, + custom_json: false, + } + )) + ); +} + +#[test] +fn parse_comment_newtype_and_custom_json() { + assert_eq!( + rule_metadata("@custom_json @newtype"), + Ok(( + "", + RuleMetadata { + name: None, + is_newtype: true, + no_alias: false, + used_as_key: false, + custom_json: true, } )) ); diff --git a/src/generation.rs b/src/generation.rs index ca27055..70d769b 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -653,18 +653,29 @@ impl GenerationScope { rust_struct.tag(), cli, ), - RustStructType::Wrapper { wrapped, min_max } => match rust_struct.tag() { + RustStructType::Wrapper { + wrapped, + min_max, + custom_json, + } => match rust_struct.tag() { Some(tag) => generate_wrapper_struct( self, types, rust_ident, &wrapped.clone().tag(tag), *min_max, + *custom_json, + cli, + ), + None => generate_wrapper_struct( + self, + types, + rust_ident, + wrapped, + *min_max, + *custom_json, cli, ), - None => { - generate_wrapper_struct(self, types, rust_ident, wrapped, *min_max, cli) - } }, RustStructType::Extern => { #[allow(clippy::single_match)] @@ -828,10 +839,14 @@ impl GenerationScope { // import encoding structs (struct files) if cli.preserve_encodings { for (rust_ident, rust_struct) in types.rust_structs() { - if matches!( - rust_struct.variant(), - RustStructType::Record(_) | RustStructType::Wrapper { .. } - ) { + if match rust_struct.variant() { + RustStructType::Record(_) => true, + RustStructType::Wrapper { wrapped, .. } => { + !encoding_fields(types, &rust_ident.to_string(), wrapped, true, cli) + .is_empty() + } + _ => false, + } { // ALL records have an encoding struct since at minimum they contian // the array or map encoding details so no need to check fields self.rust(types, rust_ident).push_import( @@ -3219,7 +3234,7 @@ fn canonical_param(cli: &Cli) -> &'static str { /// the codegen crate doesn't support proc macros for fields so we need to /// do this with newlines. codegen takes care of indentation somehow. -fn encoding_var_macros(used_in_key: bool, cli: &Cli) -> String { +fn encoding_var_macros(used_in_key: bool, custom_json: bool, cli: &Cli) -> String { let mut ret = if used_in_key { format!( "#[derivative({})]\n", @@ -3232,7 +3247,7 @@ fn encoding_var_macros(used_in_key: bool, cli: &Cli) -> String { } else { String::new() }; - if cli.json_serde_derives { + if cli.json_serde_derives && !custom_json { ret.push_str("#[serde(skip)]\n"); } ret @@ -3513,11 +3528,18 @@ impl DataType for codegen::Enum { fn create_base_rust_struct( types: &IntermediateTypes<'_>, ident: &RustIdent, + manual_json_impl: bool, cli: &Cli, ) -> (codegen::Struct, codegen::Impl) { let name = &ident.to_string(); let mut s = codegen::Struct::new(name); - add_struct_derives(&mut s, types.used_as_key(ident), false, cli); + add_struct_derives( + &mut s, + types.used_as_key(ident), + false, + manual_json_impl, + cli, + ); let group_impl = codegen::Impl::new(name); // TODO: anything here? (s, group_impl) @@ -4984,7 +5006,7 @@ fn codegen_struct( // Rust-only for the rest of this function // Struct (fields) + constructor - let (mut native_struct, mut native_impl) = create_base_rust_struct(types, name, cli); + let (mut native_struct, mut native_impl) = create_base_rust_struct(types, name, false, cli); native_struct.vis("pub"); let mut native_new = codegen::Function::new("new"); let (ctor_ret, ctor_before) = if new_can_fail { @@ -5070,7 +5092,7 @@ fn codegen_struct( native_struct.field( &format!( "{}pub encodings", - encoding_var_macros(types.used_as_key(name), cli) + encoding_var_macros(types.used_as_key(name), false, cli) ), format!("Option<{encoding_name}>"), ); @@ -6189,7 +6211,7 @@ impl EnumVariantInRust { // the codeen crate doesn't support proc macros on fields but we just inline // these with a newline in the field names for declaring as workaround. // Indentation is never an issue as we're always 2 levels deep for field declarations - format!("{}{}", encoding_var_macros(used_in_key, cli), name) + format!("{}{}", encoding_var_macros(used_in_key, false, cli), name) } }) .collect() @@ -6306,7 +6328,7 @@ fn generate_c_style_enum( ) .vis("pub"); } - add_struct_derives(&mut e, types.used_as_key(name), true, cli); + add_struct_derives(&mut e, types.used_as_key(name), true, false, cli); for variant in variants.iter() { e.new_variant(variant.name.to_string()); } @@ -6467,7 +6489,7 @@ fn generate_enum( // instead of using create_serialize_impl() and having the length encoded there, we want to make it easier // to offer definite length encoding even if we're mixing plain group members and non-plain group members (or mixed length plain ones) // by potentially wrapping the choices with the array/map tag in the variant branch when applicable - add_struct_derives(&mut e, types.used_as_key(name), true, cli); + add_struct_derives(&mut e, types.used_as_key(name), true, false, cli); let mut ser_impl = make_serialization_impl(name.as_ref(), cli); let mut ser_func = make_serialization_function("serialize", cli); if let Some(tag) = tag { @@ -7066,6 +7088,7 @@ fn generate_wrapper_struct( type_name: &RustIdent, field_type: &RustType, min_max: Option<(Option, Option)>, + custom_json: bool, cli: &Cli, ) { if min_max.is_some() { @@ -7103,7 +7126,136 @@ fn generate_wrapper_struct( // TODO: do we want to get rid of the rust struct and embed the tag / min/max size here? // The tag is easy but the min/max size would require error types in any place that sets/modifies these in other structs. - let (mut s, mut s_impl) = create_base_rust_struct(types, type_name, cli); + let (mut s, mut s_impl) = create_base_rust_struct(types, type_name, true, cli); + let (inner_var, self_var) = if cli.preserve_encodings { + ("inner", "self.inner") + } else { + ("0", "self.0") + }; + + // manual JSON impls + let mut serde_ser_impl = codegen::Impl::new(type_name); + let mut serde_deser_impl = codegen::Impl::new(type_name); + let mut json_schema_impl = codegen::Impl::new(type_name); + let json_hex_bytes = matches!( + field_type.resolve_alias_shallow(), + ConceptualRustType::Primitive(Primitive::Bytes) + ); + let json_schema_type = if json_hex_bytes { + Cow::Borrowed("String") + } else { + Cow::Owned(field_type.for_rust_member(types, false, cli)) + }; + + if !custom_json { + // serde Serialize / Deserialize + if cli.json_serde_derives { + let mut serde_ser_fn = codegen::Function::new("serialize"); + serde_ser_fn + .generic("S") + .bound("S", "serde::Serializer") + .arg_ref_self() + .arg("serializer", "S") + .ret("Result"); + let mut serde_deser_fn = codegen::Function::new("deserialize"); + serde_deser_fn + .generic("D") + .bound("D", "serde::de::Deserializer<'de>") + .arg("deserializer", "D") + .ret("Result"); + if json_hex_bytes { + serde_ser_fn.line(format!( + "serializer.serialize_str(&hex::encode({self_var}.clone()))" + )); + let err_body = "{ serde::de::Error::invalid_value(serde::de::Unexpected::Str(&s), &\"invalid hex bytes\") }"; + serde_deser_fn + .line("let s = ::deserialize(deserializer)?;") + .line("hex::decode(&s)"); + if types.can_new_fail(type_name) { + serde_deser_fn + .line(format!( + ".ok().and_then(|bytes| {type_name}::new(bytes).ok())" + )) + .line(format!(".ok_or_else(|| {err_body})")); + } else { + serde_deser_fn + .line(format!(".map({type_name}::new)")) + .line(format!(".map_err(|_e| {err_body})")); + } + } else { + serde_ser_fn.line(format!("{self_var}.serialize(serializer)")); + serde_deser_fn + .line(format!("let inner = <{json_schema_type} as serde::de::Deserialize>::deserialize(deserializer)?;")); + if types.can_new_fail(type_name) { + let unexpected = match field_type.resolve_alias_shallow() { + ConceptualRustType::Alias(_, _) => unreachable!(), + ConceptualRustType::Array(_) => "Seq", + ConceptualRustType::Fixed(fixed) => match fixed { + FixedValue::Bool(_) => "Bool(inner)", + FixedValue::Float(_) => "Float(inner)", + FixedValue::Nint(_) => "Signed(inner as i64)", + FixedValue::Null => "Option", + FixedValue::Text(_) => "Str(&inner)", + FixedValue::Uint(_) => "Unsigned(inner)", + }, + ConceptualRustType::Map(_, _) => "Map", + ConceptualRustType::Optional(_) => "Option", + ConceptualRustType::Primitive(p) => match p { + Primitive::Bool => "Bool(inner)", + Primitive::Bytes => "Bytes(&inner)", + Primitive::F32 => "Float(inner as f64)", + Primitive::F64 => "Float(inner)", + Primitive::I8 + | Primitive::I16 + | Primitive::I32 + | Primitive::I64 + | Primitive::N64 => "Signed(inner as i64)", + Primitive::Str => "Str(&inner)", + Primitive::U8 | Primitive::U16 | Primitive::U32 => { + "Unsigned(inner as u64)" + } + Primitive::U64 => "Unsigned(inner)", + }, + ConceptualRustType::Rust(_) => "StructVariant", + }; + serde_deser_fn + .line("Self::new(inner)") + .line(format!(".map_err(|_e| {{ serde::de::Error::invalid_value(serde::de::Unexpected::{unexpected}, &\"invalid {type_name}\") }})")); + } else { + serde_deser_fn.line("Ok(Self::new(inner))"); + } + } + serde_ser_impl + .impl_trait("serde::Serialize") + .push_fn(serde_ser_fn); + serde_deser_impl + .impl_trait("serde::de::Deserialize<'de>") + .generic("'de") + .push_fn(serde_deser_fn); + } + + // JsonSchema + if cli.json_schema_export { + let mut schema_name_fn = codegen::Function::new("schema_name"); + schema_name_fn + .ret("String") + .line(format!("String::from(\"{type_name}\")")); + let mut json_schema_fn = codegen::Function::new("json_schema"); + json_schema_fn + .arg("gen", "&mut schemars::gen::SchemaGenerator") + .ret("schemars::schema::Schema") + .line(format!("{json_schema_type}::json_schema(gen)")); + let mut is_referenceable = codegen::Function::new("is_referenceable"); + is_referenceable + .ret("bool") + .line(format!("{json_schema_type}::is_referenceable()")); + json_schema_impl + .impl_trait("schemars::JsonSchema") + .push_fn(schema_name_fn) + .push_fn(json_schema_fn) + .push_fn(is_referenceable); + } + } s.vis("pub"); let encoding_name = RustIdent::new(CDDLIdent::new(format!("{type_name}Encoding"))); let enc_fields = if cli.preserve_encodings { @@ -7120,7 +7272,7 @@ fn generate_wrapper_struct( s.field( &format!( "{}pub encodings", - encoding_var_macros(types.used_as_key(type_name), cli) + encoding_var_macros(types.used_as_key(type_name), true, cli) ), format!("Option<{encoding_name}>"), ); @@ -7147,11 +7299,6 @@ fn generate_wrapper_struct( if field_type.is_copy(types) && !cli.preserve_encodings { s.derive("Copy"); } - let (inner_var, self_var) = if cli.preserve_encodings { - ("inner", "self.inner") - } else { - ("0", "self.0") - }; let mut get = codegen::Function::new("get"); get.vis("pub").arg_ref_self(); if field_type.is_copy(types) { @@ -7419,6 +7566,17 @@ fn generate_wrapper_struct( .push_impl(s_impl) .push_impl(from_impl) .push_impl(from_inner_impl); + if !custom_json { + if cli.json_serde_derives { + gen_scope + .rust(types, type_name) + .push_impl(serde_ser_impl) + .push_impl(serde_deser_impl); + } + if cli.json_schema_export { + gen_scope.rust(types, type_name).push_impl(json_schema_impl); + } + } gen_scope .rust_serialize(types, type_name) .push_impl(ser_impl) @@ -7441,15 +7599,23 @@ fn key_derives(for_ignore: bool, cli: &Cli) -> &'static [&'static str] { } } -fn add_struct_derives(data_type: &mut T, used_in_key: bool, is_enum: bool, cli: &Cli) { +fn add_struct_derives( + data_type: &mut T, + used_in_key: bool, + is_enum: bool, + custom_json: bool, + cli: &Cli, +) { data_type.derive("Clone").derive("Debug"); - if cli.json_serde_derives { - data_type - .derive("serde::Deserialize") - .derive("serde::Serialize"); - } - if cli.json_schema_export { - data_type.derive("schemars::JsonSchema"); + if !custom_json { + if cli.json_serde_derives { + data_type + .derive("serde::Deserialize") + .derive("serde::Serialize"); + } + if cli.json_schema_export { + data_type.derive("schemars::JsonSchema"); + } } if used_in_key { if cli.preserve_encodings { @@ -7530,14 +7696,14 @@ fn generate_int(gen_scope: &mut GenerationScope, types: &IntermediateTypes, cli: uint.named("value", "u64").named( &format!( "{}encoding", - encoding_var_macros(types.used_as_key(&ident), cli) + encoding_var_macros(types.used_as_key(&ident), true, cli) ), "Option", ); nint.named("value", "u64").named( &format!( "{}encoding", - encoding_var_macros(types.used_as_key(&ident), cli) + encoding_var_macros(types.used_as_key(&ident), true, cli) ), "Option", ); @@ -7547,7 +7713,13 @@ fn generate_int(gen_scope: &mut GenerationScope, types: &IntermediateTypes, cli: } native_struct.push_variant(uint); native_struct.push_variant(nint); - add_struct_derives(&mut native_struct, types.used_as_key(&ident), true, cli); + add_struct_derives( + &mut native_struct, + types.used_as_key(&ident), + true, + true, + cli, + ); // impl Int let mut native_impl = codegen::Impl::new("Int"); diff --git a/src/intermediate.rs b/src/intermediate.rs index 1c30060..4b59ba8 100644 --- a/src/intermediate.rs +++ b/src/intermediate.rs @@ -2167,6 +2167,7 @@ pub enum RustStructType { Wrapper { wrapped: RustType, min_max: Option<(Option, Option)>, + custom_json: bool, }, /// This is a no-op in generation but to prevent lookups of things in the prelude /// e.g. `int` from not being resolved while still being able to detect it when @@ -2263,6 +2264,7 @@ impl RustStruct { tag: Option, wrapped_type: RustType, min_max: Option<(Option, Option)>, + custom_json: bool, ) -> Self { Self { ident, @@ -2270,6 +2272,7 @@ impl RustStruct { variant: RustStructType::Wrapper { wrapped: wrapped_type, min_max, + custom_json, }, } } diff --git a/src/parsing.rs b/src/parsing.rs index 13aaa32..67b0ad7 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -525,6 +525,7 @@ fn parse_type( outer_tag, ranged_type, Some(min_max), + rule_metadata.custom_json, ), cli, ); @@ -613,6 +614,7 @@ fn parse_type( None, concrete_type, None, + rule_metadata.custom_json, ), cli, ); diff --git a/src/test.rs b/src/test.rs index 72cbe7b..d7494dd 100644 --- a/src/test.rs +++ b/src/test.rs @@ -9,6 +9,7 @@ fn run_test( external_rust_file_path: Option, external_wasm_file_path: Option, input_is_dir: bool, + test_deps: &[&str], ) { use std::str::FromStr; let export_path = match export_suffix { @@ -71,6 +72,16 @@ fn run_test( lib_rs.write_all("\n\n".as_bytes()).unwrap(); lib_rs.write_all(test_rs.as_bytes()).unwrap(); std::mem::drop(lib_rs); + // add extra deps used within tests + let mut cargo_toml = std::fs::OpenOptions::new() + .write(true) + .append(true) + .open(test_path.join(format!("{export_path}/rust/Cargo.toml"))) + .unwrap(); + for dep in test_deps { + cargo_toml.write_all(dep.as_bytes()).unwrap(); + } + std::mem::drop(cargo_toml); // run tests in generated code println!(" ------ testing ------"); let cargo_test = std::process::Command::new("cargo") @@ -182,6 +193,7 @@ fn core_with_wasm() { Some(extern_rust_path), Some(extern_wasm_path), false, + &[], ); } @@ -198,6 +210,7 @@ fn core_no_wasm() { Some(extern_rust_path), None, false, + &[], ); } @@ -210,6 +223,7 @@ fn comment_dsl() { None, None, false, + &[], ); } @@ -222,6 +236,7 @@ fn preserve_encodings() { None, None, false, + &[], ); } @@ -234,12 +249,13 @@ fn canonical() { None, None, false, + &[], ); } #[test] fn rust_wasm_split() { - run_test("rust-wasm-split", &[], None, None, None, false); + run_test("rust-wasm-split", &[], None, None, None, false, &[]); } #[test] @@ -259,6 +275,7 @@ fn multifile() { Some(extern_rust_path), Some(extern_wasm_path), true, + &[], ); } @@ -286,6 +303,7 @@ fn multifile_json_preserve() { Some(extern_rust_path), Some(extern_wasm_path), true, + &[], ); } @@ -305,6 +323,7 @@ fn raw_bytes() { Some(extern_rust_path), Some(extern_wasm_path), false, + &[], ); } @@ -324,5 +343,44 @@ fn raw_bytes_preserve() { Some(extern_rust_path), Some(extern_wasm_path), false, + &[], + ); +} + +#[test] +fn json() { + use std::str::FromStr; + let extern_rust_path = std::path::PathBuf::from_str("tests") + .unwrap() + .join("external_json_impls"); + run_test( + "json", + &["--json-serde-derives=true", "--json-schema-export=true"], + None, + Some(extern_rust_path), + None, + false, + &[], + ); +} + +#[test] +fn json_preserve() { + use std::str::FromStr; + let extern_rust_path = std::path::PathBuf::from_str("tests") + .unwrap() + .join("external_json_impls"); + run_test( + "json", + &[ + "--preserve-encodings=true", + "--json-serde-derives=true", + "--json-schema-export=true", + ], + Some("preserve"), + Some(extern_rust_path), + None, + false, + &[], ); } diff --git a/static/Cargo_rust.toml b/static/Cargo_rust.toml index 8a1c63e..4fd8a52 100644 --- a/static/Cargo_rust.toml +++ b/static/Cargo_rust.toml @@ -8,3 +8,4 @@ crate-type = ["cdylib", "rlib"] [dependencies] cbor_event = "2.4.0" +hex = "0.4.0" \ No newline at end of file From 39cf134bcecbc45e4d803aff6ca8bc770eeb595d Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:07:22 -0800 Subject: [PATCH 2/7] clippy fixes --- src/generation.rs | 2 +- src/test.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 70d769b..82dd74f 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -842,7 +842,7 @@ impl GenerationScope { if match rust_struct.variant() { RustStructType::Record(_) => true, RustStructType::Wrapper { wrapped, .. } => { - !encoding_fields(types, &rust_ident.to_string(), wrapped, true, cli) + !encoding_fields(types, rust_ident.as_ref(), wrapped, true, cli) .is_empty() } _ => false, diff --git a/src/test.rs b/src/test.rs index d7494dd..a94d2dc 100644 --- a/src/test.rs +++ b/src/test.rs @@ -46,7 +46,6 @@ fn run_test( assert!(cargo_run_result.status.success()); // copy tests into generated code let mut lib_rs = std::fs::OpenOptions::new() - .write(true) .append(true) .open(test_path.join(format!("{export_path}/rust/src/lib.rs"))) .unwrap(); @@ -74,7 +73,6 @@ fn run_test( std::mem::drop(lib_rs); // add extra deps used within tests let mut cargo_toml = std::fs::OpenOptions::new() - .write(true) .append(true) .open(test_path.join(format!("{export_path}/rust/Cargo.toml"))) .unwrap(); @@ -108,7 +106,6 @@ fn run_test( if let Some(external_wasm_file_path) = external_wasm_file_path { println!("trying to open: {external_wasm_file_path:?}"); let mut wasm_lib_rs = std::fs::OpenOptions::new() - .write(true) .append(true) .open(test_path.join(format!("{export_path}/wasm/src/lib.rs"))) .unwrap(); From afaa56ad7b307d3ea77a37c139b18087ee09a694 Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:09:03 -0800 Subject: [PATCH 3/7] cargo fmt again after clippy fix broke it --- src/generation.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 82dd74f..651454e 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -842,8 +842,7 @@ impl GenerationScope { if match rust_struct.variant() { RustStructType::Record(_) => true, RustStructType::Wrapper { wrapped, .. } => { - !encoding_fields(types, rust_ident.as_ref(), wrapped, true, cli) - .is_empty() + !encoding_fields(types, rust_ident.as_ref(), wrapped, true, cli).is_empty() } _ => false, } { From ffe55b552c24e05bdc833a8d396554e5e77493c3 Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:13:33 -0800 Subject: [PATCH 4/7] rust 1.77 new clippy fix --- src/parsing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsing.rs b/src/parsing.rs index 67b0ad7..1f8b00b 100644 --- a/src/parsing.rs +++ b/src/parsing.rs @@ -41,7 +41,7 @@ pub fn rule_is_scope_marker(cddl_rule: &cddl::ast::Rule) -> Option if value.type_choices.len() == 1 && ident.starts_with(SCOPE_MARKER) { match &value.type_choices[0].type1.type2 { Type2::TextValue { value, .. } => Some(ModuleScope::new( - value.to_string().split("::").map(String::from).collect(), + value.as_ref().split("::").map(String::from).collect(), )), _ => None, } From 26878c002ce203858b7b3b44c193bc49611b434d Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:27:13 -0800 Subject: [PATCH 5/7] test fix --- src/generation.rs | 20 +++++++++++++++++++- static/Cargo_rust.toml | 1 - 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/generation.rs b/src/generation.rs index 651454e..a78fc13 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -1161,7 +1161,25 @@ impl GenerationScope { if cli.json_schema_export { rust_cargo_toml.push_str("schemars = \"0.8.8\"\n"); } - if export_raw_bytes_encoding_trait { + if export_raw_bytes_encoding_trait + || types + .rust_structs() + .iter() + .any(|(_, rust_struct)| match &rust_struct.variant { + RustStructType::Wrapper { + wrapped, + custom_json, + .. + } => { + !custom_json + && matches!( + wrapped.resolve_alias_shallow(), + ConceptualRustType::Primitive(Primitive::Bytes) + ) + } + _ => false, + }) + { rust_cargo_toml.push_str("hex = \"0.4.3\"\n"); } if cli.wasm diff --git a/static/Cargo_rust.toml b/static/Cargo_rust.toml index 4fd8a52..8a1c63e 100644 --- a/static/Cargo_rust.toml +++ b/static/Cargo_rust.toml @@ -8,4 +8,3 @@ crate-type = ["cdylib", "rlib"] [dependencies] cbor_event = "2.4.0" -hex = "0.4.0" \ No newline at end of file From ba372da723ea442907a3467c94d8f8f8a30b0e4e Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:32:52 -0800 Subject: [PATCH 6/7] test fix 2: electric boogaloo --- tests/external_json_impls | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/external_json_impls diff --git a/tests/external_json_impls b/tests/external_json_impls new file mode 100644 index 0000000..45ee0ab --- /dev/null +++ b/tests/external_json_impls @@ -0,0 +1,40 @@ +impl serde::Serialize for CustomWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&u64::from(self.clone()).to_string()) + } +} + +impl<'de> serde::de::Deserialize<'de> for CustomWrapper { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + use std::str::FromStr; + let s = ::deserialize(deserializer)?; + u64::from_str(&s) + .map(CustomWrapper::new) + .map_err(|_e| { + serde::de::Error::invalid_value( + serde::de::Unexpected::Str(&s), + &"invalid u64 as string", + ) + }) + } +} + +impl schemars::JsonSchema for CustomWrapper { + fn schema_name() -> String { + String::from("CustomWrapper") + } + + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + String::json_schema(gen) + } + + fn is_referenceable() -> bool { + String::is_referenceable() + } +} \ No newline at end of file From 4cb76a2940a2063df65deb00b338084ed514f318 Mon Sep 17 00:00:00 2001 From: rooooooooob Date: Tue, 30 Jan 2024 09:38:47 -0800 Subject: [PATCH 7/7] forgot these files too due to git reseting to fix a commit message... --- tests/json/input.cddl | 13 ++++++ tests/json/tests.rs | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/json/input.cddl create mode 100644 tests/json/tests.rs diff --git a/tests/json/input.cddl b/tests/json/input.cddl new file mode 100644 index 0000000..1a268c5 --- /dev/null +++ b/tests/json/input.cddl @@ -0,0 +1,13 @@ +bytes_wrapper = bytes ; @newtype +str_wrapper = text ; @newtype +u8_wrapper = uint .lt 256 ; @newtype +u64_wrapper = uint ; @newtype +i16_wrapper = int .size 2 ; @newtype +i64_wrapper = int .size 8 ; @newtype +nint_wrapper = nint ; @newtype + +; TODO: issue: https://github.com/dcSpark/cddl-codegen/issues/223 +; bool_wrapper = bool ; @newtype + +struct_wrapper = u64_wrapper ; @newtype +custom_wrapper = uint ; @newtype @custom_json \ No newline at end of file diff --git a/tests/json/tests.rs b/tests/json/tests.rs new file mode 100644 index 0000000..8afd25e --- /dev/null +++ b/tests/json/tests.rs @@ -0,0 +1,92 @@ +#[cfg(test)] +mod tests { + use super::*; + use cbor_event::de::Deserializer; + use serialization::Deserialize; + + #[test] + fn bytes_wrapper() { + let bytes = vec![0xBA, 0xAD, 0xF0, 0x0D]; + let hex = format!("\"{}\"", hex::encode(&bytes)); + let from_bytes = BytesWrapper::new(bytes.clone()); + let from_hex: BytesWrapper = serde_json::from_str(&hex).unwrap(); + assert_eq!(hex, serde_json::to_string_pretty(&from_bytes).unwrap()); + assert_eq!(hex, serde_json::to_string_pretty(&from_hex).unwrap()); + } + + #[test] + fn str_wrapper() { + let text = "hello, world"; + let json_str = format!("\"{text}\""); + let from_str = StrWrapper::new(text.to_owned()); + let from_json: StrWrapper = serde_json::from_str(&json_str).unwrap(); + assert_eq!(json_str, serde_json::to_string_pretty(&from_str).unwrap()); + assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); + } + + fn json_wrapper_test(value: V) + where W: TryFrom + serde::Serialize + for <'de> serde::Deserialize<'de>, + V: std::fmt::Display, + >::Error: std::fmt::Debug + { + let json_str = value.to_string(); + let from_value = W::try_from(value).unwrap(); + let from_json: W = serde_json::from_str(&json_str).unwrap(); + assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); + assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); + } + + #[test] + fn u8_wrapper() { + json_wrapper_test::(u8::MIN); + json_wrapper_test::(u8::MAX); + } + + #[test] + fn u64_wrapper() { + json_wrapper_test::(u64::MIN); + json_wrapper_test::(u64::MAX); + } + + #[test] + fn i16_wrapper() { + json_wrapper_test::(i16::MIN); + json_wrapper_test::(i16::MAX); + } + + #[test] + fn i64_wrapper() { + json_wrapper_test::(i64::MIN); + json_wrapper_test::(i64::MAX); + } + + #[test] + fn nint_wrapper() { + json_wrapper_test::(u64::MIN); + json_wrapper_test::(u64::MAX); + } + + // #[test] + // fn bool_wrapper() { + // json_wrapper_test::(false); + // json_wrapper_test::(true); + // } + + #[test] + fn struct_wrapper() { + let json_str = u64::MAX.to_string(); + let from_value = StructWrapper::from(U64Wrapper::from(u64::MAX)); + let from_json: StructWrapper = serde_json::from_str(&json_str).unwrap(); + assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); + assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); + } + + #[test] + fn custom_wrapper() { + let json_str = "\"1234\""; + let from_value = CustomWrapper::from(1234u64); + let from_json: CustomWrapper = serde_json::from_str(&json_str).unwrap(); + assert_eq!(json_str, serde_json::to_string_pretty(&from_value).unwrap()); + assert_eq!(json_str, serde_json::to_string_pretty(&from_json).unwrap()); + } +}