diff --git a/Cargo.lock b/Cargo.lock index b0ba1d1..dc8cc97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,7 @@ dependencies = [ "infer", "ion-rs 1.0.0-rc.6", "ion-schema", + "itertools 0.13.0", "lowcharts", "matches", "pager", @@ -1054,6 +1055,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1388,7 +1398,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", - "itertools", + "itertools 0.10.5", "predicates-core", ] diff --git a/Cargo.toml b/Cargo.toml index 12f7d8b..9526dc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ thiserror = "1.0.50" zstd = "0.13.0" termcolor = "1.4.1" derive_builder = "0.20.0" +itertools = "0.13.0" [target.'cfg(not(target_os = "windows"))'.dependencies] pager = "0.16.1" diff --git a/code-gen-projects/input/bad/sequence_with_enum_element/invalid_value.ion b/code-gen-projects/input/bad/sequence_with_enum_element/invalid_value.ion new file mode 100644 index 0000000..c336718 --- /dev/null +++ b/code-gen-projects/input/bad/sequence_with_enum_element/invalid_value.ion @@ -0,0 +1 @@ +[foobar] // expected values are either foo , bar or baz, found foobar. \ No newline at end of file diff --git a/code-gen-projects/input/good/sequence_with_enum_element/valid_value.ion b/code-gen-projects/input/good/sequence_with_enum_element/valid_value.ion new file mode 100644 index 0000000..3470110 --- /dev/null +++ b/code-gen-projects/input/good/sequence_with_enum_element/valid_value.ion @@ -0,0 +1 @@ +[foo, bar, baz] \ No newline at end of file diff --git a/code-gen-projects/java/code-gen-demo/src/test/java/org/example/CodeGenTest.java b/code-gen-projects/java/code-gen-demo/src/test/java/org/example/CodeGenTest.java index 318403a..5511b3c 100644 --- a/code-gen-projects/java/code-gen-demo/src/test/java/org/example/CodeGenTest.java +++ b/code-gen-projects/java/code-gen-demo/src/test/java/org/example/CodeGenTest.java @@ -137,6 +137,12 @@ void roundtripBadTestForEnumType() throws IOException { runRoundtripBadTest("/bad/enum_type", EnumType::readFrom); } + + @Test + void roundtripBadTestForSequenceWithEnumElement() throws IOException { + runRoundtripBadTest("/bad/sequence_with_enum_element", SequenceWithEnumElement::readFrom); + } + private void runRoundtripBadTest(String path, ReaderFunction readerFunction) throws IOException { File dir = new File(System.getenv("ION_INPUT") + path); String[] fileNames = dir.list(); @@ -182,6 +188,11 @@ void roundtripGoodTestForEnumType() throws IOException { runRoundtripGoodTest("/good/enum_type", EnumType::readFrom, (item, writer) -> item.writeTo(writer)); } + @Test + void roundtripGoodTestForSequenceWithEnumElement() throws IOException { + runRoundtripGoodTest("/good/sequence_with_enum_element", SequenceWithEnumElement::readFrom, (item, writer) -> item.writeTo(writer)); + } + private void runRoundtripGoodTest(String path, ReaderFunction readerFunction, WriterFunction writerFunction) throws IOException { File dir = new File(System.getenv("ION_INPUT") + path); String[] fileNames = dir.list(); diff --git a/code-gen-projects/schema/sequence_with_enum_element.isl b/code-gen-projects/schema/sequence_with_enum_element.isl new file mode 100644 index 0000000..bf09523 --- /dev/null +++ b/code-gen-projects/schema/sequence_with_enum_element.isl @@ -0,0 +1,5 @@ +type::{ + name: sequence_with_enum_element, + type: list, + element: { valid_values: [foo, bar, baz] } +} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/generator.rs b/src/bin/ion/commands/generate/generator.rs index a2b899a..d54bb70 100644 --- a/src/bin/ion/commands/generate/generator.rs +++ b/src/bin/ion/commands/generate/generator.rs @@ -1,7 +1,7 @@ use crate::commands::generate::context::{CodeGenContext, SequenceType}; use crate::commands::generate::model::{ AbstractDataType, DataModelNode, EnumBuilder, FieldPresence, FieldReference, - FullyQualifiedTypeReference, ScalarBuilder, SequenceBuilder, StructureBuilder, + FullyQualifiedTypeReference, NamespaceNode, ScalarBuilder, SequenceBuilder, StructureBuilder, WrappedScalarBuilder, WrappedSequenceBuilder, }; use crate::commands::generate::result::{ @@ -32,7 +32,7 @@ pub(crate) struct CodeGenerator<'a, L: Language> { pub(crate) tera: Tera, output: &'a Path, // This field is used by Java code generation to get the namespace for generated code. - current_type_fully_qualified_name: Vec, + current_type_fully_qualified_name: Vec, // Represents a counter for naming nested type definitions pub(crate) nested_type_counter: usize, pub(crate) data_model_store: HashMap, @@ -85,7 +85,7 @@ impl<'a> CodeGenerator<'a, RustLanguage> { } impl<'a> CodeGenerator<'a, JavaLanguage> { - pub fn new(output: &'a Path, namespace: Vec) -> CodeGenerator<'a, JavaLanguage> { + pub fn new(output: &'a Path, namespace: Vec) -> CodeGenerator<'a, JavaLanguage> { let mut tera = Tera::default(); // Add all templates using `java_templates` module constants // This allows packaging binary without the need of template resources. @@ -331,9 +331,6 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { // unwrap here is safe because all the top-level type definition always has a name let isl_type_name = isl_type.name().clone().unwrap(); self.generate_abstract_data_type(&isl_type_name, isl_type)?; - // Since the fully qualified name of this generator represents the current fully qualified name, - // remove it before generating code for the next ISL type. - L::reset_namespace(&mut self.current_type_fully_qualified_name); } Ok(()) @@ -353,7 +350,6 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { let mut code_gen_context = CodeGenContext::new(); let mut data_model_node = self.convert_isl_type_def_to_data_model_node( type_name, - field_presence, isl_type, &mut code_gen_context, true, @@ -364,8 +360,13 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { .nested_types .push(data_model_node.to_owned()); - // pop out the nested type name from the fully qualified namespace as it has been already added to the type store and to nested types - self.current_type_fully_qualified_name.pop(); + // since nested sequence does not create a separate class, all its nested types should also be added to parent code gen context + if data_model_node.is_sequence() { + parent_code_gen_context + .nested_types + .extend_from_slice(&data_model_node.nested_types); + } + match field_presence { FieldPresence::Optional => Ok(L::target_type_as_optional( data_model_node.fully_qualified_type_ref::().ok_or( @@ -392,7 +393,6 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { let data_model_node = self.convert_isl_type_def_to_data_model_node( isl_type_name, - FieldPresence::Required, // Sets `field_presence` as `Required`, as the top level type definition can not be `Optional`. isl_type, &mut code_gen_context, false, @@ -409,7 +409,15 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { ); context.insert("model", &data_model_node); - self.render_generated_code(isl_type_name, &mut context, &data_model_node) + self.render_generated_code( + isl_type_name, + &mut context, + &data_model_node, + data_model_node + .fully_qualified_type_name() + .unwrap() + .as_slice(), + ) } /// _Note: `field_presence` is only used for variably occurring type references and currently that is only supported with `fields` constraint. @@ -417,7 +425,6 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { fn convert_isl_type_def_to_data_model_node( &mut self, isl_type_name: &String, - field_presence: FieldPresence, isl_type: &IslType, code_gen_context: &mut CodeGenContext, is_nested_type: bool, @@ -482,22 +489,19 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { // TODO: verify the `occurs` value within a field, by default the fields are optional. // add current data model node into the data model store // verify if the field presence was provided as optional and set the type reference name as optional. - let type_name = match field_presence { - FieldPresence::Optional => abstract_data_type.fully_qualified_type_ref::().ok_or( - invalid_abstract_data_type_raw_error( - "Can not determine fully qualified name for the data model", - ), - )?, - FieldPresence::Required => abstract_data_type.fully_qualified_type_ref::().ok_or( - invalid_abstract_data_type_raw_error( - "Can not determine fully qualified name for the data model", - ), - )?, - }; + let type_name = abstract_data_type.fully_qualified_type_ref::(); self.data_model_store .insert(type_name, data_model_node.to_owned()); + // pop out the nested type name from the fully qualified namespace as it has been already added to the type store and to nested types + // For sequence type, it would already have popped out the nested type name. + if !data_model_node.is_sequence() { + // Since the fully qualified name of this generator represents the current fully qualified name, + // remove it before generating code for the next ISL type. + L::reset_namespace(&mut self.current_type_fully_qualified_name); + } + Ok(data_model_node) } @@ -527,12 +531,19 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { type_name: &str, context: &mut Context, data_model_node: &DataModelNode, + fully_qualified_name: &[NamespaceNode], ) -> CodeGenResult<()> { // Add namespace to tera context let mut import_context = Context::new(); - let namespace_ref = self.current_type_fully_qualified_name.as_slice(); - context.insert("namespace", &namespace_ref[0..namespace_ref.len() - 1]); - import_context.insert("namespace", &namespace_ref[0..namespace_ref.len() - 1]); + + context.insert( + "namespace", + &fully_qualified_name[0..fully_qualified_name.len() - 1], + ); + import_context.insert( + "namespace", + &fully_qualified_name[0..fully_qualified_name.len() - 1], + ); // Render or generate file for the template with the given context let template: &Template = &data_model_node.try_into()?; @@ -580,7 +591,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { L::target_type(&schema_type) .as_ref() .map(|type_name| FullyQualifiedTypeReference { - type_name: vec![type_name.to_string()], + type_name: vec![NamespaceNode::Type(type_name.to_string())], parameters: vec![], }) .map(|t| { @@ -838,6 +849,12 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { for constraint in constraints { match constraint.constraint() { IslConstraintValue::Type(isl_type) => { + // Nested/Anonymous types are not allowed within wrapped scalar models + if matches!(isl_type, IslTypeRef::Anonymous(_, _)) { + return invalid_abstract_data_type_error( + "Nested types are not supported within wrapped scalar types(i.e. within top level ISL type definition's `type` constraint)", + ); + } let type_name = self.handle_duplicate_constraint( found_base_type, "type", @@ -1016,6 +1033,10 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { parent_isl_type: &IslType, ) -> CodeGenResult { let mut sequence_builder = SequenceBuilder::default(); + // For nested sequence type remove the anonymous type name from current fully qualified name + // Nested sequence does not create a separate class, so the anonymous type name shouldn't be used for the fully qualified type name. + L::reset_namespace(&mut self.current_type_fully_qualified_name); + sequence_builder.source(parent_isl_type.to_owned()); for constraint in constraints { match constraint.constraint() { @@ -1081,25 +1102,25 @@ mod isl_to_model_tests { // Initialize code generator for Java let mut java_code_generator = CodeGenerator::::new( Path::new("./"), - vec!["org".to_string(), "example".to_string()], + vec![ + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + ], ); let data_model_node = java_code_generator.convert_isl_type_def_to_data_model_node( &"my_struct".to_string(), - FieldPresence::Required, &isl_type, &mut CodeGenContext::new(), false, )?; let abstract_data_type = data_model_node.code_gen_type.unwrap(); assert_eq!( - abstract_data_type - .fully_qualified_type_ref::() - .unwrap(), + abstract_data_type.fully_qualified_type_ref::(), FullyQualifiedTypeReference { type_name: vec![ - "org".to_string(), - "example".to_string(), - "MyStruct".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyStruct".to_string()) ], parameters: vec![] } @@ -1109,9 +1130,9 @@ mod isl_to_model_tests { assert_eq!( structure.name, vec![ - "org".to_string(), - "example".to_string(), - "MyStruct".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyStruct".to_string()) ] ); assert!(!structure.is_closed); @@ -1123,7 +1144,7 @@ mod isl_to_model_tests { "foo".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![] }, FieldPresence::Optional @@ -1133,7 +1154,7 @@ mod isl_to_model_tests { "bar".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["Integer".to_string()], + type_name: vec![NamespaceNode::Type("Integer".to_string())], parameters: vec![] }, FieldPresence::Optional @@ -1170,25 +1191,25 @@ mod isl_to_model_tests { // Initialize code generator for Java let mut java_code_generator = CodeGenerator::::new( Path::new("./"), - vec!["org".to_string(), "example".to_string()], + vec![ + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + ], ); let data_model_node = java_code_generator.convert_isl_type_def_to_data_model_node( &"my_nested_struct".to_string(), - FieldPresence::Required, &isl_type, &mut CodeGenContext::new(), false, )?; let abstract_data_type = data_model_node.code_gen_type.unwrap(); assert_eq!( - abstract_data_type - .fully_qualified_type_ref::() - .unwrap(), + abstract_data_type.fully_qualified_type_ref::(), FullyQualifiedTypeReference { type_name: vec![ - "org".to_string(), - "example".to_string(), - "MyNestedStruct".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyNestedStruct".to_string()) ], parameters: vec![] } @@ -1198,9 +1219,9 @@ mod isl_to_model_tests { assert_eq!( structure.name, vec![ - "org".to_string(), - "example".to_string(), - "MyNestedStruct".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyNestedStruct".to_string()) ] ); assert!(!structure.is_closed); @@ -1213,10 +1234,10 @@ mod isl_to_model_tests { FieldReference( FullyQualifiedTypeReference { type_name: vec![ - "org".to_string(), - "example".to_string(), - "MyNestedStruct".to_string(), - "NestedType1".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyNestedStruct".to_string()), + NamespaceNode::Type("NestedType1".to_string()) ], parameters: vec![] }, @@ -1227,7 +1248,7 @@ mod isl_to_model_tests { "bar".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["Integer".to_string()], + type_name: vec![NamespaceNode::Type("Integer".to_string())], parameters: vec![] }, FieldPresence::Optional @@ -1242,15 +1263,15 @@ mod isl_to_model_tests { .as_ref() .unwrap() .fully_qualified_type_ref::(), - Some(FullyQualifiedTypeReference { + FullyQualifiedTypeReference { type_name: vec![ - "org".to_string(), - "example".to_string(), - "MyNestedStruct".to_string(), - "NestedType1".to_string() + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("MyNestedStruct".to_string()), + NamespaceNode::Type("NestedType1".to_string()) ], parameters: vec![] - }) + } ); } Ok(()) diff --git a/src/bin/ion/commands/generate/mod.rs b/src/bin/ion/commands/generate/mod.rs index b234e57..b801685 100644 --- a/src/bin/ion/commands/generate/mod.rs +++ b/src/bin/ion/commands/generate/mod.rs @@ -7,6 +7,7 @@ mod utils; mod model; use crate::commands::generate::generator::CodeGenerator; +use crate::commands::generate::model::NamespaceNode; use crate::commands::generate::utils::{JavaLanguage, RustLanguage}; use crate::commands::IonCliCommand; use anyhow::{bail, Result}; @@ -124,7 +125,7 @@ impl IonCliCommand for GenerateCommand { match language { "java" => { Self::print_java_code_gen_warnings(); - CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| s.to_string()).collect()) + CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| NamespaceNode::Package(s.to_string())).collect()) .generate_code_for_authorities(&authorities, &mut schema_system)? }, "rust" => { @@ -143,7 +144,7 @@ impl IonCliCommand for GenerateCommand { match language { "java" => { Self::print_java_code_gen_warnings(); - CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| s.to_string()).collect()).generate_code_for_schema(&mut schema_system, schema_id)? + CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| NamespaceNode::Package(s.to_string())).collect()).generate_code_for_schema(&mut schema_system, schema_id)? }, "rust" => { Self::print_rust_code_gen_warnings(); diff --git a/src/bin/ion/commands/generate/model.rs b/src/bin/ion/commands/generate/model.rs index 8ef7f88..69ae640 100644 --- a/src/bin/ion/commands/generate/model.rs +++ b/src/bin/ion/commands/generate/model.rs @@ -1,5 +1,6 @@ use derive_builder::Builder; use ion_schema::isl::isl_type::IslType; +use itertools::Itertools; use std::collections::{BTreeSet, HashMap}; use std::fmt::Debug; // This module contains a data model that the code generator can use to render a template based on the type of the model. @@ -76,7 +77,13 @@ impl DataModelNode { pub fn fully_qualified_type_ref(&mut self) -> Option { self.code_gen_type .as_ref() - .and_then(|t| t.fully_qualified_type_ref::()) + .map(|t| t.fully_qualified_type_ref::()) + } + + pub fn fully_qualified_type_name(&self) -> Option { + self.code_gen_type + .as_ref() + .and_then(|t| t.fully_qualified_type_name()) } } @@ -84,7 +91,23 @@ impl DataModelNode { /// e.g. For a `Foo` class in `org.example` namespace /// In Java, `org.example.Foo` /// In Rust, `org::example::Foo` -type FullyQualifiedTypeName = Vec; +type FullyQualifiedTypeName = Vec; + +/// Represents a node in the fully qualified namespace path +#[derive(Debug, Clone, PartialEq, Serialize, Hash, Eq)] +pub enum NamespaceNode { + Package(String), // represents a package or module name + Type(String), // represents a class, struct or enum type name +} + +impl NamespaceNode { + pub fn name(&self) -> &String { + match self { + NamespaceNode::Package(name) => name, + NamespaceNode::Type(name) => name, + } + } +} /// Represents a fully qualified type name for a type reference #[derive(Debug, Clone, PartialEq, Serialize, Hash, Eq)] @@ -122,7 +145,21 @@ impl TryFrom<&Value> for FullyQualifiedTypeReference { .as_array() .unwrap() .iter() - .map(|s| s.as_str().unwrap().to_string()) + .map(|s| { + let namespace_node = s.as_object().unwrap(); + if let Some(package_name) = namespace_node.get("Package") { + NamespaceNode::Package(package_name.as_str().unwrap().to_string()) + } else { + NamespaceNode::Type( + namespace_node + .get("Type") + .unwrap() + .as_str() + .unwrap() + .to_string(), + ) + } + }) .collect(); } else { let parameters_result: Result, tera::Error> = @@ -151,7 +188,11 @@ impl FullyQualifiedTypeReference { /// Provides string representation of this `FullyQualifiedTypeReference` pub fn string_representation(&self) -> String { if self.parameters.is_empty() { - return self.type_name.join(L::namespace_separator()).to_string(); + return self + .type_name + .iter() + .map(|n| n.name()) + .join(L::namespace_separator()); } let parameters = self .parameters @@ -161,7 +202,10 @@ impl FullyQualifiedTypeReference { .join(", "); format!( "{}<{}>", - self.type_name.join(L::namespace_separator()), + self.type_name + .iter() + .map(|n| n.name()) + .join(L::namespace_separator()), parameters ) } @@ -211,20 +255,30 @@ impl AbstractDataType { } } - pub fn fully_qualified_type_ref(&self) -> Option { + pub fn fully_qualified_type_ref(&self) -> FullyQualifiedTypeReference { match self { - AbstractDataType::WrappedScalar(w) => { - Some(w.fully_qualified_type_name().to_owned().into()) - } - AbstractDataType::Scalar(s) => Some(s.base_type.to_owned()), + AbstractDataType::WrappedScalar(w) => w.fully_qualified_type_name().to_owned().into(), + AbstractDataType::Scalar(s) => s.base_type.to_owned(), AbstractDataType::Sequence(seq) => { - Some(L::target_type_as_sequence(seq.element_type.to_owned())) + L::target_type_as_sequence(seq.element_type.to_owned()) } AbstractDataType::WrappedSequence(seq) => { - Some(L::target_type_as_sequence(seq.element_type.to_owned())) + L::target_type_as_sequence(seq.element_type.to_owned()) } - AbstractDataType::Structure(structure) => Some(structure.name.to_owned().into()), - AbstractDataType::Enum(enum_type) => Some(enum_type.name.to_owned().into()), + AbstractDataType::Structure(structure) => structure.name.to_owned().into(), + AbstractDataType::Enum(enum_type) => enum_type.name.to_owned().into(), + } + } + + pub fn fully_qualified_type_name(&self) -> Option { + // nested types would return None + match self { + AbstractDataType::WrappedScalar(w) => Some(w.fully_qualified_type_name().to_owned()), + AbstractDataType::Scalar(_) => None, + AbstractDataType::Sequence(_) => None, + AbstractDataType::WrappedSequence(seq) => Some(seq.name.to_owned()), + AbstractDataType::Structure(structure) => Some(structure.name.to_owned()), + AbstractDataType::Enum(enum_type) => Some(enum_type.name.to_owned()), } } } @@ -501,7 +555,7 @@ mod model_tests { fn scalar_builder_test() { let expected_scalar = Scalar { base_type: FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }, doc_comment: Some("This is scalar type".to_string()), @@ -512,7 +566,7 @@ mod model_tests { // sets all the information about the scalar type scalar_builder - .base_type(vec!["String".to_string()]) + .base_type(vec![NamespaceNode::Type("String".to_string())]) .doc_comment(Some("This is scalar type".to_string())) .source(anonymous_type(vec![type_constraint(named_type_ref( "string", @@ -525,9 +579,9 @@ mod model_tests { #[test] fn wrapped_scalar_builder_test() { let expected_scalar = WrappedScalar { - name: vec!["Foo".to_string()], + name: vec![NamespaceNode::Type("Foo".to_string())], base_type: FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }, doc_comment: Some("This is scalar type".to_string()), @@ -538,9 +592,9 @@ mod model_tests { // sets all the information about the scalar type scalar_builder - .name(vec!["Foo".to_string()]) + .name(vec![NamespaceNode::Type("Foo".to_string())]) .base_type(FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }) .doc_comment(Some("This is scalar type".to_string())) @@ -557,7 +611,7 @@ mod model_tests { let expected_seq = Sequence { doc_comment: Some("This is sequence type of strings".to_string()), element_type: FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }, sequence_type: SequenceType::List, @@ -583,7 +637,7 @@ mod model_tests { // sets the `element_type` for the sequence seq_builder.element_type(FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }); @@ -594,7 +648,11 @@ mod model_tests { #[test] fn struct_builder_test() { let expected_struct = Structure { - name: vec!["org".to_string(), "example".to_string(), "Foo".to_string()], + name: vec![ + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("Foo".to_string()), + ], doc_comment: Some("This is a structure".to_string()), is_closed: false, fields: HashMap::from_iter(vec![ @@ -602,7 +660,7 @@ mod model_tests { "foo".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }, FieldPresence::Required, @@ -612,7 +670,7 @@ mod model_tests { "bar".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["int".to_string()], + type_name: vec![NamespaceNode::Type("int".to_string())], parameters: vec![], }, FieldPresence::Required, @@ -648,9 +706,9 @@ mod model_tests { // sets all the information about the structure struct_builder .name(vec![ - "org".to_string(), - "example".to_string(), - "Foo".to_string(), + NamespaceNode::Package("org".to_string()), + NamespaceNode::Package("example".to_string()), + NamespaceNode::Type("Foo".to_string()), ]) .doc_comment(Some("This is a structure".to_string())) .is_closed(false) @@ -659,7 +717,7 @@ mod model_tests { "foo".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["String".to_string()], + type_name: vec![NamespaceNode::Type("String".to_string())], parameters: vec![], }, FieldPresence::Required, @@ -669,7 +727,7 @@ mod model_tests { "bar".to_string(), FieldReference( FullyQualifiedTypeReference { - type_name: vec!["int".to_string()], + type_name: vec![NamespaceNode::Type("int".to_string())], parameters: vec![], }, FieldPresence::Required, diff --git a/src/bin/ion/commands/generate/templates/java/class.templ b/src/bin/ion/commands/generate/templates/java/class.templ index f0663f7..28698f7 100644 --- a/src/bin/ion/commands/generate/templates/java/class.templ +++ b/src/bin/ion/commands/generate/templates/java/class.templ @@ -5,7 +5,7 @@ {% macro class(model, is_nested) %} {% if is_nested == false %} -{% set full_namespace = namespace | join(sep=".") %} +{% set full_namespace = namespace | map(attribute="Package") | join(sep=".") %} package {{ full_namespace }}; import com.amazon.ion.IonReader; @@ -147,8 +147,7 @@ import java.io.IOException; } {% for inline_type in model.nested_types -%} - {% set is_nested = true %} - {{ macros::nested_type(model=inline_type, is_nested=is_nested) }} + {{ macros::nested_type(model=inline_type, is_nested=true) }} {% endfor -%} } {% endmacro model %} diff --git a/src/bin/ion/commands/generate/templates/java/enum.templ b/src/bin/ion/commands/generate/templates/java/enum.templ index c3ea211..15a21e0 100644 --- a/src/bin/ion/commands/generate/templates/java/enum.templ +++ b/src/bin/ion/commands/generate/templates/java/enum.templ @@ -1,4 +1,4 @@ -{% set full_namespace = namespace | join(sep=".") %} +{% set full_namespace = namespace | map(attribute="Package") | join(sep=".") %} {% if is_nested == false %} package {{ full_namespace }}; import com.amazon.ion.IonReader; diff --git a/src/bin/ion/commands/generate/templates/java/scalar.templ b/src/bin/ion/commands/generate/templates/java/scalar.templ index c71f5f5..98d0a40 100644 --- a/src/bin/ion/commands/generate/templates/java/scalar.templ +++ b/src/bin/ion/commands/generate/templates/java/scalar.templ @@ -1,5 +1,7 @@ +{% import "nested_type.templ" as macros %} + {% macro scalar(model) %} -{% set full_namespace = namespace | join(sep=".") %} +{% set full_namespace = namespace | map(attribute="Package") | join(sep=".") %} package {{ full_namespace }}; import com.amazon.ion.IonReader; diff --git a/src/bin/ion/commands/generate/templates/java/sequence.templ b/src/bin/ion/commands/generate/templates/java/sequence.templ index 1537830..b17bcf9 100644 --- a/src/bin/ion/commands/generate/templates/java/sequence.templ +++ b/src/bin/ion/commands/generate/templates/java/sequence.templ @@ -1,7 +1,9 @@ +{% import "nested_type.templ" as macros %} + {% macro sequence(model) %} {% if is_nested == false %} -{% set full_namespace = namespace | join(sep=".") %} +{% set full_namespace = namespace | map(attribute="Package") | join(sep=".") %} package {{ full_namespace }}; import com.amazon.ion.IonReader; @@ -78,6 +80,10 @@ class {{ model.name }} { } writer.stepOut(); } + + {% for inline_type in model.nested_types -%} + {{ macros::nested_type(model=inline_type, is_nested=true) }} + {% endfor -%} } {% endmacro %} {{ self::sequence(model=model) }} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/java/util_macros.templ b/src/bin/ion/commands/generate/templates/java/util_macros.templ index 731f967..f2eb7e2 100644 --- a/src/bin/ion/commands/generate/templates/java/util_macros.templ +++ b/src/bin/ion/commands/generate/templates/java/util_macros.templ @@ -12,7 +12,7 @@ while (reader.hasNext()) { reader.next(); {% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | is_built_in_type == false %} - {{ field_name | camel }}List.add({{ field_value_model.code_gen_type["Sequence"].element_type }}.readFrom(reader)); + {{ field_name | camel }}List.add({{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name }}.readFrom(reader)); {% elif field_value_model.code_gen_type["Sequence"].element_type == "bytes[]" %} {{ field_name | camel }}List.add(reader.newBytes()); {% else %} diff --git a/src/bin/ion/commands/generate/templates/rust/scalar.templ b/src/bin/ion/commands/generate/templates/rust/scalar.templ index c6c0b0e..b25acd1 100644 --- a/src/bin/ion/commands/generate/templates/rust/scalar.templ +++ b/src/bin/ion/commands/generate/templates/rust/scalar.templ @@ -1,3 +1,5 @@ +{% import "nested_type.templ" as macros %} + {# Verify that the abstract data type is a scalar type and store information for this scalar value #} {% set scalar_info = model.code_gen_type["WrappedScalar"] %} {% set base_type = scalar_info["base_type"] | fully_qualified_type_name %} diff --git a/src/bin/ion/commands/generate/templates/rust/sequence.templ b/src/bin/ion/commands/generate/templates/rust/sequence.templ index 4a4555d..30382f9 100644 --- a/src/bin/ion/commands/generate/templates/rust/sequence.templ +++ b/src/bin/ion/commands/generate/templates/rust/sequence.templ @@ -1,5 +1,8 @@ +{% import "nested_type.templ" as macros %} + {% set sequence_info = model.code_gen_type["WrappedSequence"] %} + use {{ model.name | snake }}::{{ model.name }}; pub mod {{ model.name | snake }} { @@ -39,7 +42,7 @@ pub mod {{ model.name | snake }} { while reader.next()? != StreamItem::Nothing { {% if sequence_info["element_type"] | fully_qualified_type_name | is_built_in_type == false %} - values.push({{ sequence_info["element_type"] }}::read_from(reader)?); + values.push({{ sequence_info["element_type"] | fully_qualified_type_name }}::read_from(reader)?); {% else %} values.push(reader.read_{% if field.source is defined and field.source == "symbol" %}symbol()?.text().unwrap(){% else %}{{ sequence_info["element_type"] | fully_qualified_type_name | lower | replace(from="string", to ="str") }}()?{% endif %}{% if sequence_info["element_type"] | fully_qualified_type_name | lower== "string" %} .to_string() {% endif %}); {% endif %} @@ -63,4 +66,9 @@ pub mod {{ model.name | snake }} { Ok(()) } } + + + {% for inline_type in model.nested_types -%} + {{ macros::nested_type(model=inline_type, is_nested=true) }} + {% endfor -%} } diff --git a/src/bin/ion/commands/generate/templates/rust/struct.templ b/src/bin/ion/commands/generate/templates/rust/struct.templ index de14e79..29e25dc 100644 --- a/src/bin/ion/commands/generate/templates/rust/struct.templ +++ b/src/bin/ion/commands/generate/templates/rust/struct.templ @@ -87,8 +87,7 @@ pub mod {{ model.name | snake }} { } {% for inline_type in model.nested_types -%} - {% set is_nested = true %} - {{ macros::nested_type(model=inline_type, is_nested=is_nested) }} + {{ macros::nested_type(model=inline_type, is_nested=true) }} {% endfor -%} } {% endmacro struct %} diff --git a/src/bin/ion/commands/generate/utils.rs b/src/bin/ion/commands/generate/utils.rs index b503888..6961dad 100644 --- a/src/bin/ion/commands/generate/utils.rs +++ b/src/bin/ion/commands/generate/utils.rs @@ -1,8 +1,9 @@ use crate::commands::generate::model::{ - AbstractDataType, DataModelNode, FullyQualifiedTypeReference, + AbstractDataType, DataModelNode, FullyQualifiedTypeReference, NamespaceNode, }; use crate::commands::generate::result::{invalid_abstract_data_type_error, CodeGenError}; use convert_case::{Case, Casing}; +use itertools::Itertools; use std::fmt::{Display, Formatter}; pub trait Language { @@ -69,10 +70,14 @@ pub trait Language { /// ``` /// To add `NestedType` into the namespace path, `is_nested_type` helps remove any prior types form the path and add this current type. /// i.e. given namespace path as `foo::Foo`, it will first remove `Foo` and then add the current type as `foo::nested_type::NestedType`. - fn add_type_to_namespace(is_nested_type: bool, type_name: &str, namespace: &mut Vec); + fn add_type_to_namespace( + is_nested_type: bool, + type_name: &str, + namespace: &mut Vec, + ); /// Resets the namespace when code generation is complete for a single ISL type - fn reset_namespace(namespace: &mut Vec); + fn reset_namespace(namespace: &mut Vec); /// Returns the `FullyQualifiedReference` that represents the target type as optional in the given programming language /// e.g. In Java, it will return "java.util.Optional" @@ -121,20 +126,20 @@ impl Language for JavaLanguage { ) { Some(wrapper_name) => FullyQualifiedTypeReference { type_name: vec![ - "java".to_string(), - "util".to_string(), - "ArrayList".to_string(), + NamespaceNode::Package("java".to_string()), + NamespaceNode::Package("util".to_string()), + NamespaceNode::Type("ArrayList".to_string()), ], parameters: vec![FullyQualifiedTypeReference { - type_name: vec![wrapper_name], + type_name: vec![NamespaceNode::Type(wrapper_name)], parameters: vec![], }], }, None => FullyQualifiedTypeReference { type_name: vec![ - "java".to_string(), - "util".to_string(), - "ArrayList".to_string(), + NamespaceNode::Package("java".to_string()), + NamespaceNode::Package("util".to_string()), + NamespaceNode::Type("ArrayList".to_string()), ], parameters: vec![target_type], }, @@ -149,7 +154,7 @@ impl Language for JavaLanguage { } fn fully_qualified_type_ref(name: &FullyQualifiedTypeReference) -> String { - name.type_name.join(".") + name.type_name.iter().map(|n| n.name()).join(".") } fn template_name(template: &Template) -> String { @@ -165,11 +170,15 @@ impl Language for JavaLanguage { "." } - fn add_type_to_namespace(_is_nested_type: bool, type_name: &str, namespace: &mut Vec) { - namespace.push(type_name.to_case(Case::UpperCamel)) + fn add_type_to_namespace( + _is_nested_type: bool, + type_name: &str, + namespace: &mut Vec, + ) { + namespace.push(NamespaceNode::Type(type_name.to_case(Case::UpperCamel))) } - fn reset_namespace(namespace: &mut Vec) { + fn reset_namespace(namespace: &mut Vec) { // resets the namespace by removing current abstract dta type name namespace.pop(); } @@ -181,7 +190,7 @@ impl Language for JavaLanguage { &target_type.string_representation::(), ) { Some(wrapper_name) => FullyQualifiedTypeReference { - type_name: vec![wrapper_name], + type_name: vec![NamespaceNode::Type(wrapper_name)], parameters: vec![], }, None => target_type, @@ -271,7 +280,7 @@ impl Language for RustLanguage { target_type: FullyQualifiedTypeReference, ) -> FullyQualifiedTypeReference { FullyQualifiedTypeReference { - type_name: vec!["Vec".to_string()], + type_name: vec![NamespaceNode::Type("Vec".to_string())], parameters: vec![target_type], } } @@ -284,7 +293,7 @@ impl Language for RustLanguage { } fn fully_qualified_type_ref(name: &FullyQualifiedTypeReference) -> String { - name.type_name.join("::") + name.type_name.iter().map(|n| n.name()).join("::") } fn template_name(template: &Template) -> String { @@ -304,7 +313,11 @@ impl Language for RustLanguage { "::" } - fn add_type_to_namespace(is_nested_type: bool, type_name: &str, namespace: &mut Vec) { + fn add_type_to_namespace( + is_nested_type: bool, + type_name: &str, + namespace: &mut Vec, + ) { // e.g. For example there is a `NestedType` inside `Foo` struct. Rust code generation also generates similar modules for the generated structs. // ```rust // mod foo { @@ -319,19 +332,35 @@ impl Language for RustLanguage { // } // ``` if is_nested_type { - // Assume we have the current namespace as `foo::Foo` - // then the following step will remove `Foo` from the path for nested type. - // So that the final namespace path for `NestedType` will become `foo::nested_type::NestedType` - namespace.pop(); // Remove the parent struct/enum + if let Some(last_value) = namespace.last() { + // Assume we have the current namespace as `foo::Foo` + // then the following step will remove `Foo` from the path for nested type. + // So that the final namespace path for `NestedType` will become `foo::nested_type::NestedType` + if !matches!(last_value, NamespaceNode::Package(_)) { + // if the last value is not module name then pop the type name from namespace + namespace.pop(); // Remove the parent struct/enum + } + } } - namespace.push(type_name.to_case(Case::Snake)); // Add this type's module name to the namespace path - namespace.push(type_name.to_case(Case::UpperCamel)) // Add this type itself to the namespace path + namespace.push(NamespaceNode::Package(type_name.to_case(Case::Snake))); // Add this type's module name to the namespace path + namespace.push(NamespaceNode::Type(type_name.to_case(Case::UpperCamel))) + // Add this type itself to the namespace path } - fn reset_namespace(namespace: &mut Vec) { + fn reset_namespace(namespace: &mut Vec) { // Resets the namespace by removing current abstract data type name and module name - namespace.pop(); - namespace.pop(); + if let Some(last_value) = namespace.last() { + // Check if it is a type then pop the type and module + if matches!(last_value, NamespaceNode::Package(_)) { + // if this is a module then only pop once for the module + namespace.pop(); + } else if matches!(last_value, NamespaceNode::Type(_)) { + namespace.pop(); + if !namespace.is_empty() { + namespace.pop(); + } + } + } } fn target_type_as_optional(