diff --git a/Cargo.toml b/Cargo.toml index c577ea1..a4b1473 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,8 @@ ion-schema = "0.10.0" serde = { version = "1.0.163", features = ["derive"] } serde_json = { version = "1.0.81", features = [ "arbitrary_precision", "preserve_order" ] } base64 = "0.21.1" -tera = "1.18.1" -convert_case = "0.6.0" +tera = { version = "1.18.1", optional = true } +convert_case = { version = "0.6.0", optional = true } matches = "0.1.10" thiserror = "1.0.50" @@ -33,8 +33,8 @@ assert_cmd = "~1.0.5" tempfile = "~3.5.0" [features] -deafault = [] -beta-subcommands = [] +default = [] +beta-subcommands = ["dep:tera", "dep:convert_case"] [[bin]] name = "ion" diff --git a/src/bin/ion/commands/beta/generate/context.rs b/src/bin/ion/commands/beta/generate/context.rs index 8e0b907..58f533d 100644 --- a/src/bin/ion/commands/beta/generate/context.rs +++ b/src/bin/ion/commands/beta/generate/context.rs @@ -21,7 +21,9 @@ impl CodeGenContext { /// Represents a data model type that can be used to determine which templates can be used for code generation. #[derive(Debug, Clone, PartialEq, Serialize)] pub enum DataModel { - Value, // a struct with a scalar value (used for `type` constraint) + Value, // a struct with a scalar value (used for `type` constraint) + // TODO: Make Sequence parameterized over data type. + // add a data type for sequence here that can be used to read elements for that data type. Sequence, // a struct with a sequence value (used for `element` constraint) Struct, } diff --git a/src/bin/ion/commands/beta/generate/generator.rs b/src/bin/ion/commands/beta/generate/generator.rs index 48abc09..183c9f2 100644 --- a/src/bin/ion/commands/beta/generate/generator.rs +++ b/src/bin/ion/commands/beta/generate/generator.rs @@ -13,7 +13,7 @@ use std::io::Write; use std::path::Path; use tera::{Context, Tera}; -// TODO: generator cna store language and output path as it doesn't change during code generation process +// TODO: generator can store language and output path as it doesn't change during code generation process pub(crate) struct CodeGenerator<'a> { // Represents the templating engine - tera // more information: https://docs.rs/tera/latest/tera/ @@ -67,7 +67,7 @@ impl<'a> CodeGenerator<'a> { /// Generates code for given Ion Schema pub fn generate(&mut self, schema: IslSchema) -> CodeGenResult<()> { - // this will be used for Rust to create mod.rs which lists all the generates modules + // this will be used for Rust to create mod.rs which lists all the generated modules let mut modules = vec![]; let mut module_context = tera::Context::new(); @@ -107,7 +107,10 @@ impl<'a> CodeGenerator<'a> { let mut code_gen_context = CodeGenContext::new(); // Set the target kind name of the data model (i.e. enum/class) - context.insert("name", &data_model_name.to_case(Case::UpperCamel)); + context.insert( + "target_kind_name", + &data_model_name.to_case(Case::UpperCamel), + ); let constraints = isl_type.constraints(); for constraint in constraints { @@ -197,6 +200,21 @@ impl<'a> CodeGenerator<'a> { } /// Verify that the current data model is same as previously determined data model + /// This is referring to data model determined with each constraint that is verifies + /// that all the constraints map to a single data model and not different data models. + /// e.g. + /// ``` + /// type::{ + /// name: foo, + /// type: string, + /// fields:{ + /// source: String, + /// destination: String + /// } + /// } + /// ``` + /// For the above schema, both `fields` and `type` constraints map to different data models + /// respectively Struct(with given fields `source` and `destination`) and Value(with a single field that has String data type). fn verify_data_model_consistency( &mut self, current_data_model: DataModel, @@ -274,7 +292,7 @@ impl<'a> CodeGenerator<'a> { }) } - /// Generates field value in a struct which represents a sequence data type in codegen's programming language + /// Generates an appropriately typed sequence in the target programming language to use as a field value pub fn generate_sequence_field_value( &mut self, name: String, @@ -293,8 +311,8 @@ impl<'a> CodeGenerator<'a> { name } - /// Generates read API for a data model - /// This adds statements for reading Ion value based on given data model that will be used by data model templates + /// Generates Generates a read API for an abstract data type. + /// This adds statements for reading each the Ion value(s) that collectively represent the given abstract data type. // TODO: add support for Java fn generate_read_api( &mut self, @@ -302,7 +320,7 @@ impl<'a> CodeGenerator<'a> { tera_fields: &mut Vec, code_gen_context: &mut CodeGenContext, ) -> CodeGenResult<()> { - let mut statements = vec![]; + let mut read_statements = vec![]; if code_gen_context.data_model == Some(DataModel::Struct) || code_gen_context.data_model == Some(DataModel::Value) @@ -320,11 +338,11 @@ impl<'a> CodeGenerator<'a> { for tera_field in tera_fields { if !self.is_built_in_type(&tera_field.value) { if code_gen_context.data_model == Some(DataModel::Sequence) { - statements.push(format!( + read_statements.push(format!( "\"{}\" => {{ data_model.{} =", &tera_field.name, &tera_field.name, )); - statements.push( + read_statements.push( r#"{ let mut values = vec![]; reader.step_in()?; @@ -333,18 +351,18 @@ impl<'a> CodeGenerator<'a> { ); let sequence_type = &tera_field.value.replace("Vec<", "").replace('>', ""); if !self.is_built_in_type(sequence_type) { - statements.push(format!( + read_statements.push(format!( "values.push({}::read_from(reader)?)", sequence_type )); } else { - statements.push(format!( + read_statements.push(format!( "values.push(reader.read_{}()?)", sequence_type.to_lowercase() )); } - statements.push( + read_statements.push( r#"} values }}"# .to_string(), @@ -355,7 +373,7 @@ impl<'a> CodeGenerator<'a> { &format!("{}::read_from(reader)?", &tera_field.value,), ); } else { - statements.push(format!( + read_statements.push(format!( "\"{}\" => {{ data_model.{} = {}::read_from(reader)?;}}", &tera_field.name, &tera_field.name, &tera_field.value, )); @@ -367,7 +385,7 @@ impl<'a> CodeGenerator<'a> { &format!("reader.read_{}()?", &tera_field.value.to_lowercase(),), ); } - statements.push(format!( + read_statements.push(format!( "\"{}\" => {{ data_model.{} = reader.read_{}()?;}}", &tera_field.name, &tera_field.name, @@ -376,7 +394,7 @@ impl<'a> CodeGenerator<'a> { } } } - context.insert("statements", &statements); + context.insert("statements", &read_statements); Ok(()) } diff --git a/src/bin/ion/commands/beta/generate/mod.rs b/src/bin/ion/commands/beta/generate/mod.rs index 2086965..0abb0e7 100644 --- a/src/bin/ion/commands/beta/generate/mod.rs +++ b/src/bin/ion/commands/beta/generate/mod.rs @@ -102,9 +102,7 @@ impl IonCliCommand for GenerateCommand { println!("Started generating code..."); // generate code based on schema and programming language - CodeGenerator::new(language, output) - .generate(schema) - .unwrap(); + CodeGenerator::new(language, output).generate(schema)?; println!("Code generation complete successfully!"); println!("Path to generated code: {}", output.display()); diff --git a/src/bin/ion/commands/beta/generate/result.rs b/src/bin/ion/commands/beta/generate/result.rs index 7839804..5c33810 100644 --- a/src/bin/ion/commands/beta/generate/result.rs +++ b/src/bin/ion/commands/beta/generate/result.rs @@ -8,7 +8,7 @@ pub type CodeGenResult = Result; #[derive(Debug, Error)] pub enum CodeGenError { #[error("{source:?}")] - IonError { + IonSchemaError { #[from] source: IonSchemaError, }, diff --git a/src/bin/ion/commands/beta/generate/templates/java/class.templ b/src/bin/ion/commands/beta/generate/templates/java/class.templ index 80f4063..588bf80 100644 --- a/src/bin/ion/commands/beta/generate/templates/java/class.templ +++ b/src/bin/ion/commands/beta/generate/templates/java/class.templ @@ -1,12 +1,12 @@ {% if import %} import ion_data_model.{{ import_type }}; {% endif %} -public final class {{ name }} { +public final class {{ target_kind_name }} { {% for field in fields -%} private final {{ field.value }} {{ field.name }}; {% endfor %} - public {{ name }}({% for field in fields -%}{{ field.value }} {{ field.name }},{% endfor %}) { + public {{ target_kind_name }}({% for field in fields -%}{{ field.value }} {{ field.name }},{% endfor %}) { {% for field in fields -%} this.{{ field.name }} = {{ field.name }}; {% endfor %} @@ -16,4 +16,4 @@ public final class {{ name }} { return this.{{ field.name }}; } {% endfor %} -} \ No newline at end of file +} diff --git a/src/bin/ion/commands/beta/generate/templates/rust/mod.templ b/src/bin/ion/commands/beta/generate/templates/rust/mod.templ index d7f78e2..0c127ee 100644 --- a/src/bin/ion/commands/beta/generate/templates/rust/mod.templ +++ b/src/bin/ion/commands/beta/generate/templates/rust/mod.templ @@ -1,3 +1,3 @@ {% for module in modules -%} pub mod {{ module }}; -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/src/bin/ion/commands/beta/generate/templates/rust/struct.templ b/src/bin/ion/commands/beta/generate/templates/rust/struct.templ index d826fcd..e49560b 100644 --- a/src/bin/ion/commands/beta/generate/templates/rust/struct.templ +++ b/src/bin/ion/commands/beta/generate/templates/rust/struct.templ @@ -4,13 +4,13 @@ use crate::ion_data_model::{{ import.module_name }}::{{ import.type_name }}; {% endfor %} #[derive(Debug, Clone, Default)] -pub struct {{ name }} { +pub struct {{ target_kind_name }} { {% for field in fields -%} {{ field.name | indent(first = true) }}: {{ field.value }}, {% endfor %} } -impl {{ name }} { +impl {{ target_kind_name }} { pub fn new({% for field in fields -%}{{ field.name }}: {{ field.value }},{% endfor %}) -> Self { Self { {% for field in fields -%} @@ -29,11 +29,11 @@ impl {{ name }} { pub fn read_from(reader: &mut Reader) -> IonResult { reader.next()?; {% if data_model == "UnitStruct"%} - let mut data_model = {{ name }}::default(); + let mut data_model = {{ target_kind_name }}::default(); data_model.value = {{ read_statement }}; {% else %} {% if data_model == "Struct"%}reader.step_in()?;{% endif %} - let mut data_model = {{ name }}::default(); + let mut data_model = {{ target_kind_name }}::default(); while reader.next()? != StreamItem::Nothing { if let Some(field_name) = reader.field_name()?.text() { match field_name { @@ -57,4 +57,4 @@ impl {{ name }} { {% endfor %} Ok(()) } -} \ No newline at end of file +} diff --git a/src/bin/ion/commands/beta/generate/utils.rs b/src/bin/ion/commands/beta/generate/utils.rs index 6f3427d..8e53157 100644 --- a/src/bin/ion/commands/beta/generate/utils.rs +++ b/src/bin/ion/commands/beta/generate/utils.rs @@ -126,7 +126,7 @@ impl IonSchemaType { (Int, Java) => "int", (String | Symbol, _) => "String", (Float, Rust) => "f64", - (Float, Java) => "float", + (Float, Java) => "double", (Bool, Rust) => "bool", (Bool, Java) => "boolean", (Blob | Clob, Rust) => "Vec", diff --git a/tests/cli.rs b/tests/cli.rs index cd702c0..41d5128 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -208,10 +208,10 @@ fn test_write_all_values(#[case] number: i32, #[case] expected_output: &str) -> &["pub fn name(&self) -> &String {", "pub fn id(&self) -> &i64 {"] )] #[case( - "unit_struct", + "value_struct", r#" type::{ - name: unit_struct, + name: value_struct, type: int // this will be a field in struct } "#, @@ -323,10 +323,10 @@ fn test_code_generation_in_rust( &["public String getName() {", "public int getId() {"] )] #[case( - "UnitStruct", + "ValueStruct", r#" type::{ - name: unit_struct, + name: value_struct, type: int // this will be a field in struct } "#,