diff --git a/scripts/protocol-gen-swift.ts b/scripts/protocol-gen-swift.ts index 4f3033a05e9..5fdb860706b 100644 --- a/scripts/protocol-gen-swift.ts +++ b/scripts/protocol-gen-swift.ts @@ -80,12 +80,54 @@ function safeName(name: string) { // filled later once schemas are loaded const schemaNameByObject = new Map(); +const schemaNameBySignature = new Map(); +const duplicateSchemaSignatures = new Set(); -function swiftType(schema: JsonSchema, required: boolean): string { +function stableJson(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(stableJson); + } + if (value && typeof value === "object") { + const record = value as Record; + return Object.fromEntries( + Object.keys(record) + .toSorted() + .map((key) => [key, stableJson(record[key])]), + ); + } + return value; +} + +function schemaSignature(schema: JsonSchema): string { + return JSON.stringify(stableJson(schema)); +} + +function registerNamedSchema(name: string, schema: JsonSchema): void { + schemaNameByObject.set(schema as object, name); + const signature = schemaSignature(schema); + if (duplicateSchemaSignatures.has(signature)) { + return; + } + if (schemaNameBySignature.has(signature)) { + schemaNameBySignature.delete(signature); + duplicateSchemaSignatures.add(signature); + return; + } + schemaNameBySignature.set(signature, name); +} + +function namedSchema(schema: JsonSchema, allowStructuralFallback = false): string | undefined { + return ( + schemaNameByObject.get(schema as object) ?? + (allowStructuralFallback ? schemaNameBySignature.get(schemaSignature(schema)) : undefined) + ); +} + +function swiftType(schema: JsonSchema, required: boolean, allowStructuralNamed = false): string { const t = schema.type; const isOptional = !required; let base: string; - const named = schemaNameByObject.get(schema as object); + const named = namedSchema(schema, allowStructuralNamed); if (named) { base = named; } else if (t === "string") { @@ -97,7 +139,7 @@ function swiftType(schema: JsonSchema, required: boolean): string { } else if (t === "boolean") { base = "Bool"; } else if (t === "array") { - base = `[${swiftType(schema.items ?? { type: "Any" }, true)}]`; + base = `[${swiftType(schema.items ?? { type: "Any" }, true, true)}]`; } else if (schema.enum) { base = "String"; } else if (schema.patternProperties) { @@ -214,7 +256,7 @@ async function generate() { const definitions = Object.entries(ProtocolSchemas) as Array<[string, JsonSchema]>; for (const [name, schema] of definitions) { - schemaNameByObject.set(schema as object, name); + registerNamedSchema(name, schema); } const parts: string[] = [];