Skip to content

Commit a9eb032

Browse files
fix(ruby): implement convertObjectToHashEntries for samePropertiesAsObject discriminated unions
Co-Authored-By: [email protected] <[email protected]>
1 parent 642742c commit a9eb032

File tree

2 files changed

+50
-27
lines changed

2 files changed

+50
-27
lines changed

generators/ruby-v2/dynamic-snippets/src/__test__/__snapshots__/DynamicSnippetsGenerator.test.ts.snap

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ client.service.create_big_entity(
4646
metadata: {},
4747
revenue: 1000000
4848
},
49+
event_info: {
50+
id: 'event-12345',
51+
data: {
52+
key1: 'val1',
53+
key2: 'val2'
54+
},
55+
json_string: 'abc'
56+
},
4957
migration: {
5058
name: 'Migration 31 Aug',
5159
status: 'RUNNING'

generators/ruby-v2/dynamic-snippets/src/context/DynamicToLiteralMapper.ts

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,19 @@ export class DynamicTypeLiteralMapper {
227227
if (named == null) {
228228
return undefined;
229229
}
230-
const converted = this.convertNamed({ named, value: discriminatedUnionTypeInstance.value });
231-
// For Ruby, we expect a hash back from convertNamed for objects
232-
// We need to extract the entries from the hash
233-
return this.extractHashEntries(converted);
230+
// For samePropertiesAsObject, the named type should be an object
231+
// We convert it directly to hash entries instead of going through convertNamed
232+
if (named.type !== "object") {
233+
this.context.errors.add({
234+
severity: Severity.Critical,
235+
message: "Internal error; expected union value to be an object"
236+
});
237+
return undefined;
238+
}
239+
return this.convertObjectToHashEntries({
240+
object: named,
241+
value: discriminatedUnionTypeInstance.value
242+
});
234243
}
235244
case "singleProperty": {
236245
try {
@@ -271,19 +280,6 @@ export class DynamicTypeLiteralMapper {
271280
}
272281
}
273282

274-
private extractHashEntries(node: ruby.AstNode): ruby.HashEntry[] | undefined {
275-
// This is a workaround to extract hash entries from a TypeLiteral
276-
// We need to check if the node is a hash and extract its entries
277-
// For now, we'll return an empty array if we can't extract entries
278-
// The node should be a hash created by convertObject
279-
if (node instanceof ruby.TypeLiteral) {
280-
// TypeLiteral doesn't expose internal type, so we need to work around this
281-
// by re-converting the value as an object
282-
return undefined;
283-
}
284-
return undefined;
285-
}
286-
287283
private convertEnum({ enum_, value }: { enum_: FernIr.dynamic.EnumType; value: unknown }): ruby.AstNode {
288284
const name = this.getEnumValueName({ enum_, value });
289285
if (name == null) {
@@ -426,29 +422,48 @@ export class DynamicTypeLiteralMapper {
426422
}
427423

428424
private convertObject({ object, value }: { object: FernIr.dynamic.ObjectType; value: unknown }): ruby.AstNode {
425+
const entries = this.convertObjectToHashEntries({ object, value });
426+
if (entries == null) {
427+
return ruby.TypeLiteral.nop();
428+
}
429+
return ruby.TypeLiteral.hash(entries);
430+
}
431+
432+
private convertObjectToHashEntries({
433+
object,
434+
value
435+
}: {
436+
object: FernIr.dynamic.ObjectType;
437+
value: unknown;
438+
}): ruby.HashEntry[] | undefined {
429439
if (typeof value !== "object" || value == null) {
430440
this.context.errors.add({
431441
severity: Severity.Critical,
432442
message: `Expected object but got: ${value == null ? "null" : typeof value}`
433443
});
434-
return ruby.TypeLiteral.nop();
444+
return undefined;
435445
}
436446

437-
return ruby.TypeLiteral.hash(
438-
Object.entries(value as Record<string, unknown>).map(([key, val]) => {
439-
this.context.errors.scope(key);
447+
const entries: ruby.HashEntry[] = [];
448+
449+
for (const [key, val] of Object.entries(value as Record<string, unknown>)) {
450+
this.context.errors.scope(key);
451+
try {
440452
const property = object.properties.find((p) => p.name.wireValue === key);
441-
const typeReference = property?.typeReference ?? { type: "unknown" };
453+
const typeReference = property?.typeReference ?? { type: "unknown" as const };
442454
// Use snake_case property name for Ruby, falling back to wire value if not found
443455
const propertyName = property?.name.name.snakeCase.safeName ?? key;
444-
const astNode = {
456+
457+
entries.push({
445458
key: ruby.TypeLiteral.string(propertyName),
446459
value: this.convert({ typeReference, value: val })
447-
};
460+
});
461+
} finally {
448462
this.context.errors.unscope();
449-
return astNode;
450-
})
451-
);
463+
}
464+
}
465+
466+
return entries;
452467
}
453468

454469
private getValueAsNumber({

0 commit comments

Comments
 (0)