diff --git a/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_element_type.ion b/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_element_type.ion new file mode 100644 index 0000000..3c2f0c3 --- /dev/null +++ b/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_element_type.ion @@ -0,0 +1 @@ +[ mango ] // expected apple, banana or strawberry, found mango \ No newline at end of file diff --git a/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_type.ion b/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_type.ion new file mode 100644 index 0000000..be76528 --- /dev/null +++ b/code-gen-projects/input/bad/sequence_with_import/mismatched_sequence_type.ion @@ -0,0 +1 @@ +(apple banana) // expected list, found sexp \ No newline at end of file diff --git a/code-gen-projects/input/bad/struct_with_inline_import/mismatched_import_type.ion b/code-gen-projects/input/bad/struct_with_inline_import/mismatched_import_type.ion new file mode 100644 index 0000000..d2d179a --- /dev/null +++ b/code-gen-projects/input/bad/struct_with_inline_import/mismatched_import_type.ion @@ -0,0 +1,5 @@ +// simple struct with type mismatched import field +{ + A: "hello", + B: false, // expected field type symbol +} diff --git a/code-gen-projects/input/good/sequence_with_import/empty_sequence.ion b/code-gen-projects/input/good/sequence_with_import/empty_sequence.ion new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/code-gen-projects/input/good/sequence_with_import/empty_sequence.ion @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/code-gen-projects/input/good/sequence_with_import/valid_elements.ion b/code-gen-projects/input/good/sequence_with_import/valid_elements.ion new file mode 100644 index 0000000..7fee197 --- /dev/null +++ b/code-gen-projects/input/good/sequence_with_import/valid_elements.ion @@ -0,0 +1 @@ +[ apple, strawberry ] \ No newline at end of file diff --git a/code-gen-projects/input/good/struct_with_inline_import/valid_fields.ion b/code-gen-projects/input/good/struct_with_inline_import/valid_fields.ion new file mode 100644 index 0000000..740fbed --- /dev/null +++ b/code-gen-projects/input/good/struct_with_inline_import/valid_fields.ion @@ -0,0 +1,5 @@ +// simple struct with all valid fields +{ + A: "hello", + B: apple, +} diff --git a/code-gen-projects/input/good/struct_with_inline_import/valid_optional_fields.ion b/code-gen-projects/input/good/struct_with_inline_import/valid_optional_fields.ion new file mode 100644 index 0000000..a70e671 --- /dev/null +++ b/code-gen-projects/input/good/struct_with_inline_import/valid_optional_fields.ion @@ -0,0 +1,5 @@ +// simple struct with all valid fields +{ + A: "hello", + // B: apple, // since `B` is an optional field, this is a valid struct +} diff --git a/code-gen-projects/input/good/struct_with_inline_import/valid_unordered_fields.ion b/code-gen-projects/input/good/struct_with_inline_import/valid_unordered_fields.ion new file mode 100644 index 0000000..0b8f4ed --- /dev/null +++ b/code-gen-projects/input/good/struct_with_inline_import/valid_unordered_fields.ion @@ -0,0 +1,5 @@ +// struct with unordered fields +{ + B: banana, + A: "hello", +} 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 eb10f69..ba8a4b9 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 @@ -150,12 +150,21 @@ void roundtripBadTestForEnumType() throws IOException { runRoundtripBadTest("/bad/enum_type", EnumType::readFrom); } - @Test void roundtripBadTestForSequenceWithEnumElement() throws IOException { runRoundtripBadTest("/bad/sequence_with_enum_element", SequenceWithEnumElement::readFrom); } + @Test + void roundtripBadTestForSequenceWithImport() throws IOException { + runRoundtripBadTest("/bad/sequence_with_import", SequenceWithImport::readFrom); + } + + @Test + void roundtripBadTestForStructWithInlineImport() throws IOException { + runRoundtripBadTest("/bad/struct_with_inline_import", StructWithInlineImport::readFrom); + } + private void runRoundtripBadTest(String path, ReaderFunction readerFunction) throws IOException { File dir = new File(System.getenv("ION_INPUT") + path); String[] fileNames = dir.list(); @@ -206,6 +215,16 @@ void roundtripGoodTestForSequenceWithEnumElement() throws IOException { runRoundtripGoodTest("/good/sequence_with_enum_element", SequenceWithEnumElement::readFrom, (item, writer) -> item.writeTo(writer)); } + @Test + void roundtripGoodTestForSequenceWithImport() throws IOException { + runRoundtripGoodTest("/good/sequence_with_import", SequenceWithImport::readFrom, (item, writer) -> item.writeTo(writer)); + } + + @Test + void roundtripGoodTestForStructWithInlineImport() throws IOException { + runRoundtripGoodTest("/good/struct_with_inline_import", StructWithInlineImport::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_import.isl b/code-gen-projects/schema/sequence_with_import.isl new file mode 100644 index 0000000..9b7051e --- /dev/null +++ b/code-gen-projects/schema/sequence_with_import.isl @@ -0,0 +1,13 @@ +schema_header::{ + imports: [ + { id: "utils/fruits.isl", type: fruits } + ] +} + +type::{ + name: sequence_with_import, + type: list, + element: fruits +} + +schema_footer::{} \ No newline at end of file diff --git a/code-gen-projects/schema/struct_with_inline_import.isl b/code-gen-projects/schema/struct_with_inline_import.isl new file mode 100644 index 0000000..7522411 --- /dev/null +++ b/code-gen-projects/schema/struct_with_inline_import.isl @@ -0,0 +1,8 @@ +type::{ + name: struct_with_inline_import, + type: struct, + fields: { + A: string, + B: { id: "utils/fruits.isl", type: fruits } + } +} diff --git a/code-gen-projects/schema/utils/fruits.isl b/code-gen-projects/schema/utils/fruits.isl new file mode 100644 index 0000000..7652448 --- /dev/null +++ b/code-gen-projects/schema/utils/fruits.isl @@ -0,0 +1,4 @@ +type::{ + name: fruits, + valid_values: [apple, banana, strawberry] +} \ 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 853bd36..d154a94 100644 --- a/src/bin/ion/commands/generate/generator.rs +++ b/src/bin/ion/commands/generate/generator.rs @@ -277,30 +277,51 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { schema_system: &mut SchemaSystem, ) -> CodeGenResult<()> { for authority in authorities { - // Sort the directory paths to ensure nested type names are always ordered based - // on directory path. (nested type name uses a counter in its name to represent that type) - let mut paths = fs::read_dir(authority)?.collect::, _>>()?; - paths.sort_by_key(|dir| dir.path()); - for schema_file in paths { - let schema_file_path = schema_file.path(); - let schema_id = schema_file_path.file_name().unwrap().to_str().unwrap(); - - let schema = schema_system.load_isl_schema(schema_id).unwrap(); - - self.generate(schema)?; - } + self.generate_code_for_directory(authority, None, schema_system)?; } Ok(()) } - /// Generates code for given Ion Schema - pub fn generate_code_for_schema( + /// Helper method to generate code for all schema files in a directory + /// `relative_path` is used to provide a relative path to the authority for a nested directory + pub fn generate_code_for_directory>( &mut self, + directory: P, + relative_path: Option<&str>, schema_system: &mut SchemaSystem, - schema_id: &str, ) -> CodeGenResult<()> { - let schema = schema_system.load_isl_schema(schema_id).unwrap(); - self.generate(schema) + let paths = fs::read_dir(&directory)?.collect::, _>>()?; + for schema_file in paths { + let schema_file_path = schema_file.path(); + + // if this is a nested directory then load schema files from it + if schema_file_path.is_dir() { + self.generate_code_for_directory( + &schema_file_path, + Some( + schema_file_path + .strip_prefix(&directory) + .unwrap() + .to_str() + .unwrap(), + ), + schema_system, + )?; + } else { + let schema = if let Some(path) = relative_path { + let relative_path_with_schema_id = Path::new(path) + .join(schema_file_path.file_name().unwrap().to_str().unwrap()); + schema_system + .load_isl_schema(relative_path_with_schema_id.as_path().to_str().unwrap()) + } else { + schema_system + .load_isl_schema(schema_file_path.file_name().unwrap().to_str().unwrap()) + }?; + self.generate(schema)?; + } + } + + Ok(()) } fn generate(&mut self, schema: IslSchema) -> CodeGenResult<()> { @@ -328,7 +349,6 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { let isl_type_name = isl_type.name().clone().unwrap(); self.generate_abstract_data_type(&isl_type_name, isl_type)?; } - Ok(()) } @@ -597,24 +617,10 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { type_name_suggestion: Option<&str>, ) -> CodeGenResult> { Ok(match isl_type_ref { - IslTypeRef::Named(name, _) => { - let schema_type: IonSchemaType = name.into(); - L::target_type(&schema_type) - .as_ref() - .map(|type_name| FullyQualifiedTypeReference { - type_name: vec![NamespaceNode::Type(type_name.to_string())], - parameters: vec![], - }) - .map(|t| { - if field_presence == FieldPresence::Optional { - L::target_type_as_optional(t) - } else { - t - } - }) - } - IslTypeRef::TypeImport(_, _) => { - unimplemented!("Imports in schema are not supported yet!"); + IslTypeRef::Named(name, _) => Self::target_type_for(field_presence, name), + IslTypeRef::TypeImport(isl_import_type, _) => { + let name = isl_import_type.type_name(); + Self::target_type_for(field_presence, name) } IslTypeRef::Anonymous(type_def, _) => { let name = type_name_suggestion.map(|t| t.to_string()).ok_or( @@ -637,6 +643,27 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { }) } + /// Returns the target type based on given ISL type name and field presence + fn target_type_for( + field_presence: FieldPresence, + name: &String, + ) -> Option { + let schema_type: IonSchemaType = name.into(); + L::target_type(&schema_type) + .as_ref() + .map(|type_name| FullyQualifiedTypeReference { + type_name: vec![NamespaceNode::Type(type_name.to_string())], + parameters: vec![], + }) + .map(|t| { + if field_presence == FieldPresence::Optional { + L::target_type_as_optional(t) + } else { + t + } + }) + } + /// Returns error if duplicate constraints are present based `found_constraint` flag fn handle_duplicate_constraint( &mut self, diff --git a/src/bin/ion/commands/generate/mod.rs b/src/bin/ion/commands/generate/mod.rs index b801685..48fec8e 100644 --- a/src/bin/ion/commands/generate/mod.rs +++ b/src/bin/ion/commands/generate/mod.rs @@ -45,12 +45,6 @@ impl IonCliCommand for GenerateCommand { .short('o') .help("Output directory [default: current directory]"), ) - .arg( - Arg::new("schema") - .long("schema") - .short('s') - .help("Schema file name or schema id"), - ) // `--namespace` is required when Java language is specified for code generation .arg( Arg::new("namespace") @@ -118,49 +112,26 @@ impl IonCliCommand for GenerateCommand { println!("Started generating code..."); - // Extract schema file provided by user - match args.get_one::("schema") { - None => { - // generate code based on schema and programming language - match language { - "java" => { - Self::print_java_code_gen_warnings(); - CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| NamespaceNode::Package(s.to_string())).collect()) - .generate_code_for_authorities(&authorities, &mut schema_system)? - }, - "rust" => { - Self::print_rust_code_gen_warnings(); - CodeGenerator::::new(output) - .generate_code_for_authorities(&authorities, &mut schema_system)? - } - _ => bail!( - "Programming language '{}' is not yet supported. Currently supported targets: 'java', 'rust'", - language - ) - } - } - Some(schema_id) => { - // generate code based on schema and programming language - match language { - "java" => { - Self::print_java_code_gen_warnings(); - 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(); - CodeGenerator::::new(output) - .generate_code_for_authorities(&authorities, &mut schema_system)? - } - _ => bail!( - "Programming language '{}' is not yet supported. Currently supported targets: 'java', 'rust'", - language - ) - } + // generate code based on schema and programming language + match language { + "java" => { + Self::print_java_code_gen_warnings(); + CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| NamespaceNode::Package(s.to_string())).collect()) + .generate_code_for_authorities(&authorities, &mut schema_system)? + }, + "rust" => { + Self::print_rust_code_gen_warnings(); + CodeGenerator::::new(output) + .generate_code_for_authorities(&authorities, &mut schema_system)? } + _ => bail!( + "Programming language '{}' is not yet supported. Currently supported targets: 'java', 'rust'", + language + ) } println!("Code generation complete successfully!"); - println!("Path to generated code: {}", output.display()); + println!("All the schema files in authority(s) are generated into a flattened namespace, path to generated code: {}", output.display()); Ok(()) } } diff --git a/src/bin/ion/commands/generate/templates/java/scalar.templ b/src/bin/ion/commands/generate/templates/java/scalar.templ index 98d0a40..a5085ad 100644 --- a/src/bin/ion/commands/generate/templates/java/scalar.templ +++ b/src/bin/ion/commands/generate/templates/java/scalar.templ @@ -69,7 +69,7 @@ class {{ model.name }} { public void writeTo(IonWriter writer) throws IOException { {# Writes `Value` class with a single field `value` as an Ion value #} {% if base_type | is_built_in_type == false %} - this.value.writeTo(writer)?; + this.value.writeTo(writer); {% else %} writer.write{{ base_type | replace(from="double", to="float") | replace(from="boolean", to="bool") | upper_camel }}(this.value); {% endif %} diff --git a/tests/cli.rs b/tests/cli.rs index 881640c..0c1a3f8 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -266,8 +266,6 @@ mod code_gen_tests { cmd.args([ "-X", "generate", - "--schema", - "test_schema.isl", "--output", temp_dir.path().to_str().unwrap(), "--language",