diff --git a/lib/codegen/fromcto/rust/rustvisitor.js b/lib/codegen/fromcto/rust/rustvisitor.js index a356bf0..7d7d6c9 100644 --- a/lib/codegen/fromcto/rust/rustvisitor.js +++ b/lib/codegen/fromcto/rust/rustvisitor.js @@ -729,11 +729,8 @@ class RustVisitor { * @private */ visitMapDeclaration(mapDeclaration, parameters) { - const mapKeyType = mapDeclaration.getKey().getType(); - const mapValueType = mapDeclaration.getValue().getType(); - - const keyType = this.toRustType(mapKeyType); - const valueType = this.toRustType(mapValueType); + const keyType = this.toRustMapEntryType(mapDeclaration, mapDeclaration.getKey()); + const valueType = this.toRustMapEntryType(mapDeclaration, mapDeclaration.getValue()); parameters.fileWriter.writeLine( 0, @@ -744,6 +741,25 @@ class RustVisitor { return null; } + /** + * Resolves the Rust type of a map key or value, unwrapping scalars + * to their underlying primitive since scalars are not emitted. + * @param {MapDeclaration} mapDeclaration - the owning map declaration + * @param {Object} entry - the map key or value type + * @return {string} the corresponding type in Rust + * @private + */ + toRustMapEntryType(mapDeclaration, entry) { + const type = entry.getType(); + if (!ModelUtil.isPrimitiveType(type)) { + const declaration = mapDeclaration.getModelFile().getType(type); + if (declaration?.isScalarDeclaration?.()) { + return this.toRustType(declaration.getType()); + } + } + return this.toRustType(type); + } + /** * Visitor design pattern * @param {RelationshipDeclaration} relationshipDeclaration - the object being visited @@ -755,6 +771,8 @@ class RustVisitor { let type = relationshipDeclaration.type; if (relationshipDeclaration.isArray?.()) { type = `Vec<${type}>`; + } else { + type = `Box<${type}>`; } if (relationshipDeclaration.isOptional?.()) { type = `Option<${type}>`; diff --git a/test/codegen/__snapshots__/codegen.js.snap b/test/codegen/__snapshots__/codegen.js.snap index c5b08e3..5a23fad 100644 --- a/test/codegen/__snapshots__/codegen.js.snap +++ b/test/codegen/__snapshots__/codegen.js.snap @@ -6083,7 +6083,7 @@ pub enum TShirtSizeType { LARGE, } -pub type EmployeeTShirtSizes = HashMap; +pub type EmployeeTShirtSizes = HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Address { @@ -6172,15 +6172,15 @@ pub struct Info { pub type CompanyProperties = HashMap; -pub type EmployeeLoginTimes = HashMap; +pub type EmployeeLoginTimes = HashMap>; -pub type EmployeeSocialSecurityNumbers = HashMap; +pub type EmployeeSocialSecurityNumbers = HashMap; -pub type NextOfKin = HashMap; +pub type NextOfKin = HashMap; pub type EmployeeProfiles = HashMap; -pub type EmployeeDirectory = HashMap; +pub type EmployeeDirectory = HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Company { @@ -6402,7 +6402,7 @@ pub struct Employee { rename = "manager", skip_serializing_if = "Option::is_none", )] - pub manager: Option, + pub manager: Option>, #[serde( rename = "email", @@ -6474,7 +6474,7 @@ pub struct Contractor { rename = "manager", skip_serializing_if = "Option::is_none", )] - pub manager: Option, + pub manager: Option>, #[serde( rename = "email", @@ -6582,7 +6582,7 @@ pub struct Manager { rename = "manager", skip_serializing_if = "Option::is_none", )] - pub manager: Option, + pub manager: Option>, #[serde( rename = "email", @@ -6663,7 +6663,7 @@ pub struct Onboarded { #[serde( rename = "employee", )] - pub employee: Employee, + pub employee: Box, #[serde( rename = "$timestamp", @@ -6683,7 +6683,7 @@ pub struct ChangeOfAddress { #[serde( rename = "Person", )] - pub person: Person, + pub person: Box, #[serde( rename = "newAddress", diff --git a/test/codegen/fromcto/rust/rustvisitor.js b/test/codegen/fromcto/rust/rustvisitor.js index 313856c..d39c2eb 100644 --- a/test/codegen/fromcto/rust/rustvisitor.js +++ b/test/codegen/fromcto/rust/rustvisitor.js @@ -1191,7 +1191,7 @@ describe('RustVisitor', function () { [1, '#[serde('], [2, 'rename = "Bob",'], [1, ')]'], - [1, 'pub bob: Person,'], + [1, 'pub bob: Box,'], ]); }); @@ -1236,7 +1236,7 @@ describe('RustVisitor', function () { [2, 'rename = "Bob",'], [2, 'skip_serializing_if = "Option::is_none",'], [1, ')]'], - [1, 'pub bob: Option,'], + [1, 'pub bob: Option>,'], ]); }); @@ -1775,6 +1775,29 @@ describe('RustVisitor', function () { ).calledOnce.should.be.ok; }); + it('should unwrap scalar key and value types to their primitives', () => { + mockMapDeclaration.getName.returns('DirectoryMap'); + mockMapDeclaration.getKey.returns(mockMapKey); + mockMapDeclaration.getValue.returns(mockMapValue); + mockMapKey.getType.returns('SSN'); + mockMapValue.getType.returns('Employee'); + mockMapDeclaration.getModelFile.returns(mockModelFile); + mockModelFile.getType.withArgs('SSN').returns({ + isScalarDeclaration: () => true, + getType: () => 'String', + }); + mockModelFile.getType.withArgs('Employee').returns({ + isScalarDeclaration: () => false, + }); + + rustVisitor.visitMapDeclaration(mockMapDeclaration, param); + + param.fileWriter.writeLine.withArgs( + 0, + 'pub type DirectoryMap = HashMap;' + ).calledOnce.should.be.ok; + }); + it('should handle DateTime key types', () => { mockMapDeclaration.getName.returns('EventMap'); mockMapDeclaration.getKey.returns(mockMapKey); diff --git a/test/verification/cases.js b/test/verification/cases.js index b253ac4..d58717a 100644 --- a/test/verification/cases.js +++ b/test/verification/cases.js @@ -47,7 +47,6 @@ const CASES = [ // jsonschema: 'empty Level enum produces invalid JSON Schema (enum must have >= 1 item)', protobuf:'Empty enum produces invalid Protobuf (enum must have >= 1 item)', graphql: 'map types emit invalid GraphQL SDL (key/value field syntax)', - rust: 'map declarations reference scalar key/value types (e.g. SSN) that the Rust visitor does not emit, so cargo check fails', avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build', }, @@ -61,7 +60,6 @@ const CASES = [ // jsonschema: 'ambiguous $ref for org.acme.hr@1.0.0.Person.nextOfKin', protobuf: 'Empty enum produces invalid Protobuf (enum must have >= 1 item) and scalar map keys (e.g. SSN) are not valid Proto3 map key types', graphql: 'map types emit invalid GraphQL SDL (key/value field syntax)', - rust: 'map declarations reference scalar key/value types (e.g. SSN, Time) that the Rust visitor does not emit, so cargo check fails', avro: 'versioned namespaces (e.g. concerto.decorator@1.0.0) are illegal Avro identifiers (segment "0")', openapi: 'JSON Schema $decorators and invalid schema nesting fail OpenAPI struct validation', golang: 'GoLangVisitor emits bare package-name imports (e.g. import "concerto_decorator_1_0_0") instead of Go module-relative import paths, so any cross-namespace model fails go build',