From 7de9bb1bf9d7ac820759d46dbd4b9f209718f8a5 Mon Sep 17 00:00:00 2001 From: Khushboo <68757952+desaikd@users.noreply.github.com> Date: Thu, 24 Oct 2024 13:33:33 -0700 Subject: [PATCH] Adds support for Java enum generation (#158) * adds changes for enum support * adds tests for enum generation * Adds changes for placeholder Rust enum template * Fixes bug for Rust namespace in generated code --- .../enum_type/invalid_case_enum_varaint.ion | 1 + .../bad/enum_type/invalid_enum_variant.ion | 1 + .../bad/enum_type/mismatched_enum_type.ion | 1 + .../mismatched_sequence_element_type.ion | 7 ++ .../mismatched_sequence_type.ion | 7 ++ .../mismatched_type.ion | 7 ++ .../missing_required_fields.ion | 7 ++ .../input/good/enum_type/valid_value_1.ion | 1 + .../input/good/enum_type/valid_value_2.ion | 1 + .../input/good/enum_type/valid_value_3.ion | 1 + .../input/good/enum_type/valid_value_4.ion | 1 + .../struct_with_enum_fields/empty_values.ion | 7 ++ .../struct_with_enum_fields/valid_fields.ion | 8 ++ .../valid_optional_fields.ion | 8 ++ .../valid_unordered_fields.ion | 8 ++ .../test/java/org/example/CodeGenTest.java | 21 ++++ code-gen-projects/schema/enum_type.isl | 4 + .../schema/struct_with_enum_fields.isl | 12 ++ src/bin/ion/commands/generate/generator.rs | 113 +++++++++++++++++- src/bin/ion/commands/generate/model.rs | 43 ++++++- src/bin/ion/commands/generate/result.rs | 12 +- .../generate/templates/java/enum.templ | 57 +++++++++ .../generate/templates/java/nested_type.templ | 2 + .../ion/commands/generate/templates/mod.rs | 2 + .../generate/templates/rust/enum.templ | 22 ++++ .../generate/templates/rust/nested_type.templ | 4 +- src/bin/ion/commands/generate/utils.rs | 22 ++++ 27 files changed, 372 insertions(+), 8 deletions(-) create mode 100644 code-gen-projects/input/bad/enum_type/invalid_case_enum_varaint.ion create mode 100644 code-gen-projects/input/bad/enum_type/invalid_enum_variant.ion create mode 100644 code-gen-projects/input/bad/enum_type/mismatched_enum_type.ion create mode 100644 code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_element_type.ion create mode 100644 code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_type.ion create mode 100644 code-gen-projects/input/bad/struct_with_enum_fields/mismatched_type.ion create mode 100644 code-gen-projects/input/bad/struct_with_enum_fields/missing_required_fields.ion create mode 100644 code-gen-projects/input/good/enum_type/valid_value_1.ion create mode 100644 code-gen-projects/input/good/enum_type/valid_value_2.ion create mode 100644 code-gen-projects/input/good/enum_type/valid_value_3.ion create mode 100644 code-gen-projects/input/good/enum_type/valid_value_4.ion create mode 100644 code-gen-projects/input/good/struct_with_enum_fields/empty_values.ion create mode 100644 code-gen-projects/input/good/struct_with_enum_fields/valid_fields.ion create mode 100644 code-gen-projects/input/good/struct_with_enum_fields/valid_optional_fields.ion create mode 100644 code-gen-projects/input/good/struct_with_enum_fields/valid_unordered_fields.ion create mode 100644 code-gen-projects/schema/enum_type.isl create mode 100644 code-gen-projects/schema/struct_with_enum_fields.isl create mode 100644 src/bin/ion/commands/generate/templates/java/enum.templ create mode 100644 src/bin/ion/commands/generate/templates/rust/enum.templ diff --git a/code-gen-projects/input/bad/enum_type/invalid_case_enum_varaint.ion b/code-gen-projects/input/bad/enum_type/invalid_case_enum_varaint.ion new file mode 100644 index 0000000..587dfa5 --- /dev/null +++ b/code-gen-projects/input/bad/enum_type/invalid_case_enum_varaint.ion @@ -0,0 +1 @@ +FoobarBaz // expected FooBarBaz, found FoobarBaz \ No newline at end of file diff --git a/code-gen-projects/input/bad/enum_type/invalid_enum_variant.ion b/code-gen-projects/input/bad/enum_type/invalid_enum_variant.ion new file mode 100644 index 0000000..06a63d6 --- /dev/null +++ b/code-gen-projects/input/bad/enum_type/invalid_enum_variant.ion @@ -0,0 +1 @@ +hello // expected (foo, bar, baz or FooBarBaz) found hello \ No newline at end of file diff --git a/code-gen-projects/input/bad/enum_type/mismatched_enum_type.ion b/code-gen-projects/input/bad/enum_type/mismatched_enum_type.ion new file mode 100644 index 0000000..65c2d6b --- /dev/null +++ b/code-gen-projects/input/bad/enum_type/mismatched_enum_type.ion @@ -0,0 +1 @@ +"foo" // expected a symbol value foo for enum, found string "foo" \ No newline at end of file diff --git a/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_element_type.ion b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_element_type.ion new file mode 100644 index 0000000..723ed82 --- /dev/null +++ b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_element_type.ion @@ -0,0 +1,7 @@ +// struct with mismatched sequence element +{ + A: "hello", + B: 12, + C: (1 2 3), // expected sexpression of strings + D: 10e2 +} diff --git a/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_type.ion b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_type.ion new file mode 100644 index 0000000..300a99f --- /dev/null +++ b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_sequence_type.ion @@ -0,0 +1,7 @@ +// simple struct with type mismatched sequence type +{ + A: "hello", + B: 12, + C: ["foo", "bar", "baz"], // expected sexp + D: 10e2 +} diff --git a/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_type.ion b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_type.ion new file mode 100644 index 0000000..72253de --- /dev/null +++ b/code-gen-projects/input/bad/struct_with_enum_fields/mismatched_type.ion @@ -0,0 +1,7 @@ +// simple struct with type mismatched fields +{ + A: "hello", + B: false, // expected field type: int + C: ("foo" "bar" "baz"), + D: 10e2 +} diff --git a/code-gen-projects/input/bad/struct_with_enum_fields/missing_required_fields.ion b/code-gen-projects/input/bad/struct_with_enum_fields/missing_required_fields.ion new file mode 100644 index 0000000..e1995de --- /dev/null +++ b/code-gen-projects/input/bad/struct_with_enum_fields/missing_required_fields.ion @@ -0,0 +1,7 @@ +// simple struct with all valid fields +{ + A: "hello", + B: 12, + // C: ("foo" "bar" "baz"), // since `C` is a required field, this is an invalid struct + D: 10e2 +} diff --git a/code-gen-projects/input/good/enum_type/valid_value_1.ion b/code-gen-projects/input/good/enum_type/valid_value_1.ion new file mode 100644 index 0000000..1910281 --- /dev/null +++ b/code-gen-projects/input/good/enum_type/valid_value_1.ion @@ -0,0 +1 @@ +foo \ No newline at end of file diff --git a/code-gen-projects/input/good/enum_type/valid_value_2.ion b/code-gen-projects/input/good/enum_type/valid_value_2.ion new file mode 100644 index 0000000..ba0e162 --- /dev/null +++ b/code-gen-projects/input/good/enum_type/valid_value_2.ion @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/code-gen-projects/input/good/enum_type/valid_value_3.ion b/code-gen-projects/input/good/enum_type/valid_value_3.ion new file mode 100644 index 0000000..3f95386 --- /dev/null +++ b/code-gen-projects/input/good/enum_type/valid_value_3.ion @@ -0,0 +1 @@ +baz \ No newline at end of file diff --git a/code-gen-projects/input/good/enum_type/valid_value_4.ion b/code-gen-projects/input/good/enum_type/valid_value_4.ion new file mode 100644 index 0000000..7eff0b4 --- /dev/null +++ b/code-gen-projects/input/good/enum_type/valid_value_4.ion @@ -0,0 +1 @@ +FooBarBaz \ No newline at end of file diff --git a/code-gen-projects/input/good/struct_with_enum_fields/empty_values.ion b/code-gen-projects/input/good/struct_with_enum_fields/empty_values.ion new file mode 100644 index 0000000..a5c1386 --- /dev/null +++ b/code-gen-projects/input/good/struct_with_enum_fields/empty_values.ion @@ -0,0 +1,7 @@ +// struct with empty list, empty string and zeros +{ + C: (), + A: "", + B: 0, + D: 0e0, +} diff --git a/code-gen-projects/input/good/struct_with_enum_fields/valid_fields.ion b/code-gen-projects/input/good/struct_with_enum_fields/valid_fields.ion new file mode 100644 index 0000000..a32da8b --- /dev/null +++ b/code-gen-projects/input/good/struct_with_enum_fields/valid_fields.ion @@ -0,0 +1,8 @@ +// simple struct with all valid fields +{ + A: "hello", + B: 12, + C: ("foo" "bar" "baz"), + D: 10e2, + E: foo +} diff --git a/code-gen-projects/input/good/struct_with_enum_fields/valid_optional_fields.ion b/code-gen-projects/input/good/struct_with_enum_fields/valid_optional_fields.ion new file mode 100644 index 0000000..e423b49 --- /dev/null +++ b/code-gen-projects/input/good/struct_with_enum_fields/valid_optional_fields.ion @@ -0,0 +1,8 @@ +// simple struct with all valid fields +{ + A: "hello", + B: 12, + C: ("foo" "bar" "baz"), + // D: 10e2, // since `D` is optional field, this is a valid struct + E: foo, +} diff --git a/code-gen-projects/input/good/struct_with_enum_fields/valid_unordered_fields.ion b/code-gen-projects/input/good/struct_with_enum_fields/valid_unordered_fields.ion new file mode 100644 index 0000000..36a2d97 --- /dev/null +++ b/code-gen-projects/input/good/struct_with_enum_fields/valid_unordered_fields.ion @@ -0,0 +1,8 @@ +// struct with unordered fields +{ + C: ("foo" "bar" "baz"), + A: "hello", + B: 12, + E: foo, + D: 10e2, +} 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 6206ab2..318403a 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 @@ -127,6 +127,16 @@ void roundtripBadTestForNestedStruct() throws IOException { runRoundtripBadTest("/bad/nested_struct", NestedStruct::readFrom); } + @Test + void roundtripBadTestForStructWithEnumFields() throws IOException { + runRoundtripBadTest("/bad/struct_with_enum_fields", StructWithEnumFields::readFrom); + } + + @Test + void roundtripBadTestForEnumType() throws IOException { + runRoundtripBadTest("/bad/enum_type", EnumType::readFrom); + } + private void runRoundtripBadTest(String path, ReaderFunction readerFunction) throws IOException { File dir = new File(System.getenv("ION_INPUT") + path); String[] fileNames = dir.list(); @@ -161,6 +171,17 @@ void roundtripGoodTestForNestedStruct() throws IOException { runRoundtripGoodTest("/good/nested_struct", NestedStruct::readFrom, (item, writer) -> item.writeTo(writer)); } + @Test + void roundtripGoodTestForStructWithEnumFields() throws IOException { + runRoundtripGoodTest("/good/struct_with_enum_fields", StructWithEnumFields::readFrom, (item, writer) -> item.writeTo(writer)); + } + + + @Test + void roundtripGoodTestForEnumType() throws IOException { + runRoundtripGoodTest("/good/enum_type", EnumType::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/enum_type.isl b/code-gen-projects/schema/enum_type.isl new file mode 100644 index 0000000..5801db1 --- /dev/null +++ b/code-gen-projects/schema/enum_type.isl @@ -0,0 +1,4 @@ +type::{ + name: enum_type, + valid_values: [foo, bar, baz, FooBarBaz] +} \ No newline at end of file diff --git a/code-gen-projects/schema/struct_with_enum_fields.isl b/code-gen-projects/schema/struct_with_enum_fields.isl new file mode 100644 index 0000000..3ef77db --- /dev/null +++ b/code-gen-projects/schema/struct_with_enum_fields.isl @@ -0,0 +1,12 @@ +type::{ + name: struct_with_enum_fields, + type: struct, + fields: { + A: string, + B: int, + C: { element: string, type: sexp, occurs: required }, + D: float, + E: { valid_values: [foo, bar, baz] } + } +} + diff --git a/src/bin/ion/commands/generate/generator.rs b/src/bin/ion/commands/generate/generator.rs index af910e8..a2b899a 100644 --- a/src/bin/ion/commands/generate/generator.rs +++ b/src/bin/ion/commands/generate/generator.rs @@ -1,7 +1,8 @@ use crate::commands::generate::context::{CodeGenContext, SequenceType}; use crate::commands::generate::model::{ - AbstractDataType, DataModelNode, FieldPresence, FieldReference, FullyQualifiedTypeReference, - ScalarBuilder, SequenceBuilder, StructureBuilder, WrappedScalarBuilder, WrappedSequenceBuilder, + AbstractDataType, DataModelNode, EnumBuilder, FieldPresence, FieldReference, + FullyQualifiedTypeReference, ScalarBuilder, SequenceBuilder, StructureBuilder, + WrappedScalarBuilder, WrappedSequenceBuilder, }; use crate::commands::generate::result::{ invalid_abstract_data_type_error, invalid_abstract_data_type_raw_error, CodeGenResult, @@ -10,12 +11,14 @@ use crate::commands::generate::templates; use crate::commands::generate::utils::{IonSchemaType, Template}; use crate::commands::generate::utils::{JavaLanguage, Language, RustLanguage}; use convert_case::{Case, Casing}; +use ion_schema::external::ion_rs::element::Value; use ion_schema::isl::isl_constraint::{IslConstraint, IslConstraintValue}; use ion_schema::isl::isl_type::IslType; use ion_schema::isl::isl_type_reference::IslTypeRef; +use ion_schema::isl::util::ValidValue; use ion_schema::isl::IslSchema; use ion_schema::system::SchemaSystem; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::fs; use std::fs::OpenOptions; use std::io::Write; @@ -46,6 +49,7 @@ impl<'a> CodeGenerator<'a, RustLanguage> { ("struct.templ", templates::rust::STRUCT), ("scalar.templ", templates::rust::SCALAR), ("sequence.templ", templates::rust::SEQUENCE), + ("enum.templ", templates::rust::ENUM), ("util_macros.templ", templates::rust::UTIL_MACROS), ("import.templ", templates::rust::IMPORT), ("nested_type.templ", templates::rust::NESTED_TYPE), @@ -89,6 +93,7 @@ impl<'a> CodeGenerator<'a, JavaLanguage> { ("class.templ", templates::java::CLASS), ("scalar.templ", templates::java::SCALAR), ("sequence.templ", templates::java::SEQUENCE), + ("enum.templ", templates::java::ENUM), ("util_macros.templ", templates::java::UTIL_MACROS), ("nested_type.templ", templates::java::NESTED_TYPE), ]) @@ -328,7 +333,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { 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. - self.current_type_fully_qualified_name.pop(); + L::reset_namespace(&mut self.current_type_fully_qualified_name); } Ok(()) @@ -436,6 +441,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { // * The sequence type for `Sequence` will be stored based on `type` constraint with either `list` or `sexp`. // * If given list of constraints has any `type` constraint except `type: list`, `type: struct` and `type: sexp`, then `AbstractDataType::Scalar` needs to be constructed. // * The `base_type` for `Scalar` will be stored based on `type` constraint. + // * If given list of constraints has any `valid_values` constraint which contains exclusively symbol values, then `AbstractDataType::Enum` needs to be constructed. // * All the other constraints except the above ones are not yet supported by code generator. let abstract_data_type = if constraints .iter() @@ -455,6 +461,8 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { isl_type, )? } + } else if Self::contains_enum_constraints(constraints) { + self.build_enum_from_constraints(constraints, code_gen_context, isl_type)? } else if Self::contains_scalar_constraints(constraints) { if is_nested_type { self.build_scalar_from_constraints(constraints, code_gen_context, isl_type)? @@ -500,6 +508,20 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { && isl_type_ref.name().as_str() != "struct")) } + /// Verifies if the given constraints contain a `valid_values` constraint with only symbol values. + fn contains_enum_constraints(constraints: &[IslConstraint]) -> bool { + constraints.iter().any(|it| { + if let IslConstraintValue::ValidValues(valid_values) = it.constraint() { + valid_values + .values() + .iter() + .all(|val| matches!(val, ValidValue::Element(Value::Symbol(_)))) + } else { + false + } + }) + } + fn render_generated_code( &mut self, type_name: &str, @@ -697,6 +719,89 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { Ok(AbstractDataType::Structure(structure_builder.build()?)) } + /// Builds `AbstractDataType::Enum` from the given constraints. + /// e.g. for a given type definition as below: + /// ``` + /// type::{ + /// name: Foo, + /// type: symbol, + /// valid_values: [foo, bar, baz] + /// } + /// ``` + /// This method builds `AbstractDataType`as following: + /// ``` + /// AbstractDataType::Enum( + /// Enum { + /// name: vec!["org", "example", "Foo"], // assuming the namespace is `org.example` + /// variants: HashSet::from_iter( + /// vec![ + /// "foo", + /// "bar", + /// "baz" + /// ].iter()) // Represents enum variants + /// doc_comment: None // There is no doc comment defined in above ISL type def + /// source: IslType {name: "foo", .. } // Represents the `IslType` that is getting converted to `AbstractDataType` + /// } + /// ) + /// ``` + fn build_enum_from_constraints( + &mut self, + constraints: &[IslConstraint], + code_gen_context: &mut CodeGenContext, + parent_isl_type: &IslType, + ) -> CodeGenResult { + let mut enum_builder = EnumBuilder::default(); + enum_builder + .name(self.current_type_fully_qualified_name.to_owned()) + .source(parent_isl_type.to_owned()); + let mut found_base_type = false; + + for constraint in constraints { + match constraint.constraint() { + IslConstraintValue::ValidValues(valid_values_constraint) => { + let valid_values = valid_values_constraint + .values() + .iter() + .map(|v| match v { + ValidValue::Element(Value::Symbol(symbol_val) ) => { + symbol_val.text().map(|s| s.to_string()).ok_or(invalid_abstract_data_type_raw_error( + "Could not determine enum variant name", + )) + } + _ => invalid_abstract_data_type_error( + "Only `valid_values` constraint with values of type `symbol` are supported yet!" + ), + }) + .collect::>>()?; + enum_builder.variants(BTreeSet::from_iter(valid_values)); + } + IslConstraintValue::Type(isl_type_ref) => { + if isl_type_ref.name() != "symbol" { + return invalid_abstract_data_type_error( + "Only `valid_values` constraint with values of type `symbol` are supported yet!" + ); + } + + let _type_name = self.handle_duplicate_constraint( + found_base_type, + "type", + isl_type_ref, + FieldPresence::Required, + code_gen_context, + )?; + found_base_type = true; + } + _ => { + return invalid_abstract_data_type_error( + "Could not determine the abstract data type due to conflicting constraints", + ) + } + } + } + + Ok(AbstractDataType::Enum(enum_builder.build()?)) + } + /// Builds `AbstractDataType::WrappedScalar` from the given constraints. /// ``` /// type::{ diff --git a/src/bin/ion/commands/generate/model.rs b/src/bin/ion/commands/generate/model.rs index 2bb28bb..8ef7f88 100644 --- a/src/bin/ion/commands/generate/model.rs +++ b/src/bin/ion/commands/generate/model.rs @@ -1,6 +1,6 @@ use derive_builder::Builder; use ion_schema::isl::isl_type::IslType; -use std::collections::HashMap; +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. // Currently, this same data model is represented by `AbstractDataType` but it doesn't hold all the information for the template. @@ -182,6 +182,8 @@ pub enum AbstractDataType { WrappedSequence(WrappedSequence), // A collection of field name/value pairs (e.g. a map) Structure(Structure), + // Represents an enum type + Enum(Enum), } impl AbstractDataType { @@ -203,6 +205,9 @@ impl AbstractDataType { AbstractDataType::Structure(Structure { doc_comment, .. }) => { doc_comment.as_ref().map(|s| s.as_str()) } + AbstractDataType::Enum(Enum { doc_comment, .. }) => { + doc_comment.as_ref().map(|s| s.as_str()) + } } } @@ -219,6 +224,7 @@ impl AbstractDataType { Some(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()), } } } @@ -448,6 +454,41 @@ pub struct FieldReference( pub(crate) FieldPresence, ); +/// Represents an enum type +/// e.g. Given below ISL, +/// ``` +/// type::{ +/// name: enum_type, +/// valid_values: [foo, bar, baz] +/// } +/// ``` +/// Corresponding generated code in Rust would look like following: +/// ``` +/// enum EnumType { +/// Foo, +/// Bar, +/// Baz +/// } +/// ``` +#[allow(dead_code)] +#[derive(Debug, Clone, Builder, PartialEq, Serialize)] +#[builder(setter(into))] +pub struct Enum { + // Represents the fully qualified name for this data model + pub(crate) name: FullyQualifiedTypeName, + // The variants of this enum + variants: BTreeSet, + // Represents doc comment for the generated code + #[builder(default)] + doc_comment: Option, + // 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`. + #[serde(skip_serializing_if = "is_anonymous")] + #[serde(serialize_with = "serialize_type_name")] + source: IslType, +} + #[cfg(test)] mod model_tests { use super::*; diff --git a/src/bin/ion/commands/generate/result.rs b/src/bin/ion/commands/generate/result.rs index 8c6dcd6..d3ba082 100644 --- a/src/bin/ion/commands/generate/result.rs +++ b/src/bin/ion/commands/generate/result.rs @@ -1,6 +1,6 @@ use crate::commands::generate::model::{ - ScalarBuilderError, SequenceBuilderError, StructureBuilderError, WrappedScalarBuilderError, - WrappedSequenceBuilderError, + EnumBuilderError, ScalarBuilderError, SequenceBuilderError, StructureBuilderError, + WrappedScalarBuilderError, WrappedSequenceBuilderError, }; use ion_schema::result::IonSchemaError; use thiserror::Error; @@ -87,3 +87,11 @@ impl From for CodeGenError { } } } + +impl From for CodeGenError { + fn from(value: EnumBuilderError) -> Self { + CodeGenError::DataModelBuilderError { + description: value.to_string(), + } + } +} diff --git a/src/bin/ion/commands/generate/templates/java/enum.templ b/src/bin/ion/commands/generate/templates/java/enum.templ new file mode 100644 index 0000000..c3ea211 --- /dev/null +++ b/src/bin/ion/commands/generate/templates/java/enum.templ @@ -0,0 +1,57 @@ +{% set full_namespace = namespace | join(sep=".") %} +{% if is_nested == false %} +package {{ full_namespace }}; +import com.amazon.ion.IonReader; +import com.amazon.ion.IonException; +import com.amazon.ion.IonWriter; +import com.amazon.ion.IonType; +import java.io.IOException; +{% endif %} + +{# Verify that the abstract data type is a enum and store information for this enum #} +{% set enum_info = model.code_gen_type["Enum"] %} + +public {% if is_nested == true %} static {% endif %} enum {{ model.name }} { + {% for variant in enum_info["variants"] -%} + {{ variant | snake | upper }}("{{variant}}"), + {% endfor %}; + + private String textValue; + + {{model.name}}(String textValue) { + this.textValue = textValue; + } + + /** + * Writes a {{ model.name }} as Ion from an {@link IonWriter}. + * + * This method does not close the writer after writing is complete. + * The caller is responsible for closing the stream associated with the writer. + */ + public void writeTo(IonWriter writer) throws IOException { + writer.writeSymbol(this.textValue); + } + + /** + * Reads a {{ model.name }} from an {@link IonReader}. + * + * This method does not advance the reader at the current level. + * The caller is responsible for positioning the reader on the value to read. + */ + public static {{ model.name }} readFrom(IonReader reader) { + {# Enums are only supported for symbol types #} + if (reader.getType() != IonType.SYMBOL) { + throw new IonException("Expected symbol, found " + reader.getType() + " while reading {{ model.name }}"); + } + {# Reads given value as a string #} + String value = reader.stringValue(); + switch(value) { + {% for variant in enum_info["variants"] %} + case "{{ variant }}": + return {{ variant | snake | upper }}; + {% endfor %} + default: + throw new IonException(value + "is not a valid value for {{ model.name }}"); + } + } +} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/java/nested_type.templ b/src/bin/ion/commands/generate/templates/java/nested_type.templ index be4c0b2..644f0d2 100644 --- a/src/bin/ion/commands/generate/templates/java/nested_type.templ +++ b/src/bin/ion/commands/generate/templates/java/nested_type.templ @@ -4,5 +4,7 @@ {% macro nested_type(model, is_nested) -%} {% if model.code_gen_type is containing("Structure")%} {% include "class.templ" %} + {% elif model.code_gen_type is containing("Enum")%} + {% include "enum.templ" %} {% endif %} {% endmacro nested_type -%} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/mod.rs b/src/bin/ion/commands/generate/templates/mod.rs index ff60f9a..22edcac 100644 --- a/src/bin/ion/commands/generate/templates/mod.rs +++ b/src/bin/ion/commands/generate/templates/mod.rs @@ -17,6 +17,7 @@ pub(crate) mod java { pub(crate) const CLASS: &str = include_template!("java/class.templ"); pub(crate) const SCALAR: &str = include_template!("java/scalar.templ"); pub(crate) const SEQUENCE: &str = include_template!("java/sequence.templ"); + pub(crate) const ENUM: &str = include_template!("java/enum.templ"); pub(crate) const UTIL_MACROS: &str = include_template!("java/util_macros.templ"); pub(crate) const NESTED_TYPE: &str = include_template!("java/nested_type.templ"); } @@ -26,6 +27,7 @@ pub(crate) mod rust { pub(crate) const STRUCT: &str = include_template!("rust/struct.templ"); pub(crate) const SCALAR: &str = include_template!("rust/scalar.templ"); pub(crate) const SEQUENCE: &str = include_template!("rust/sequence.templ"); + pub(crate) const ENUM: &str = include_template!("rust/enum.templ"); pub(crate) const UTIL_MACROS: &str = include_template!("rust/util_macros.templ"); pub(crate) const RESULT: &str = include_template!("rust/result.templ"); pub(crate) const NESTED_TYPE: &str = include_template!("rust/nested_type.templ"); diff --git a/src/bin/ion/commands/generate/templates/rust/enum.templ b/src/bin/ion/commands/generate/templates/rust/enum.templ new file mode 100644 index 0000000..95ded38 --- /dev/null +++ b/src/bin/ion/commands/generate/templates/rust/enum.templ @@ -0,0 +1,22 @@ +// Enum support is not yet completed for Rust code generation +// This template is just used as placeholder for enums. + + +use {{ model.name | snake }}::{{ model.name }}; +pub mod {{ model.name | snake }} { + use super::*; + #[derive(Debug, Clone, Default)] + pub enum {{ model.name }} { + #[default] + Unit // This is just a placeholder variant for enum generation + } + impl {{ model.name }} { + pub fn read_from(reader: &mut Reader) -> SerdeResult { + todo!("Enums are not supported with code generation yet!") + } + + pub fn write_to(&self, writer: &mut W) -> SerdeResult<()> { + todo!("Enums are not supported with code generation yet!") + } + } +} \ No newline at end of file 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 ec991a8..ddaeb49 100644 --- a/src/bin/ion/commands/generate/templates/rust/nested_type.templ +++ b/src/bin/ion/commands/generate/templates/rust/nested_type.templ @@ -2,7 +2,9 @@ {# following macro defines an anonymous type as children class for its parent type definition #} {% macro nested_type(model, is_nested) -%} - {% if model.code_gen_type is containing("Structure")%} + {% if model.code_gen_type is containing("Structure")%} {% include "struct.templ" %} + {% elif model.code_gen_type is containing("Enum") %} + {% include "enum.templ" %} {% endif %} {% endmacro nested_type -%} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/utils.rs b/src/bin/ion/commands/generate/utils.rs index 897418e..b503888 100644 --- a/src/bin/ion/commands/generate/utils.rs +++ b/src/bin/ion/commands/generate/utils.rs @@ -71,6 +71,9 @@ pub trait Language { /// 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); + /// Resets the namespace when code generation is complete for a single ISL type + 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" /// In Rust, it will return "Option" @@ -154,6 +157,7 @@ impl Language for JavaLanguage { Template::Struct => "class".to_string(), Template::Scalar => "scalar".to_string(), Template::Sequence => "sequence".to_string(), + Template::Enum => "enum".to_string(), } } @@ -165,6 +169,11 @@ impl Language for JavaLanguage { namespace.push(type_name.to_case(Case::UpperCamel)) } + fn reset_namespace(namespace: &mut Vec) { + // resets the namespace by removing current abstract dta type name + namespace.pop(); + } + fn target_type_as_optional( target_type: FullyQualifiedTypeReference, ) -> FullyQualifiedTypeReference { @@ -283,6 +292,11 @@ impl Language for RustLanguage { Template::Struct => "struct".to_string(), Template::Scalar => "scalar".to_string(), Template::Sequence => "sequence".to_string(), + Template::Enum => { + //TODO: Rust enums are not supported yet + // The template `enum.templ` is just a placeholder + "enum".to_string() + } } } @@ -314,6 +328,12 @@ impl Language for RustLanguage { namespace.push(type_name.to_case(Case::UpperCamel)) // Add this type itself to the namespace path } + fn reset_namespace(namespace: &mut Vec) { + // Resets the namespace by removing current abstract data type name and module name + namespace.pop(); + namespace.pop(); + } + fn target_type_as_optional( target_type: FullyQualifiedTypeReference, ) -> FullyQualifiedTypeReference { @@ -342,6 +362,7 @@ pub enum Template { Struct, // Represents a template for a Rust struct or Java class with Ion struct value Sequence, // Represents a template for a Rust struct or Java class with Ion sequence value Scalar, // Represents a template for a Rust struct or Java class with Ion scalar value + Enum, // Represents a template for a Rust or Java enum } impl TryFrom<&DataModelNode> for Template { @@ -357,6 +378,7 @@ impl TryFrom<&DataModelNode> for Template { Ok(Template::Sequence) } AbstractDataType::Structure(_) => Ok(Template::Struct), + AbstractDataType::Enum(_) => Ok(Template::Enum), } } else { invalid_abstract_data_type_error(