From b67a6ed8a9ab4254d02408143ddd66084f61ea8b Mon Sep 17 00:00:00 2001 From: Adam Leyshon Date: Mon, 12 Jul 2021 22:33:26 +0100 Subject: [PATCH 1/8] Add support for `rustCustomDerive` and `rustCustomUse` metadata for Structs. This allows us to additional `use` statements or `derive` macros on generated structs. `rustCustomUse` is a string of use statments separated by `;` It supports single and multi-import statements. For example: `serde::{Deserialize, Serialize}` or `serde::Serialize` are both valid. `rustCustomUse` is a string of derive identifers separated by `,` For example: `FromSqlRow,AsExpression,Debug,Default` --- crates/target_rust/src/lib.rs | 50 ++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index 5c7ae462..b032be85 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -4,6 +4,7 @@ use lazy_static::lazy_static; use serde_json::Value; use std::collections::{BTreeMap, BTreeSet}; use std::io::Write; +use jtd_codegen::error::Error; lazy_static! { static ref KEYWORDS: BTreeSet = include_str!("keywords") @@ -234,15 +235,62 @@ impl jtd_codegen::target::Target for Target { return Ok(Some(s.into())); } + let mut derives = vec!["Serialize", "Deserialize"]; + + if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { + derives.extend( + s.split(",") + ); + } + state .imports .entry("serde".into()) .or_default() .extend(vec!["Deserialize".to_owned(), "Serialize".to_owned()]); + let mut custom_use = Vec::<&str>::new(); + if let Some(s) = metadata.get("rustCustomUse").and_then(|v| v.as_str()) { + custom_use.extend( + s.split(";") + ); + } + for cu in custom_use + { + // custom::path::{import,export} or custom::path::single + let mut use_imports = Vec::<&str>::new(); + let mut path_parts = cu.split("::").collect::>(); + let mut last_part = path_parts.pop().unwrap(); + // If there are no path_parts or the last part was "", panic! + if path_parts.len() < 1 || last_part.trim().len() < 1 { + return Err( + Error::Io( + std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid custom use statement: {:?}", cu) + ) + ) + ); + } + if last_part.starts_with('{') + { + // Strip the first/last chars and split + last_part = &last_part[1..last_part.len() - 1]; + use_imports.extend(last_part.split(",")) + } else { + // No, just push it into the imports list + use_imports.push(last_part); + } + state + .imports + .entry(path_parts.join("::").into()) + .or_default() + .extend(use_imports.drain(..).map(|i| i.trim().to_owned())); + } + writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; - writeln!(out, "#[derive(Serialize, Deserialize)]")?; + writeln!(out, "#[derive({})]", derives.join(", "))?; if fields.is_empty() { writeln!(out, "pub struct {} {{}}", name)?; From dc0bf810ff8a0bb845fbf256318aacf6b703d679 Mon Sep 17 00:00:00 2001 From: Adam Leyshon Date: Thu, 15 Jul 2021 09:01:06 +0100 Subject: [PATCH 2/8] rustfmt --- crates/target_rust/src/lib.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index b032be85..b6215cef 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -1,10 +1,10 @@ +use jtd_codegen::error::Error; use jtd_codegen::target::{self, inflect, metadata}; use jtd_codegen::Result; use lazy_static::lazy_static; use serde_json::Value; use std::collections::{BTreeMap, BTreeSet}; use std::io::Write; -use jtd_codegen::error::Error; lazy_static! { static ref KEYWORDS: BTreeSet = include_str!("keywords") @@ -238,9 +238,7 @@ impl jtd_codegen::target::Target for Target { let mut derives = vec!["Serialize", "Deserialize"]; if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { - derives.extend( - s.split(",") - ); + derives.extend(s.split(",")); } state @@ -251,29 +249,21 @@ impl jtd_codegen::target::Target for Target { let mut custom_use = Vec::<&str>::new(); if let Some(s) = metadata.get("rustCustomUse").and_then(|v| v.as_str()) { - custom_use.extend( - s.split(";") - ); + custom_use.extend(s.split(";")); } - for cu in custom_use - { + for cu in custom_use { // custom::path::{import,export} or custom::path::single let mut use_imports = Vec::<&str>::new(); let mut path_parts = cu.split("::").collect::>(); let mut last_part = path_parts.pop().unwrap(); // If there are no path_parts or the last part was "", panic! if path_parts.len() < 1 || last_part.trim().len() < 1 { - return Err( - Error::Io( - std::io::Error::new( - std::io::ErrorKind::Other, - format!("Invalid custom use statement: {:?}", cu) - ) - ) - ); + return Err(Error::Io(std::io::Error::new( + std::io::ErrorKind::Other, + format!("Invalid custom use statement: {:?}", cu), + ))); } - if last_part.starts_with('{') - { + if last_part.starts_with('{') { // Strip the first/last chars and split last_part = &last_part[1..last_part.len() - 1]; use_imports.extend(last_part.split(",")) From bfaa6c0bc08271f9c862e1f824d7a5c645154b9f Mon Sep 17 00:00:00 2001 From: Paul FREAKN Baker Date: Tue, 16 Aug 2022 15:45:28 -0500 Subject: [PATCH 3/8] Updating rust output to be language idomatic Struct fields should be snake_case, but they were accidentaly set to camelCase. --- crates/target_rust/output/custom_overrides/mod.rs | 12 ++++++------ crates/target_rust/output/description/mod.rs | 12 ++++++------ .../output/empty_and_nonascii_properties/mod.rs | 8 ++++---- crates/target_rust/output/enum_collisions/mod.rs | 2 +- crates/target_rust/output/initialisms/mod.rs | 6 +++--- crates/target_rust/output/nullable_references/mod.rs | 12 ++++++------ crates/target_rust/output/type_collisions/mod.rs | 2 +- crates/target_rust/src/lib.rs | 2 +- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/target_rust/output/custom_overrides/mod.rs b/crates/target_rust/output/custom_overrides/mod.rs index 72373428..bda9bb11 100644 --- a/crates/target_rust/output/custom_overrides/mod.rs +++ b/crates/target_rust/output/custom_overrides/mod.rs @@ -9,20 +9,20 @@ pub struct RootOverrideTypeDiscriminatorBaz {} #[derive(Serialize, Deserialize)] pub struct Root { #[serde(rename = "override_elements_container")] - pub overrideElementsContainer: Vec, + pub override_elements_container: Vec, #[serde(rename = "override_type_discriminator")] - pub overrideTypeDiscriminator: serde_json::Value, + pub override_type_discriminator: serde_json::Value, #[serde(rename = "override_type_enum")] - pub overrideTypeEnum: serde_json::Value, + pub override_type_enum: serde_json::Value, #[serde(rename = "override_type_expr")] - pub overrideTypeExpr: serde_json::Value, + pub override_type_expr: serde_json::Value, #[serde(rename = "override_type_properties")] - pub overrideTypeProperties: serde_json::Value, + pub override_type_properties: serde_json::Value, #[serde(rename = "override_values_container")] - pub overrideValuesContainer: HashMap, + pub override_values_container: HashMap, } diff --git a/crates/target_rust/output/description/mod.rs b/crates/target_rust/output/description/mod.rs index bed9ffe6..b7815119 100644 --- a/crates/target_rust/output/description/mod.rs +++ b/crates/target_rust/output/description/mod.rs @@ -38,11 +38,11 @@ pub struct RootPropertiesWithDescription {} pub struct Root { /// A description for discriminator #[serde(rename = "discriminator_with_description")] - pub discriminatorWithDescription: RootDiscriminatorWithDescription, + pub discriminator_with_description: RootDiscriminatorWithDescription, /// A description for enum #[serde(rename = "enum_with_description")] - pub enumWithDescription: RootEnumWithDescription, + pub enum_with_description: RootEnumWithDescription, /// Whereas disregard and contempt for human rights have resulted in /// barbarous acts which have outraged the conscience of mankind, and the @@ -50,19 +50,19 @@ pub struct Root { /// and belief and freedom from fear and want has been proclaimed as the /// highest aspiration of the common people, #[serde(rename = "long_description")] - pub longDescription: String, + pub long_description: String, /// A description for properties #[serde(rename = "properties_with_description")] - pub propertiesWithDescription: RootPropertiesWithDescription, + pub properties_with_description: RootPropertiesWithDescription, /// A description for ref #[serde(rename = "ref_with_description")] - pub refWithDescription: Baz, + pub ref_with_description: Baz, /// A description for string #[serde(rename = "string_with_description")] - pub stringWithDescription: String, + pub string_with_description: String, } /// A description for a definition diff --git a/crates/target_rust/output/empty_and_nonascii_properties/mod.rs b/crates/target_rust/output/empty_and_nonascii_properties/mod.rs index cfc1a970..270b00bb 100644 --- a/crates/target_rust/output/empty_and_nonascii_properties/mod.rs +++ b/crates/target_rust/output/empty_and_nonascii_properties/mod.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct Root { #[serde(rename = "")] - pub defaultName: String, + pub default_name: String, #[serde(rename = "$foo")] pub foo: String, @@ -17,14 +17,14 @@ pub struct Root { pub foo1: String, #[serde(rename = "foo\nbar")] - pub fooBar: String, + pub foo_bar: String, #[serde(rename = "foo bar")] - pub fooBar0: String, + pub foo_bar0: String, #[serde(rename = "foo0bar")] pub foo0bar: String, #[serde(rename = "fooï·½bar")] - pub fooBar1: String, + pub foo_bar1: String, } diff --git a/crates/target_rust/output/enum_collisions/mod.rs b/crates/target_rust/output/enum_collisions/mod.rs index 15349b7c..f0e39b14 100644 --- a/crates/target_rust/output/enum_collisions/mod.rs +++ b/crates/target_rust/output/enum_collisions/mod.rs @@ -32,5 +32,5 @@ pub struct Root { pub foo: RootFoo, #[serde(rename = "foo_bar")] - pub fooBar: RootFooBar0, + pub foo_bar: RootFooBar0, } diff --git a/crates/target_rust/output/initialisms/mod.rs b/crates/target_rust/output/initialisms/mod.rs index 36f2af75..a913fa55 100644 --- a/crates/target_rust/output/initialisms/mod.rs +++ b/crates/target_rust/output/initialisms/mod.rs @@ -20,14 +20,14 @@ pub struct Root { pub id: String, #[serde(rename = "nested_id_initialism")] - pub nestedIdInitialism: RootNestedIdInitialism, + pub nested_id_initialism: RootNestedIdInitialism, #[serde(rename = "utf8")] pub utf8: String, #[serde(rename = "word_with_embedded_id_initialism")] - pub wordWithEmbeddedIdInitialism: String, + pub word_with_embedded_id_initialism: String, #[serde(rename = "word_with_trailing_initialism_id")] - pub wordWithTrailingInitialismId: String, + pub word_with_trailing_initialism_id: String, } diff --git a/crates/target_rust/output/nullable_references/mod.rs b/crates/target_rust/output/nullable_references/mod.rs index 958a994b..b49b8adf 100644 --- a/crates/target_rust/output/nullable_references/mod.rs +++ b/crates/target_rust/output/nullable_references/mod.rs @@ -5,22 +5,22 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] pub struct Root { #[serde(rename = "notnull_ref_notnull_string")] - pub notnullRefNotnullString: NotnullRefNotnullString, + pub notnull_ref_notnull_string: NotnullRefNotnullString, #[serde(rename = "notnull_ref_null_string")] - pub notnullRefNullString: NotnullRefNullString, + pub notnull_ref_null_string: NotnullRefNullString, #[serde(rename = "notnull_string")] - pub notnullString: NotnullString, + pub notnull_string: NotnullString, #[serde(rename = "null_ref_notnull_string")] - pub nullRefNotnullString: NullRefNotnullString, + pub null_ref_notnull_string: NullRefNotnullString, #[serde(rename = "null_ref_null_string")] - pub nullRefNullString: NullRefNullString, + pub null_ref_null_string: NullRefNullString, #[serde(rename = "null_string")] - pub nullString: NullString, + pub null_string: NullString, } pub type NotnullRefNotnullString = NotnullString; diff --git a/crates/target_rust/output/type_collisions/mod.rs b/crates/target_rust/output/type_collisions/mod.rs index ff75effd..251d30a2 100644 --- a/crates/target_rust/output/type_collisions/mod.rs +++ b/crates/target_rust/output/type_collisions/mod.rs @@ -26,5 +26,5 @@ pub struct Root { pub foo: RootFoo, #[serde(rename = "foo_bar")] - pub fooBar: RootFooBar0, + pub foo_bar: RootFooBar0, } diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index 5c7ae462..33321278 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -18,7 +18,7 @@ lazy_static! { static ref FIELD_NAMING_CONVENTION: Box = Box::new(inflect::KeywordAvoidingInflector::new( KEYWORDS.clone(), - inflect::TailInflector::new(inflect::Case::camel_case()) + inflect::TailInflector::new(inflect::Case::snake_case()) )); static ref ENUM_MEMBER_NAMING_CONVENTION: Box = Box::new(inflect::KeywordAvoidingInflector::new( From e584cf5b24b86bb269e3bda3ed977a99d0fac05b Mon Sep 17 00:00:00 2001 From: Ryan Atallah Date: Wed, 15 Feb 2023 00:33:15 -0800 Subject: [PATCH 4/8] Write all permissions in CI pipeline --- .github/workflows/publish.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fa083ade..f46f8b5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: jobs: release_binary: + permissions: write-all runs-on: ${{ matrix.runs_on }} strategy: matrix: From b7c58fca0a12165a52b72914d3ae8ac46ae22233 Mon Sep 17 00:00:00 2001 From: Ryan Atallah Date: Wed, 15 Feb 2023 00:53:14 -0800 Subject: [PATCH 5/8] Better handling of optional properties in Rust (#2) --- crates/core/src/codegen/mod.rs | 59 +++++++++++++++++++-- crates/core/src/target/mod.rs | 1 + crates/target_csharp_system_text/src/lib.rs | 1 + crates/target_go/src/lib.rs | 1 + crates/target_java_jackson/src/lib.rs | 1 + crates/target_python/src/lib.rs | 1 + crates/target_ruby/src/lib.rs | 1 + crates/target_ruby_sig/src/lib.rs | 1 + crates/target_rust/src/lib.rs | 16 +++--- crates/target_typescript/src/lib.rs | 1 + 10 files changed, 73 insertions(+), 10 deletions(-) diff --git a/crates/core/src/codegen/mod.rs b/crates/core/src/codegen/mod.rs index 1b7cb662..1f6d1b09 100644 --- a/crates/core/src/codegen/mod.rs +++ b/crates/core/src/codegen/mod.rs @@ -9,7 +9,7 @@ use crate::target::{ use ast::{Ast, SchemaAst}; use jtd::Schema; use namespace::Namespace; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use std::fs::File; use std::io::Write; use std::path::Path; @@ -36,6 +36,7 @@ struct CodeGenerator<'a, T> { out_dir: &'a Path, strategy: Strategy, definition_names: BTreeMap, + recursive_definitions: BTreeSet, } struct FileData { @@ -50,6 +51,7 @@ impl<'a, T: Target> CodeGenerator<'a, T> { out_dir, strategy: target.strategy(), definition_names: BTreeMap::new(), + recursive_definitions: BTreeSet::new(), } } @@ -68,6 +70,18 @@ impl<'a, T: Target> CodeGenerator<'a, T> { self.definition_names.insert(name.clone(), ast_name); } + // Detect recursive definitions, as some target language need to handle + // them specifically (e.g. Rust). + // Note that a type is *not* considered to be recursive it it contains + // instances of itself only through "elements" or "values" + // (the underlying container is considered to break the recursion). + for (name, ast) in &schema_ast.definitions { + let mut visited = vec![]; + if find_recursion(name, ast, &schema_ast.definitions, &mut visited) { + self.recursive_definitions.insert(name.clone()); + } + } + // If the target is using FilePerType partitioning, then this state // won't actually be used at all. If it's using SingleFile partitioning, // then this is the only file state that will be used. @@ -120,8 +134,17 @@ impl<'a, T: Target> CodeGenerator<'a, T> { // Ref nodes are a special sort of "expr-like" node, where we // already know what the name of the expression is; it's the name of // the definition. - Ast::Ref { definition, .. } => self.definition_names[&definition].clone(), - + // Note however that recursive definition may need some special + // treatment by the target. + Ast::Ref { metadata, definition } => { + let sub_expr = self.definition_names[&definition].clone(); + if self.recursive_definitions.iter().any(|i| i == &definition) { + self.target + .expr(&mut file_data.state, metadata, Expr::RecursiveRef(sub_expr)) + } else { + sub_expr + } + } // The remaining "expr-like" node types just build up strings and // possibly alter the per-file state (usually in order to add // "imports" to the file). @@ -479,3 +502,33 @@ impl<'a, T: Target> CodeGenerator<'a, T> { Ok(()) } } + +fn find_recursion(name: &str, ast: &Ast, definitions: &BTreeMap, visited: &mut Vec) -> bool { + match ast { + Ast::Ref { definition, .. } => { + if definition == name { + true + } else if visited.iter().any(|i| i == &name) { + false + } else if let Some(ast2) = definitions.get(definition) { + visited.push(definition.clone()); + find_recursion(name, &ast2, definitions, visited) + } else { + false + } + } + Ast::NullableOf { type_, .. } => { + find_recursion(name, type_, definitions, visited) + } + Ast::Struct { fields, .. } => { + fields.iter() + .any(|f| find_recursion(name, &f.type_, definitions, visited)) + } + Ast::Discriminator { variants, .. } => { + variants.iter() + .flat_map(|v| v.fields.iter()) + .any(|f| find_recursion(name, &f.type_, definitions, visited)) + } + _ => false, + } +} \ No newline at end of file diff --git a/crates/core/src/target/mod.rs b/crates/core/src/target/mod.rs index cfabf9c6..14158671 100644 --- a/crates/core/src/target/mod.rs +++ b/crates/core/src/target/mod.rs @@ -87,6 +87,7 @@ pub enum Expr { ArrayOf(String), DictOf(String), NullableOf(String), + RecursiveRef(String), } #[derive(Debug)] diff --git a/crates/target_csharp_system_text/src/lib.rs b/crates/target_csharp_system_text/src/lib.rs index f1f6e371..6125e46a 100644 --- a/crates/target_csharp_system_text/src/lib.rs +++ b/crates/target_csharp_system_text/src/lib.rs @@ -125,6 +125,7 @@ impl jtd_codegen::target::Target for Target { format!("IDictionary", sub_expr) } target::Expr::NullableOf(sub_expr) => format!("{}?", sub_expr), + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_go/src/lib.rs b/crates/target_go/src/lib.rs index 3c934b86..b8b92cbe 100644 --- a/crates/target_go/src/lib.rs +++ b/crates/target_go/src/lib.rs @@ -114,6 +114,7 @@ impl jtd_codegen::target::Target for Target { target::Expr::ArrayOf(sub_expr) => format!("[]{}", sub_expr), target::Expr::DictOf(sub_expr) => format!("map[string]{}", sub_expr), target::Expr::NullableOf(sub_expr) => format!("*{}", sub_expr), + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_java_jackson/src/lib.rs b/crates/target_java_jackson/src/lib.rs index d8696c37..96123287 100644 --- a/crates/target_java_jackson/src/lib.rs +++ b/crates/target_java_jackson/src/lib.rs @@ -125,6 +125,7 @@ impl jtd_codegen::target::Target for Target { format!("Map", sub_expr) } target::Expr::NullableOf(sub_expr) => sub_expr, // everything is already nullable + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_python/src/lib.rs b/crates/target_python/src/lib.rs index 1e91cb59..c0eb5dec 100644 --- a/crates/target_python/src/lib.rs +++ b/crates/target_python/src/lib.rs @@ -135,6 +135,7 @@ impl jtd_codegen::target::Target for Target { format!("Optional[{}]", sub_expr) } + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_ruby/src/lib.rs b/crates/target_ruby/src/lib.rs index db7e3ee7..5c64dc96 100644 --- a/crates/target_ruby/src/lib.rs +++ b/crates/target_ruby/src/lib.rs @@ -117,6 +117,7 @@ impl jtd_codegen::target::Target for Target { format!("Hash[String, {}]", sub_expr) } target::Expr::NullableOf(sub_expr) => sub_expr, + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_ruby_sig/src/lib.rs b/crates/target_ruby_sig/src/lib.rs index 271bab75..4f96c411 100644 --- a/crates/target_ruby_sig/src/lib.rs +++ b/crates/target_ruby_sig/src/lib.rs @@ -116,6 +116,7 @@ impl jtd_codegen::target::Target for Target { format!("Hash[String, {}]", sub_expr) } target::Expr::NullableOf(sub_expr) => format!("{}?", sub_expr), + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index 33321278..0c93f50a 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -121,14 +121,16 @@ impl jtd_codegen::target::Target for Target { format!("HashMap", sub_expr) } - // TODO: A Box here is necessary because otherwise a recursive data - // structure may fail to compile, such as in the geojson test case. + target::Expr::NullableOf(sub_expr) => format!("Option<{}>", sub_expr), + // A Box here is usually necessary for recursive data structures, + // such as in the geojson test case. // - // A more "proper" fix to this problem would be to have a cyclical - // reference detector, and insert Box only if it's necessary to - // break a cyclic dependency. It's unclear how much of a problem - // this is in the real world. - target::Expr::NullableOf(sub_expr) => format!("Option>", sub_expr), + // Note that this strategy is slighyly over-defensive; + // in a cycle of mutually recursive types, + // only one of the types needs to be boxed to break the cycle. + // In such cases, the user may want to optimize the code, + // overriding some of the boxings with metadata.rustType. + target::Expr::RecursiveRef(sub_expr) => format!("Box<{}>", sub_expr), } } diff --git a/crates/target_typescript/src/lib.rs b/crates/target_typescript/src/lib.rs index a6465579..185dce8e 100644 --- a/crates/target_typescript/src/lib.rs +++ b/crates/target_typescript/src/lib.rs @@ -92,6 +92,7 @@ impl jtd_codegen::target::Target for Target { target::Expr::ArrayOf(sub_expr) => format!("{}[]", sub_expr), target::Expr::DictOf(sub_expr) => format!("{{ [key: string]: {} }}", sub_expr), target::Expr::NullableOf(sub_expr) => format!("({} | null)", sub_expr), + target::Expr::RecursiveRef(sub_expr) => sub_expr, } } From 5423dc5193e24dcaf1ab5ffe1608b1673531fc1c Mon Sep 17 00:00:00 2001 From: diamondburned Date: Mon, 13 Feb 2023 04:03:53 -0800 Subject: [PATCH 6/8] Generate Go interfaces for discriminators This commit implements jsontypedef/json-typedef-codegen/issues/67. --- crates/target_go/src/lib.rs | 117 ++++++++++++++++++++++++++++-------- 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/crates/target_go/src/lib.rs b/crates/target_go/src/lib.rs index 3c934b86..829796bd 100644 --- a/crates/target_go/src/lib.rs +++ b/crates/target_go/src/lib.rs @@ -259,27 +259,12 @@ impl jtd_codegen::target::Target for Target { writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; writeln!(out, "type {} struct {{", name)?; - writeln!(out, "\t{} string", tag_field_name)?; - for variant in &variants { - writeln!(out)?; - writeln!(out, "\t{} {}", &variant.field_name, &variant.type_name)?; - } + writeln!(out, "\tValue I{} `json:\"-\"`", name)?; writeln!(out, "}}")?; writeln!(out)?; writeln!(out, "func (v {}) MarshalJSON() ([]byte, error) {{", name)?; - writeln!(out, "\tswitch v.{} {{", tag_field_name)?; - for variant in &variants { - writeln!(out, "\tcase {:?}:", variant.tag_value)?; - writeln!(out, "\t\treturn json.Marshal(struct {{ T string `json:\"{}\"`; {} }}{{ v.{}, v.{} }})", tag_json_name, variant.type_name, tag_field_name, variant.field_name)?; - } - writeln!(out, "\t}}")?; - writeln!(out)?; - writeln!( - out, - "\treturn nil, fmt.Errorf(\"bad {0} value: %s\", v.{0})", - tag_field_name - )?; + writeln!(out, "\treturn json.Marshal(v.Value)")?; writeln!(out, "}}")?; writeln!(out)?; @@ -293,21 +278,21 @@ impl jtd_codegen::target::Target for Target { writeln!(out, "\t\treturn err")?; writeln!(out, "\t}}")?; writeln!(out)?; + writeln!(out, "\tvar value I{}", name)?; writeln!(out, "\tvar err error")?; + writeln!(out)?; writeln!(out, "\tswitch t.T {{")?; for variant in &variants { writeln!(out, "\tcase {:?}:", variant.tag_value)?; - writeln!( - out, - "\t\terr = json.Unmarshal(b, &v.{})", - variant.field_name - )?; + writeln!(out, "\t\tvar v {}", variant.type_name)?; + writeln!(out, "\t\terr = json.Unmarshal(b, &v)")?; + writeln!(out, "\t\tvalue = v")?; } writeln!(out, "\tdefault:")?; writeln!( out, - "\t\terr = fmt.Errorf(\"bad {} value: %s\", t.T)", - tag_field_name + "\t\terr = fmt.Errorf(\"{}: bad {} value: %q\", t.T)", + name, tag_json_name )?; writeln!(out, "\t}}")?; writeln!(out)?; @@ -315,10 +300,92 @@ impl jtd_codegen::target::Target for Target { writeln!(out, "\t\treturn err")?; writeln!(out, "\t}}")?; writeln!(out)?; - writeln!(out, "\tv.{} = t.T", tag_field_name)?; + writeln!(out, "\tv.Value = value")?; writeln!(out, "\treturn nil")?; writeln!(out, "}}")?; + writeln!(out)?; + writeln!( + out, + "// I{} is an interface type that {} types implement.", + name, name + )?; + writeln!(out, "// It can be the following types:")?; + writeln!(out, "//")?; + for variant in &variants { + writeln!( + out, + "// - [{}] ({})", + &variant.type_name, &variant.tag_value + )?; + } + writeln!(out, "//")?; + writeln!(out, "type I{} interface {{", name)?; + writeln!(out, "\t{}() string", tag_field_name)?; + writeln!(out, "\tis{}()", name)?; + writeln!(out, "}}")?; + writeln!(out)?; + + for variant in &variants { + writeln!( + out, + "func ({}) {}() string {{ return {:?} }}", + variant.type_name, tag_field_name, variant.tag_value + )?; + } + writeln!(out)?; + + for variant in &variants { + writeln!(out, "func ({}) is{}() {{}}", variant.type_name, name)?; + } + writeln!(out)?; + + for variant in &variants { + writeln!( + out, + "func (v {}) MarshalJSON() ([]byte, error) {{", + variant.type_name + )?; + writeln!(out, "\ttype Alias {}", variant.type_name)?; + writeln!(out, "\treturn json.Marshal(struct {{")?; + writeln!(out, "\t\tT string `json:\"{}\"`", tag_json_name)?; + writeln!(out, "\t\tAlias")?; + writeln!(out, "\t}}{{")?; + writeln!(out, "\t\tv.{}(),", tag_field_name)?; + writeln!(out, "\t\tAlias(v),")?; + writeln!(out, "\t}})")?; + writeln!(out, "}}")?; + writeln!(out)?; + + writeln!( + out, + "func (v *{}) UnmarshalJSON(b []byte) error {{", + variant.type_name + )?; + writeln!(out, "\ttype Alias {}", variant.type_name)?; + writeln!(out, "\tvar a struct {{")?; + writeln!(out, "\t\tT string `json:\"{}\"`", tag_json_name)?; + writeln!(out, "\t\tAlias")?; + writeln!(out, "\t}}")?; + writeln!(out)?; + writeln!(out, "\tif err := json.Unmarshal(b, &a); err != nil {{")?; + writeln!(out, "\t\treturn err")?; + writeln!(out, "\t}}")?; + writeln!(out)?; + writeln!(out, "\tif a.T != {:?} {{", variant.tag_value)?; + writeln!( + out, + "\t\treturn fmt.Errorf(\"{}: bad {} value: %q\", a.T)", + variant.type_name, tag_json_name + )?; + writeln!(out, "\t}}")?; + writeln!(out)?; + writeln!(out, "\t*v = {}(a.Alias)", variant.type_name)?; + writeln!(out, "\treturn nil")?; + writeln!(out, "}}")?; + writeln!(out)?; + } + None } From 0bece3912d6f28d411b0bfe6b1552fda520c2970 Mon Sep 17 00:00:00 2001 From: Alissa Pajer Date: Wed, 22 Mar 2023 14:53:45 -0400 Subject: [PATCH 7/8] derive custom rust types for enum --- crates/target_rust/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index fcbd0e58..f9f73c9a 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -203,9 +203,15 @@ impl jtd_codegen::target::Target for Target { .or_default() .extend(vec!["Deserialize".to_owned(), "Serialize".to_owned()]); + let mut derives = vec!["Serialize", "Deserialize"]; + + if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { + derives.extend(s.split(",")); + } + writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; - writeln!(out, "#[derive(Serialize, Deserialize)]")?; + writeln!(out, "#[derive({})]", derives.join(", "))?; writeln!(out, "pub enum {} {{", name)?; for (index, member) in members.into_iter().enumerate() { From 17c36193a702c2ed0451c430904b9285ca926446 Mon Sep 17 00:00:00 2001 From: Alissa Pajer Date: Wed, 22 Mar 2023 15:13:30 -0400 Subject: [PATCH 8/8] rustCustomDerive in all places --- crates/target_rust/src/lib.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/target_rust/src/lib.rs b/crates/target_rust/src/lib.rs index f9f73c9a..ea8b88a9 100644 --- a/crates/target_rust/src/lib.rs +++ b/crates/target_rust/src/lib.rs @@ -197,18 +197,18 @@ impl jtd_codegen::target::Target for Target { return Ok(Some(s.into())); } - state - .imports - .entry("serde".into()) - .or_default() - .extend(vec!["Deserialize".to_owned(), "Serialize".to_owned()]); - let mut derives = vec!["Serialize", "Deserialize"]; if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { derives.extend(s.split(",")); } + state + .imports + .entry("serde".into()) + .or_default() + .extend(vec!["Deserialize".to_owned(), "Serialize".to_owned()]); + writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; writeln!(out, "#[derive({})]", derives.join(", "))?; @@ -327,6 +327,12 @@ impl jtd_codegen::target::Target for Target { return Ok(Some(s.into())); } + let mut derives = vec!["Serialize", "Deserialize"]; + + if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { + derives.extend(s.split(",")); + } + state .imports .entry("serde".into()) @@ -335,7 +341,7 @@ impl jtd_codegen::target::Target for Target { writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; - writeln!(out, "#[derive(Serialize, Deserialize)]")?; + writeln!(out, "#[derive({})]", derives.join(", "))?; writeln!(out, "#[serde(tag = {:?})]", tag_json_name)?; writeln!(out, "pub enum {} {{", name)?; @@ -368,6 +374,12 @@ impl jtd_codegen::target::Target for Target { return Ok(Some(s.into())); } + let mut derives = vec!["Serialize", "Deserialize"]; + + if let Some(s) = metadata.get("rustCustomDerive").and_then(|v| v.as_str()) { + derives.extend(s.split(",")); + } + state .imports .entry("serde".into()) @@ -376,7 +388,7 @@ impl jtd_codegen::target::Target for Target { writeln!(out)?; write!(out, "{}", description(&metadata, 0))?; - writeln!(out, "#[derive(Serialize, Deserialize)]")?; + writeln!(out, "#[derive({})]", derives.join(", "))?; if fields.is_empty() { writeln!(out, "pub struct {} {{}}", name)?;