Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 23 additions & 5 deletions lib/codegen/fromcto/rust/rustvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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}>`;
Expand Down
20 changes: 10 additions & 10 deletions test/codegen/__snapshots__/codegen.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6083,7 +6083,7 @@ pub enum TShirtSizeType {
LARGE,
}

pub type EmployeeTShirtSizes = HashMap<SSN, TShirtSizeType>;
pub type EmployeeTShirtSizes = HashMap<String, TShirtSizeType>;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Address {
Expand Down Expand Up @@ -6172,15 +6172,15 @@ pub struct Info {

pub type CompanyProperties = HashMap<String, String>;

pub type EmployeeLoginTimes = HashMap<String, Time>;
pub type EmployeeLoginTimes = HashMap<String, DateTime<Utc>>;

pub type EmployeeSocialSecurityNumbers = HashMap<String, SSN>;
pub type EmployeeSocialSecurityNumbers = HashMap<String, String>;

pub type NextOfKin = HashMap<KinName, KinTelephone>;
pub type NextOfKin = HashMap<String, String>;

pub type EmployeeProfiles = HashMap<String, Concept>;

pub type EmployeeDirectory = HashMap<SSN, Employee>;
pub type EmployeeDirectory = HashMap<String, Employee>;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Company {
Expand Down Expand Up @@ -6402,7 +6402,7 @@ pub struct Employee {
rename = "manager",
skip_serializing_if = "Option::is_none",
)]
pub manager: Option<Manager>,
pub manager: Option<Box<Manager>>,

#[serde(
rename = "email",
Expand Down Expand Up @@ -6474,7 +6474,7 @@ pub struct Contractor {
rename = "manager",
skip_serializing_if = "Option::is_none",
)]
pub manager: Option<Manager>,
pub manager: Option<Box<Manager>>,

#[serde(
rename = "email",
Expand Down Expand Up @@ -6582,7 +6582,7 @@ pub struct Manager {
rename = "manager",
skip_serializing_if = "Option::is_none",
)]
pub manager: Option<Manager>,
pub manager: Option<Box<Manager>>,

#[serde(
rename = "email",
Expand Down Expand Up @@ -6663,7 +6663,7 @@ pub struct Onboarded {
#[serde(
rename = "employee",
)]
pub employee: Employee,
pub employee: Box<Employee>,

#[serde(
rename = "$timestamp",
Expand All @@ -6683,7 +6683,7 @@ pub struct ChangeOfAddress {
#[serde(
rename = "Person",
)]
pub person: Person,
pub person: Box<Person>,

#[serde(
rename = "newAddress",
Expand Down
27 changes: 25 additions & 2 deletions test/codegen/fromcto/rust/rustvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -1191,7 +1191,7 @@ describe('RustVisitor', function () {
[1, '#[serde('],
[2, 'rename = "Bob",'],
[1, ')]'],
[1, 'pub bob: Person,'],
[1, 'pub bob: Box<Person>,'],
]);
});

Expand Down Expand Up @@ -1236,7 +1236,7 @@ describe('RustVisitor', function () {
[2, 'rename = "Bob",'],
[2, 'skip_serializing_if = "Option::is_none",'],
[1, ')]'],
[1, 'pub bob: Option<Person>,'],
[1, 'pub bob: Option<Box<Person>>,'],
]);
});

Expand Down Expand Up @@ -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<String, Employee>;'
).calledOnce.should.be.ok;
});

it('should handle DateTime key types', () => {
mockMapDeclaration.getName.returns('EventMap');
mockMapDeclaration.getKey.returns(mockMapKey);
Expand Down
2 changes: 0 additions & 2 deletions test/verification/cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
Expand All @@ -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',
Expand Down