diff --git a/src/bin/ion/commands/generate/generator.rs b/src/bin/ion/commands/generate/generator.rs index 54574d85..c519d37f 100644 --- a/src/bin/ion/commands/generate/generator.rs +++ b/src/bin/ion/commands/generate/generator.rs @@ -212,7 +212,9 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { _map: &HashMap, ) -> Result { let fully_qualified_type_ref: &FullyQualifiedTypeReference = &value.try_into()?; - Ok(tera::Value::String(fully_qualified_type_ref.to_string())) + Ok(tera::Value::String( + fully_qualified_type_ref.string_representation::(), + )) } /// Generates code for all the schemas in given authorities @@ -325,7 +327,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { &self .data_model_store .iter() - .map(|(k, v)| (format!("{}", k), v)) + .map(|(k, v)| (k.string_representation::(), v)) .collect::>(), ); context.insert("model", &data_model_node); @@ -340,8 +342,11 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { code_gen_context: &mut CodeGenContext, is_nested_type: bool, ) -> CodeGenResult { - self.current_type_fully_qualified_name - .push(isl_type_name.to_case(Case::UpperCamel)); + L::add_type_to_namespace( + is_nested_type, + isl_type_name, + &mut self.current_type_fully_qualified_name, + ); let constraints = isl_type.constraints(); diff --git a/src/bin/ion/commands/generate/mod.rs b/src/bin/ion/commands/generate/mod.rs index 528a2dd0..2c83515c 100644 --- a/src/bin/ion/commands/generate/mod.rs +++ b/src/bin/ion/commands/generate/mod.rs @@ -7,7 +7,7 @@ mod utils; mod model; use crate::commands::generate::generator::CodeGenerator; -use crate::commands::generate::utils::JavaLanguage; +use crate::commands::generate::utils::{JavaLanguage, RustLanguage}; use crate::commands::IonCliCommand; use anyhow::{bail, Result}; use clap::{Arg, ArgAction, ArgMatches, Command}; @@ -124,8 +124,8 @@ impl IonCliCommand for GenerateCommand { CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| s.to_string()).collect()) .generate_code_for_authorities(&authorities, &mut schema_system)?, "rust" => { - // TODO: Initialize and run code generator for `rust`, once the rust templates are modified based on new code generation model - todo!("Rust support is disabled until this is resolved: https://github.com/amazon-ion/ion-cli/issues/136") + CodeGenerator::::new(output) + .generate_code_for_authorities(&authorities, &mut schema_system)? } _ => bail!( "Programming language '{}' is not yet supported. Currently supported targets: 'java', 'rust'", @@ -138,8 +138,8 @@ impl IonCliCommand for GenerateCommand { match language { "java" => CodeGenerator::::new(output, namespace.unwrap().split('.').map(|s| s.to_string()).collect()).generate_code_for_schema(&mut schema_system, schema_id)?, "rust" => { - // TODO: Initialize and run code generator for `rust`, once the rust templates are modified based on new code generation model - todo!() + CodeGenerator::::new(output) + .generate_code_for_authorities(&authorities, &mut schema_system)? } _ => bail!( "Programming language '{}' is not yet supported. Currently supported targets: 'java', 'rust'", diff --git a/src/bin/ion/commands/generate/model.rs b/src/bin/ion/commands/generate/model.rs index 7eedcc21..185d9df9 100644 --- a/src/bin/ion/commands/generate/model.rs +++ b/src/bin/ion/commands/generate/model.rs @@ -1,17 +1,19 @@ use derive_builder::Builder; use ion_schema::isl::isl_type::IslType; use std::collections::HashMap; -use std::fmt::{Display, Formatter}; +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. // Currently, this same data model is represented by `AbstractDataType` but it doesn't hold all the information for the template. // e.g. currently there are different fields in the template that hold this information like fields, target_kind_name, abstract_data_type. // Also, the current approach doesn't allow having nested sequences in the generated code. Because the `element_type` in `AbstractDataType::Sequence` // doesn't have information on its nested types' `element_type`. This can be resolved with below defined new data model. // _Note: This model will eventually use a map (FullQualifiedTypeReference, DataModel) to resolve some the references in container types(sequence or structure)._ +// Any changes to the model will require subsequent changes to the templates which use this model. // TODO: This is not yet used in the implementation, modify current implementation to use this data model. use crate::commands::generate::context::SequenceType; use crate::commands::generate::utils::Language; -use serde::Serialize; +use serde::ser::Error; +use serde::{Serialize, Serializer}; use serde_json::Value; /// Represent a node in the data model tree of the generated code. @@ -104,24 +106,6 @@ impl From for FullyQualifiedTypeReference { } } -impl Display for FullyQualifiedTypeReference { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if self.parameters.is_empty() { - return write!(f, "{}", self.type_name.join(".")); - } - write!(f, "{}<", self.type_name.join("."))?; - - for (i, parameter) in self.parameters.iter().enumerate() { - if i == self.parameters.len() - 1 { - write!(f, "{}", parameter)?; - } else { - write!(f, "{},", parameter)?; - } - } - write!(f, ">") - } -} - // This is useful for code generator to convert input `serde_json::Value` coming from tera(template engine) into `FullyQualifiedTypeReference` impl TryFrom<&Value> for FullyQualifiedTypeReference { type Error = tera::Error; @@ -163,6 +147,24 @@ impl FullyQualifiedTypeReference { pub fn with_parameters(&mut self, parameters: Vec) { self.parameters = parameters; } + + /// Provides string representation of this `FullyQualifiedTypeReference` + pub fn string_representation(&self) -> String { + if self.parameters.is_empty() { + return format!("{}", self.type_name.join(&L::namespace_separator())); + } + let parameters = self + .parameters + .iter() + .map(|p| p.string_representation::()) + .collect::>() + .join(", "); + format!( + "{}<{}>", + self.type_name.join(&L::namespace_separator()), + parameters + ) + } } /// A target-language-agnostic data type that determines which template(s) to use for code generation. @@ -221,6 +223,26 @@ impl AbstractDataType { } } +/// Helper function for serializing abstract data type's `source` field that represents an ISL type. +/// This method returns the name for the given ISL type. +// TODO: `IslType` does not implement `Serialize`, once that is available this method can be removed. +fn serialize_type_name(isl_type: &IslType, serializer: S) -> Result +where + S: Serializer, +{ + isl_type + .name() + .as_ref() + .ok_or(S::Error::custom("Isl type doesn't have a name"))? + .serialize(serializer) +} + +/// Helper function for checking to skip or serialize `source` field in abstract data type that represents an ISL type. +/// This method returns true if the ISl type doesn't have a name, otherwise returns false. +fn is_anonymous(isl_type: &IslType) -> bool { + isl_type.name().is_none() +} + /// Represents a scalar type (e.g. a string or integer or user defined type) #[allow(dead_code)] #[derive(Debug, Clone, Builder, PartialEq, Serialize)] @@ -245,8 +267,8 @@ pub struct Scalar { // Represents the source ISL type which can be used to get other constraints useful for this type. // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. // This will also be useful for `text` type to verify if this is a `string` or `symbol`. - // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. - #[serde(skip_serializing)] + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] source: IslType, } @@ -286,8 +308,8 @@ pub struct WrappedScalar { // Represents the source ISL type which can be used to get other constraints useful for this type. // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. // This will also be useful for `text` type to verify if this is a `string` or `symbol`. - // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. - #[serde(skip_serializing)] + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] source: IslType, } @@ -330,8 +352,8 @@ pub struct WrappedSequence { // Represents the source ISL type which can be used to get other constraints useful for this type. // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. // This will also be useful for `text` type to verify if this is a `string` or `symbol`. - // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. - #[serde(skip_serializing)] + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] source: IslType, } @@ -365,8 +387,8 @@ pub struct Sequence { // Represents the source ISL type which can be used to get other constraints useful for this type. // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. // This will also be useful for `text` type to verify if this is a `string` or `symbol`. - // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. - #[serde(skip_serializing)] + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] pub(crate) source: IslType, } @@ -406,8 +428,8 @@ pub struct Structure { // Represents the source ISL type which can be used to get other constraints useful for this type. // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. // This will also be useful for `text` type to verify if this is a `string` or `symbol`. - // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. - #[serde(skip_serializing)] + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] pub(crate) source: IslType, } diff --git a/src/bin/ion/commands/generate/templates/rust/nested_type.templ b/src/bin/ion/commands/generate/templates/rust/nested_type.templ index 6caa5ee0..ec991a88 100644 --- a/src/bin/ion/commands/generate/templates/rust/nested_type.templ +++ b/src/bin/ion/commands/generate/templates/rust/nested_type.templ @@ -1,133 +1,8 @@ {% import "util_macros.templ" as util_macros %} {# following macro defines an anonymous type as children class for its parent type definition #} -{% macro nested_type(target_kind_name, fields, abstract_data_type, nested_anonymous_types) -%} - #[derive(Debug, Clone, Default)] - pub struct {{ target_kind_name }} { - {% for field in fields -%} - {{ field.name | snake | indent(first = true) }}: {{ field.value_type }}, - {% endfor %} - } - - impl {{ target_kind_name }} { - pub fn new({% for field in fields | sort(attribute="name") -%}{{ field.name | snake }}: {{ field.value_type }},{% endfor %}) -> Self { - Self { - {% for field in fields -%} - {{ field.name | snake }}, - {% endfor %} - } - } - - - {% for field in fields -%}pub fn {{ field.name | snake }}(&self) -> &{{ field.value_type }} { - &self.{{ field.name | snake }} - } - {% endfor %} - - - pub fn read_from(reader: &mut Reader) -> SerdeResult { - let mut abstract_data_type = {{ target_kind_name }}::default(); - {% if abstract_data_type == "Value"%} - abstract_data_type.value = {% if fields[0].value_type | is_built_in_type == false %} - {{ fields[0].value_type }}::read_from(reader)?; - {% else %} - reader.read_{% if fields[0].isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ fields[0].value_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if fields[0].value_type | lower == "string" %} .to_string() {% endif %}; - {% endif %} - {% elif abstract_data_type is object and abstract_data_type is containing("Structure") %} - reader.step_in()?; - while reader.next()? != StreamItem::Nothing { - if let Some(field_name) = reader.field_name()?.text() { - match field_name { - {% for field in fields -%} - {% if field.value_type | is_built_in_type == false %} - {% if field.value_type is containing("Vec") %} - "{{ field.name }}" => { {{ util_macros::read_as_sequence(field=field) }} } - {% else %} - "{{ field.name }}" => { abstract_data_type.{{ field.name | snake }} = {{ field.value_type }}::read_from(reader)?; } - {% endif %} - {% else %} - "{{ field.name }}" => { abstract_data_type.{{ field.name | snake}} = reader.read_{% if field.isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ field.value_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if field.value_type | lower== "string" %} .to_string() {% endif %}; } - {% endif %} - {% endfor %} - _ => { - {% if abstract_data_type["Structure"] %} - return validation_error( - "Can not read field name:{{ field.name }} for {{ target_kind_name }} as it doesn't exist in the given schema type definition." - ); - {% endif %} - } - } - } - } - reader.step_out()?; - {% elif abstract_data_type is object and abstract_data_type is containing("Sequence") %} - if reader.ion_type() != Some(IonType::{{ abstract_data_type["Sequence"].sequence_type }}) { - return validation_error(format!( - "Expected {{ abstract_data_type["Sequence"].sequence_type }}, found {} while reading {{ target_kind_name }}.", reader.ion_type().unwrap() - )); - } - reader.step_in()?; - - abstract_data_type.value = { - let mut values = vec![]; - - while reader.next()? != StreamItem::Nothing { - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - values.push({{ abstract_data_type["Sequence"].element_type }}::read_from(reader)?); - {% else %} - values.push(reader.read_{% if fields[0].isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ abstract_data_type["Sequence"].element_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if abstract_data_type["Sequence"].element_type | lower== "string" %} .to_string() {% endif %}); - {% endif %} - } - values - }; - reader.step_out()?; - {% else %} - return validation_error("Can not resolve read API template for {{ target_kind_name }}"); - {% endif %} - Ok(abstract_data_type) - } - - pub fn write_to(&self, writer: &mut W) -> SerdeResult<()> { - {% if abstract_data_type == "Value" %} - {% for field in fields %} - {% if field.value_type | is_built_in_type == false %} - self.{{ field.name | snake }}.write_to(writer)?; - {% else %} - writer.write_{% if field.isl_type_name == "symbol" %}symbol{% else %}{{ field.value_type | lower }}{% endif %}(self.value.to_owned())?; - {% endif %} - {% endfor %} - {% elif abstract_data_type is object and abstract_data_type is containing("Structure") %} - writer.step_in(IonType::Struct)?; - {% for field in fields %} - writer.set_field_name("{{ field.name }}"); - {% if field.value_type | is_built_in_type == false %} - {% if field.value_type is containing("Vec") %} - {{ util_macros::write_as_sequence(field=field) }} - {% else %} - self.{{ field.name | snake }}.write_to(writer)?; - {% endif %} - {% else %} - {# TODO: Change the following `to_owned` to only be used when writing i64,f32,f64,bool which require owned value as input #} - writer.write_{% if field.isl_type_name == "symbol" %}symbol{% else %}{{ field.value_type | lower }}{% endif %}(self.{{ field.name | snake }}.to_owned())?; - {% endif %} - {% endfor %} - writer.step_out()?; - {% elif abstract_data_type is object and abstract_data_type is containing("Sequence") %} - writer.step_in(IonType::{{ abstract_data_type["Sequence"].sequence_type }})?; - for value in &self.value { - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - value.write_to(writer)?; - {% else %} - writer.write_{% if fields[0].isl_type_name == "symbol" %}symbol{% else %}{{ abstract_data_type["Sequence"].element_type | lower }}{% endif %}(value.to_owned())?; - {% endif %} - } - writer.step_out()?; - {% endif %} - Ok(()) - } - } - - {% for inline_type in nested_anonymous_types -%} - {{ self::nested_type(target_kind_name=inline_type.target_kind_name, fields=inline_type.fields, abstract_data_type=inline_type.abstract_data_type, nested_anonymous_types=inline_type.nested_types) }} - {% endfor -%} -{% endmacro %} \ No newline at end of file +{% macro nested_type(model, is_nested) -%} + {% if model.code_gen_type is containing("Structure")%} + {% include "struct.templ" %} + {% endif %} +{% endmacro nested_type -%} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/rust/scalar.templ b/src/bin/ion/commands/generate/templates/rust/scalar.templ index cd9d9d7d..c6c0b0ef 100644 --- a/src/bin/ion/commands/generate/templates/rust/scalar.templ +++ b/src/bin/ion/commands/generate/templates/rust/scalar.templ @@ -1,40 +1,44 @@ -use {{ target_kind_name | snake }}::{{ target_kind_name }}; -pub mod {{ target_kind_name | snake }} { +{# 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 %} + +use {{ model.name | snake }}::{{ model.name }}; +pub mod {{ model.name | snake }} { use super::*; #[derive(Debug, Clone, Default)] - pub struct {{ target_kind_name }} { - value: {{ fields[0].value_type }}, + pub struct {{ model.name }} { + value: {{ base_type }}, } - impl {{ target_kind_name }} { - pub fn new(value: {{ fields[0].value_type }}) -> Self { + impl {{ model.name }} { + pub fn new(value: {{ base_type }}) -> Self { Self { value, } } - pub fn value(&self) -> &{{ fields[0].value_type }} { + pub fn value(&self) -> &{{ base_type }} { &self.value } pub fn read_from(reader: &mut Reader) -> SerdeResult { - let mut abstract_data_type = {{ target_kind_name }}::default(); - abstract_data_type.value = {% if fields[0].value_type | is_built_in_type == false %} - {{ fields[0].value_type }}::read_from(reader)?; + let mut abstract_data_type = {{ model.name }}::default(); + abstract_data_type.value = {% if base_type | is_built_in_type == false %} + {{ base_type }}::read_from(reader)?; {% else %} - reader.read_{% if fields[0].isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ fields[0].value_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if fields[0].value_type | lower == "string" %} .to_string() {% endif %}; + reader.read_{% if field.source is defined and field.source == "symbol" %}symbol()?.text().unwrap(){% else %}{{ base_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if base_type| lower == "string" %} .to_string() {% endif %}; {% endif %} Ok(abstract_data_type) } pub fn write_to(&self, writer: &mut W) -> SerdeResult<()> { - {% if fields[0].value_type | is_built_in_type == false %} + {% if base_type | is_built_in_type == false %} self.value.write_to(writer)?; {% else %} - writer.write_{% if fields[0].isl_type_name == "symbol" %}symbol{% else %}{{ fields[0].value_type | lower }}{% endif %}(self.value.to_owned())?; + writer.write_{% if field.source is defined and field.source == "symbol" %}symbol{% else %}{{ base_type | lower }}{% endif %}(self.value.to_owned())?; {% endif %} Ok(()) } diff --git a/src/bin/ion/commands/generate/templates/rust/sequence.templ b/src/bin/ion/commands/generate/templates/rust/sequence.templ index 4bfb37a8..4a4555d4 100644 --- a/src/bin/ion/commands/generate/templates/rust/sequence.templ +++ b/src/bin/ion/commands/generate/templates/rust/sequence.templ @@ -1,31 +1,34 @@ -use {{ target_kind_name | snake }}::{{ target_kind_name }}; -pub mod {{ target_kind_name | snake }} { +{% set sequence_info = model.code_gen_type["WrappedSequence"] %} + +use {{ model.name | snake }}::{{ model.name }}; + +pub mod {{ model.name | snake }} { use super::*; #[derive(Debug, Clone, Default)] - pub struct {{ target_kind_name }} { - value: {{ fields[0].value_type }}, + pub struct {{ model.name }} { + value: Vec<{{ sequence_info["element_type"] | fully_qualified_type_name }}>, } - impl {{ target_kind_name }} { - pub fn new(value: {{ fields[0].value_type }}) -> Self { + impl {{ model.name }} { + pub fn new(value: Vec<{{ sequence_info["element_type"] | fully_qualified_type_name }}>) -> Self { Self { value, } } - pub fn value(&self) -> &{{ fields[0].value_type }} { + pub fn value(&self) -> &Vec<{{ sequence_info["element_type"] | fully_qualified_type_name }}> { &self.value } pub fn read_from(reader: &mut Reader) -> SerdeResult { - let mut abstract_data_type = {{ target_kind_name }}::default(); + let mut abstract_data_type = {{ model.name }}::default(); - if reader.ion_type() != Some(IonType::{{ abstract_data_type["Sequence"].sequence_type }}) { + if reader.ion_type() != Some(IonType::{{ sequence_info["sequence_type"] }}) { return validation_error(format!( - "Expected {{ abstract_data_type["Sequence"].sequence_type }}, found {} while reading {{ target_kind_name }}.", reader.ion_type().unwrap() + "Expected {{ sequence_info["sequence_type"] }}, found {} while reading {{ model.name }}.", reader.ion_type().unwrap() )); } @@ -35,10 +38,10 @@ pub mod {{ target_kind_name | snake }} { let mut values = vec![]; while reader.next()? != StreamItem::Nothing { - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - values.push({{ abstract_data_type["Sequence"].element_type }}::read_from(reader)?); + {% if sequence_info["element_type"] | fully_qualified_type_name | is_built_in_type == false %} + values.push({{ sequence_info["element_type"] }}::read_from(reader)?); {% else %} - values.push(reader.read_{% if fields[0].isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ abstract_data_type["Sequence"].element_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if abstract_data_type["Sequence"].element_type | lower== "string" %} .to_string() {% endif %}); + 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 %} } values @@ -48,12 +51,12 @@ pub mod {{ target_kind_name | snake }} { } pub fn write_to(&self, writer: &mut W) -> SerdeResult<()> { - writer.step_in(IonType::{{ abstract_data_type["Sequence"].sequence_type }})?; + writer.step_in(IonType::{{ sequence_info["sequence_type"] }})?; for value in &self.value { - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} + {% if sequence_info["element_type"] | fully_qualified_type_name | is_built_in_type == false %} value.write_to(writer)?; {% else %} - writer.write_{% if fields[0].isl_type_name == "symbol" %}symbol{% else %}{{ abstract_data_type["Sequence"].element_type | lower }}{% endif %}(value.to_owned())?; + writer.write_{% if field.source is defined and field.source == "symbol" %}symbol{% else %}{{ sequence_info["element_type"] | fully_qualified_type_name | lower }}{% endif %}(value.to_owned())?; {% endif %} } writer.step_out()?; diff --git a/src/bin/ion/commands/generate/templates/rust/struct.templ b/src/bin/ion/commands/generate/templates/rust/struct.templ index 5c00c9e8..de14e79c 100644 --- a/src/bin/ion/commands/generate/templates/rust/struct.templ +++ b/src/bin/ion/commands/generate/templates/rust/struct.templ @@ -2,55 +2,59 @@ {% import "nested_type.templ" as macros %} {% import "util_macros.templ" as util_macros %} -use {{ target_kind_name | snake }}::{{ target_kind_name }}; -pub mod {{ target_kind_name | snake }} { +{% macro struct(model, is_nested) %} +{% set struct_info = model.code_gen_type["Structure"] %} + +use {{ model.name | snake }}::{{ model.name }}; +pub mod {{ model.name | snake }} { use super::*; #[derive(Debug, Clone, Default)] - pub struct {{ target_kind_name }} { - {% for field in fields -%} - {{ field.name | snake | indent(first = true) }}: {{ field.value_type }}, + pub struct {{ model.name }} { + {% for field_name, field_value in struct_info["fields"] -%} + {{ field_name | snake | indent(first = true) }}: {{ field_value.0 | fully_qualified_type_name }}, {% endfor %} } - impl {{ target_kind_name }} { - pub fn new({% for field in fields | sort(attribute="name") -%}{{ field.name | snake }}: {{ field.value_type }},{% endfor %}) -> Self { + impl {{ model.name }} { + pub fn new({% for field_name in struct_info["fields"] | field_names -%}{% set field_value = struct_info["fields"][field_name] %}{{ field_name | snake }}: {{ field_value.0 | fully_qualified_type_name }},{% endfor %}) -> Self { Self { - {% for field in fields -%} - {{ field.name | snake }}, + {% for field_name, field_value in struct_info["fields"] -%} + {{ field_name | snake }}, {% endfor %} } } - {% for field in fields -%}pub fn {{ field.name | snake }}(&self) -> &{{ field.value_type }} { - &self.{{ field.name | snake }} + {% for field_name, field_value in struct_info["fields"] -%}pub fn {{ field_name | snake }}(&self) -> &{{ field_value.0 | fully_qualified_type_name }} { + &self.{{ field_name | snake }} } {% endfor %} pub fn read_from(reader: &mut Reader) -> SerdeResult { - let mut abstract_data_type = {{ target_kind_name }}::default(); + let mut abstract_data_type = {{ model.name }}::default(); reader.step_in()?; while reader.next()? != StreamItem::Nothing { if let Some(field_name) = reader.field_name()?.text() { match field_name { - {% for field in fields -%} - {% if field.value_type | is_built_in_type == false %} - {% if field.value_type is containing("Vec") %} - "{{ field.name }}" => { {{ util_macros::read_as_sequence(field=field) }} } + {% for field_name, field_val in struct_info["fields"] -%} + {% set field_value = field_val.0 | fully_qualified_type_name %} + {% if field_value | is_built_in_type == false %} + {% if field_value is containing("Vec") %} + "{{ field_name }}" => { {{ util_macros::read_as_sequence(field_value=field_value,field_name=field_name,type_store=type_store) }} } {% else %} - "{{ field.name }}" => { abstract_data_type.{{ field.name | snake }} = {{ field.value_type }}::read_from(reader)?; } + "{{ field_name }}" => { abstract_data_type.{{ field_name | snake }} = {{ field_value }}::read_from(reader)?; } {% endif %} {% else %} - "{{ field.name }}" => { abstract_data_type.{{ field.name | snake}} = reader.read_{% if field.isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ field.value_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if field.value_type | lower== "string" %} .to_string() {% endif %}; } + "{{ field_name }}" => { abstract_data_type.{{ field_name | snake}} = reader.read_{% if field.source is defined and field.source == "symbol" %}symbol()?.text().unwrap(){% else %}{{ field_value | lower | replace(from="string", to ="str") }}()?{% endif %}{% if field_value | lower== "string" %} .to_string() {% endif %}; } {% endif %} {% endfor %} _ => { {% if abstract_data_type["Structure"] %} return validation_error( - "Can not read field name:{{ field.name }} for {{ target_kind_name }} as it doesn't exist in the given schema type definition." + "Can not read field name:{{ field_name }} for {{ model.name }} as it doesn't exist in the given schema type definition." ); {% endif %} } @@ -63,17 +67,18 @@ pub mod {{ target_kind_name | snake }} { pub fn write_to(&self, writer: &mut W) -> SerdeResult<()> { writer.step_in(IonType::Struct)?; - {% for field in fields %} - writer.set_field_name("{{ field.name }}"); - {% if field.value_type | is_built_in_type == false %} - {% if field.value_type is containing("Vec") %} - {{ util_macros::write_as_sequence(field=field) }} + {% for field_name, field_val in struct_info["fields"] %} + {% set field_value = field_val.0 | fully_qualified_type_name %} + writer.set_field_name("{{ field_name }}"); + {% if field_value | is_built_in_type == false %} + {% if field_value is containing("Vec") %} + {{ util_macros::write_as_sequence(field_value=field_value,field_name=field_name,type_store=type_store) }} {% else %} - self.{{ field.name | snake }}.write_to(writer)?; + self.{{ field_name | snake }}.write_to(writer)?; {% endif %} {% else %} {# TODO: Change the following `to_owned` to only be used when writing i64,f32,f64,bool which require owned value as input #} - writer.write_{% if field.isl_type_name == "symbol" %}symbol{% else %}{{ field.value_type | lower }}{% endif %}(self.{{ field.name | snake }}.to_owned())?; + writer.write_{% if field.source is defined and field.source == "symbol" %}symbol{% else %}{{ field_value | lower }}{% endif %}(self.{{ field_name | snake }}.to_owned())?; {% endif %} {% endfor %} writer.step_out()?; @@ -81,7 +86,10 @@ pub mod {{ target_kind_name | snake }} { } } - {% for inline_type in nested_types -%} - {{ macros::nested_type(target_kind_name=inline_type.target_kind_name, fields=inline_type.fields, abstract_data_type=inline_type.abstract_data_type, nested_anonymous_types=inline_type.nested_types) }} + {% for inline_type in model.nested_types -%} + {% set is_nested = true %} + {{ macros::nested_type(model=inline_type, is_nested=is_nested) }} {% endfor -%} } +{% endmacro struct %} +{{ self::struct(model=model, is_nested=is_nested) }} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/rust/util_macros.templ b/src/bin/ion/commands/generate/templates/rust/util_macros.templ index 3d2e3614..66f306c4 100644 --- a/src/bin/ion/commands/generate/templates/rust/util_macros.templ +++ b/src/bin/ion/commands/generate/templates/rust/util_macros.templ @@ -1,20 +1,22 @@ {# following macro defines statements to read a class field as sequence #} -{% macro read_as_sequence(field) %} - if reader.ion_type() != Some(IonType::{{ field.abstract_data_type["Sequence"].sequence_type }}) { +{% macro read_as_sequence(field_name, field_value, type_store) %} + {% set field_value_model = type_store[field_value] %} + + if reader.ion_type() != Some(IonType::{{ field_value_model.code_gen_type["Sequence"].sequence_type }}) { return validation_error(format!( - "Expected {{ field.abstract_data_type["Sequence"].sequence_type }}, found {} while reading {{ target_kind_name }}.", reader.ion_type().unwrap() + "Expected {{ field_value_model.code_gen_type["Sequence"].sequence_type }}, found {} while reading {{ field_name }}.", reader.ion_type().unwrap() )); } reader.step_in()?; - abstract_data_type.{{ field.name | snake }} = { + abstract_data_type.{{ field_name | snake }} = { let mut values = vec![]; while reader.next()? != StreamItem::Nothing { - {% if field.abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - values.push({{ field.abstract_data_type["Sequence"].element_type }}::read_from(reader)?); + {% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | is_built_in_type == false %} + values.push({{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name }}::read_from(reader)?); {% else %} - values.push(reader.read_{% if field.isl_type_name == "symbol" %}symbol()?.text().unwrap(){% else %}{{ field.abstract_data_type["Sequence"].element_type | lower | replace(from="string", to ="str") }}()?{% endif %}{% if field.abstract_data_type["Sequence"].element_type | lower== "string" %} .to_string() {% endif %}); + values.push(reader.read_{% if field.source is defined and field.source == "symbol" %}symbol()?.text().unwrap(){% else %}{{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | lower | replace(from="string", to ="str") }}()?{% endif %}{% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | lower== "string" %} .to_string() {% endif %}); {% endif %} } values @@ -22,13 +24,14 @@ reader.step_out()?; {% endmacro %} {# following macro defines statements to write a class field as sequence #} -{% macro write_as_sequence(field) %} - writer.step_in(IonType::{{ field.abstract_data_type["Sequence"].sequence_type }}); - for value in &self.{{ field.name | snake }} { - {% if field.abstract_data_type["Sequence"].element_type | is_built_in_type == false %} +{% macro write_as_sequence(field_name, field_value, type_store) %} + {% set field_value_model = type_store[field_value] %} + writer.step_in(IonType::{{ field_value_model.code_gen_type["Sequence"].sequence_type }}); + for value in &self.{{ field_name | snake }} { + {% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | is_built_in_type == false %} value.write_to(writer)?; {% else %} - writer.write_{% if field.isl_type_name == "symbol" %}symbol{% else %}{{ field.abstract_data_type["Sequence"].element_type | lower }}{% endif %}(value.to_owned())?; + writer.write_{% if field.source is defined and field.source == "symbol" %}symbol{% else %}{{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | lower }}{% endif %}(value.to_owned())?; {% endif %} } writer.step_out()?; diff --git a/src/bin/ion/commands/generate/utils.rs b/src/bin/ion/commands/generate/utils.rs index 93647680..2f254de7 100644 --- a/src/bin/ion/commands/generate/utils.rs +++ b/src/bin/ion/commands/generate/utils.rs @@ -50,6 +50,26 @@ pub trait Language { /// In Rust, Template::Struct -> "struct" /// In Java, Template::Struct -> "class" fn template_name(template: &Template) -> String; + + /// Returns the namespace separator for programming language + /// e.g. In Java, it returns "::" + /// In Rust, it returns "." + fn namespace_separator() -> &'static str; + + /// Modifies the given namespace to add the given type to the namespace path. + /// _Note:_ For Rust, it uses the `is_nested_type` field to only get modules in the path name until the leaf type is reached. + /// e.g. given a module as below: + /// ``` + /// mod foo { + /// struct Foo { ... } + /// mod nested_type { + /// struct NestedType { ... } + /// } + /// } + /// ``` + /// 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: &String, namespace: &mut Vec); } pub struct JavaLanguage; @@ -86,7 +106,7 @@ impl Language for JavaLanguage { fn target_type_as_sequence( target_type: FullyQualifiedTypeReference, ) -> FullyQualifiedTypeReference { - match JavaLanguage::wrapper_class(&format!("{}", target_type)) { + match JavaLanguage::wrapper_class(&target_type.string_representation::()) { Some(wrapper_name) => FullyQualifiedTypeReference { type_name: vec![ "java".to_string(), @@ -127,6 +147,18 @@ impl Language for JavaLanguage { Template::Sequence => "sequence".to_string(), } } + + fn namespace_separator() -> &'static str { + "." + } + + fn add_type_to_namespace( + _is_nested_type: bool, + type_name: &String, + namespace: &mut Vec, + ) { + namespace.push(type_name.to_case(Case::UpperCamel)) + } } impl JavaLanguage { @@ -193,15 +225,7 @@ impl Language for RustLanguage { fn is_built_in_type(type_name: String) -> bool { matches!( type_name.as_str(), - "i64" - | "String" - | "bool" - | "Vec" - | "f64" - | "Vec" - | "Vec" - | "Vec" - | "Vec" + "i64" | "String" | "bool" | "Vec" | "f64" ) } @@ -216,6 +240,38 @@ impl Language for RustLanguage { Template::Sequence => "sequence".to_string(), } } + + fn namespace_separator() -> &'static str { + "::" + } + + fn add_type_to_namespace( + is_nested_type: bool, + type_name: &String, + 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 { + // struct Foo { + // ... + // } + // mod nested_type { + // struct NestedType { + // ... + // } + // } + // } + // ``` + 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 + } + 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 + } } impl Display for RustLanguage { diff --git a/tests/code-gen-tests.rs b/tests/code-gen-tests.rs index 54c60a57..a7b8d559 100644 --- a/tests/code-gen-tests.rs +++ b/tests/code-gen-tests.rs @@ -49,6 +49,53 @@ fn roundtrip_tests_for_generated_code_gradle() -> Result<()> { Ok(()) } +#[test] +fn roundtrip_tests_for_generated_code_cargo() -> Result<()> { + // run the cargo project defined under `code-gen-projects`, + // this project runs the code generator in its build process and generates code, + // this project also has some predefined tests for the generated code, + // so simply running the tests on this project builds the project, generates code and runs tests + + // absolute paths for crate and executables + let ion_executable = env!("CARGO_BIN_EXE_ion"); + let test_project_path = code_gen_projects_path().join("rust").join("code-gen-demo"); + let cargo_executable = env!("CARGO"); + + // Clean + let cargo_clean_output = std::process::Command::new(cargo_executable) + .current_dir(&test_project_path) + .arg("clean") + .output() + .expect("failed to execute 'cargo clean'"); + + println!("Cargo clean status: {}", cargo_clean_output.status); + std::io::stdout() + .write_all(&cargo_clean_output.stdout) + .unwrap(); + std::io::stderr() + .write_all(&cargo_clean_output.stderr) + .unwrap(); + + // Test + let cargo_test_output = std::process::Command::new(cargo_executable) + .current_dir(&test_project_path) + .arg("test") + .env("ION_CLI", ion_executable) + .output() + .expect("failed to execute 'cargo test'"); + + println!("Cargo test status: {}", cargo_test_output.status); + std::io::stdout() + .write_all(&cargo_test_output.stdout) + .unwrap(); + std::io::stderr() + .write_all(&cargo_test_output.stderr) + .unwrap(); + + assert!(cargo_test_output.status.success()); + Ok(()) +} + //TODO: Add cargo roundtrip tests once the rust templates are modified based on new code generation model #[rstest]