primitive-admin 1.0.53 → 1.0.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -10
- package/dist/bin/primitive.js +8 -0
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/apps.js +54 -2
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.js +16 -6
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/blob-buckets.js +84 -7
- package/dist/src/commands/blob-buckets.js.map +1 -1
- package/dist/src/commands/catalog.js +3 -3
- package/dist/src/commands/catalog.js.map +1 -1
- package/dist/src/commands/collections.js +12 -9
- package/dist/src/commands/collections.js.map +1 -1
- package/dist/src/commands/databases.js +220 -82
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +77 -0
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/group-type-configs.js +1 -9
- package/dist/src/commands/group-type-configs.js.map +1 -1
- package/dist/src/commands/groups.js +12 -6
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/guides.d.ts +161 -0
- package/dist/src/commands/guides.js +502 -84
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/integrations.js +2 -2
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/metadata.d.ts +2 -0
- package/dist/src/commands/metadata.js +344 -0
- package/dist/src/commands/metadata.js.map +1 -0
- package/dist/src/commands/prompts.js +2 -2
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.d.ts +1 -0
- package/dist/src/commands/rule-sets.js +25 -3
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +2 -0
- package/dist/src/commands/scripts.js +667 -0
- package/dist/src/commands/scripts.js.map +1 -0
- package/dist/src/commands/settings.d.ts +15 -0
- package/dist/src/commands/settings.js +102 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/sync-app-settings.d.ts +105 -0
- package/dist/src/commands/sync-app-settings.js +339 -0
- package/dist/src/commands/sync-app-settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +204 -9
- package/dist/src/commands/sync.js +1706 -371
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/vars.d.ts +8 -0
- package/dist/src/commands/vars.js +110 -0
- package/dist/src/commands/vars.js.map +1 -0
- package/dist/src/commands/webhooks.js +17 -3
- package/dist/src/commands/webhooks.js.map +1 -1
- package/dist/src/commands/workflows.d.ts +30 -0
- package/dist/src/commands/workflows.js +667 -59
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +122 -18
- package/dist/src/lib/api-client.js +131 -2
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
- package/dist/src/lib/app-settings-descriptor.js +250 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -0
- package/dist/src/lib/batch.d.ts +26 -0
- package/dist/src/lib/batch.js +32 -0
- package/dist/src/lib/batch.js.map +1 -0
- package/dist/src/lib/block-layout.d.ts +160 -0
- package/dist/src/lib/block-layout.js +451 -0
- package/dist/src/lib/block-layout.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +76 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js +144 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
- package/dist/src/lib/config-toml.d.ts +10 -0
- package/dist/src/lib/config-toml.js +42 -0
- package/dist/src/lib/config-toml.js.map +1 -0
- package/dist/src/lib/config.d.ts +16 -0
- package/dist/src/lib/config.js +41 -0
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/constants.d.ts +9 -0
- package/dist/src/lib/constants.js +9 -0
- package/dist/src/lib/constants.js.map +1 -1
- package/dist/src/lib/csv.d.ts +1 -2
- package/dist/src/lib/csv.js +3 -8
- package/dist/src/lib/csv.js.map +1 -1
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +21 -65
- package/dist/src/lib/db-codegen/dbGenerator.js +117 -321
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +37 -0
- package/dist/src/lib/db-codegen/dbNaming.js +70 -0
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +165 -5
- package/dist/src/lib/db-codegen/dbTemplates.js +292 -16
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +7 -3
- package/dist/src/lib/db-codegen/dbTsTypes.js +23 -1
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +101 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +200 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
- package/dist/src/lib/generated-allowlist.js +43 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/init-config.js +2 -2
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/output.d.ts +25 -5
- package/dist/src/lib/output.js +32 -4
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/query-operators.d.ts +43 -0
- package/dist/src/lib/query-operators.js +80 -0
- package/dist/src/lib/query-operators.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +68 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +380 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +84 -0
- package/dist/src/lib/swift-codegen/generator.js +178 -0
- package/dist/src/lib/swift-codegen/generator.js.map +1 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
- package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +225 -0
- package/dist/src/lib/sync-resource-types.js +394 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +10 -19
- package/dist/src/lib/toml-database-config.js +28 -59
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.d.ts +147 -0
- package/dist/src/lib/toml-metadata-config.js +463 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -0
- package/dist/src/lib/toml-native-form.d.ts +46 -0
- package/dist/src/lib/toml-native-form.js +78 -0
- package/dist/src/lib/toml-native-form.js.map +1 -0
- package/dist/src/lib/toml-params-validator.d.ts +34 -0
- package/dist/src/lib/toml-params-validator.js +118 -3
- package/dist/src/lib/toml-params-validator.js.map +1 -1
- package/dist/src/lib/workflow-apply.d.ts +86 -0
- package/dist/src/lib/workflow-apply.js +127 -0
- package/dist/src/lib/workflow-apply.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
- package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
- package/dist/src/lib/workflow-codegen/generator.d.ts +87 -0
- package/dist/src/lib/workflow-codegen/generator.js +290 -0
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +85 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
- package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
- package/dist/src/lib/workflow-codegen/naming.js +81 -0
- package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +64 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.js +282 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
- package/dist/src/lib/workflow-fragments.js +3 -3
- package/dist/src/lib/workflow-fragments.js.map +1 -1
- package/dist/src/lib/workflow-payload.d.ts +68 -0
- package/dist/src/lib/workflow-payload.js +150 -0
- package/dist/src/lib/workflow-payload.js.map +1 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +1 -1
- package/dist/src/lib/workflow-toml-validator.js +37 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +11 -0
- package/dist/src/validators.d.ts +4 -3
- package/dist/src/validators.js +6 -5
- package/dist/src/validators.js.map +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-Schema → Swift `Codable` converter for `swift-codegen` (issue #1547,
|
|
3
|
+
* Phase 1). The Swift counterpart of `workflow-codegen/schemaToTs.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Both emitters read the SAME drift-guarded interpretation of the JSON-Schema
|
|
6
|
+
* subset the workflow platform supports — the vendored `SchemaDescriptor`
|
|
7
|
+
* (`workflow-codegen/generated-schema-descriptor.ts`, a byte copy of the server
|
|
8
|
+
* validator). Only the emitted syntax differs, so a generated Swift type can
|
|
9
|
+
* never promise a shape the runtime validator rejects. See the design and
|
|
10
|
+
* maintainer decision on issue #1547.
|
|
11
|
+
*
|
|
12
|
+
* Where Swift diverges from TypeScript:
|
|
13
|
+
*
|
|
14
|
+
* - **Named types, not anonymous.** TS renders nested object/enum/`oneOf`
|
|
15
|
+
* types inline; Swift `Codable` needs a NAMED `struct`/`enum` for each, so
|
|
16
|
+
* this emitter declares nested types inside their enclosing type.
|
|
17
|
+
* - **Scalars** (Fork 4, maintainer-confirmed): `string`→`String`,
|
|
18
|
+
* `number`→`Double`, `integer`→`Int` (ergonomic divergence from TS
|
|
19
|
+
* `number`), `boolean`→`Bool`.
|
|
20
|
+
* - **Un-descended nodes** (`$ref`, `allOf`, `format`, tuple `items`,
|
|
21
|
+
* `patternProperties`, numeric constraints, multi-scalar non-null unions,
|
|
22
|
+
* mixed-type enums, open-object extra values) → `JSONValue`, the Swift
|
|
23
|
+
* analog of TS `unknown`.
|
|
24
|
+
* - **`never`** (an uninhabitable enum after the type gate) → a strict codegen
|
|
25
|
+
* error, since the field can never be constructed (Fork 4).
|
|
26
|
+
* - **Scalar `enum`, all strings** → a nested `enum … : String, Codable,
|
|
27
|
+
* CaseIterable, Sendable`, the same form the Swift model codegen emits.
|
|
28
|
+
* - **`oneOf` discriminated union** → an `enum` with associated values plus a
|
|
29
|
+
* generated `init(from:)`/`encode(to:)` keyed on the discriminator.
|
|
30
|
+
*
|
|
31
|
+
* Codable semantics adopted from the maintainer decision (binding item 4):
|
|
32
|
+
*
|
|
33
|
+
* - Every generated `struct` gets a generated **public memberwise
|
|
34
|
+
* initializer** (Swift's synthesized memberwise init is not `public` across
|
|
35
|
+
* module boundaries).
|
|
36
|
+
* - A **required-nullable** property (required, but its value may be `null`)
|
|
37
|
+
* is encoded with an EXPLICIT null (not omitted) so server-side
|
|
38
|
+
* required-key validation still sees the key; decode is lenient (a missing
|
|
39
|
+
* key decodes to `nil`).
|
|
40
|
+
* - An **open object** stores unknown keys in an `extra: [String: JSONValue]`
|
|
41
|
+
* catch-all via custom Codable that separates known keys from extras; a key
|
|
42
|
+
* that collides with a known property is owned by the known property (the
|
|
43
|
+
* extra copy is dropped on encode, and never captured on decode).
|
|
44
|
+
* - A `oneOf` branch `struct` CARRIES the discriminator as a single-case
|
|
45
|
+
* `String` enum property, so decoding a branch rejects a conflicting
|
|
46
|
+
* discriminator value, and encoding re-emits the tag from the branch.
|
|
47
|
+
*
|
|
48
|
+
* Errors are named (`SwiftCodegenError`) and carry the source file and schema
|
|
49
|
+
* path for diagnostics (binding item 6).
|
|
50
|
+
*/
|
|
51
|
+
import { describeSchema, isType, } from "../workflow-codegen/generated-schema-descriptor.js";
|
|
52
|
+
import { schemaRejectsEmptyObject } from "../workflow-codegen/schemaToTs.js";
|
|
53
|
+
import { enumCaseIdentifier, enumCaseNames, enumTypeName, escapeIfReserved, structTypeName, swiftPropertyName, unescapeIdentifier, } from "./swiftNaming.js";
|
|
54
|
+
/** Re-exported so the invoker phases share ONE required-vs-optional decision. */
|
|
55
|
+
export { schemaRejectsEmptyObject };
|
|
56
|
+
/** Conformances every generated struct/enum promises (all satisfied by `JSONValue`). */
|
|
57
|
+
const CONFORMANCES = "Codable, Equatable, Sendable";
|
|
58
|
+
const ACCESS = "public";
|
|
59
|
+
const INDENT_UNIT = " ";
|
|
60
|
+
/** A named codegen error carrying the source file + schema path (binding 6). */
|
|
61
|
+
export class SwiftCodegenError extends Error {
|
|
62
|
+
sourceFile;
|
|
63
|
+
schemaPath;
|
|
64
|
+
constructor(message, opts) {
|
|
65
|
+
const where = [opts?.sourceFile, opts?.schemaPath]
|
|
66
|
+
.filter((s) => s)
|
|
67
|
+
.join(" ");
|
|
68
|
+
super(where ? `${message} (at ${where})` : message);
|
|
69
|
+
this.name = "SwiftCodegenError";
|
|
70
|
+
this.sourceFile = opts?.sourceFile;
|
|
71
|
+
this.schemaPath = opts?.schemaPath;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const SCALAR_SWIFT = {
|
|
75
|
+
string: "String",
|
|
76
|
+
number: "Double",
|
|
77
|
+
integer: "Int",
|
|
78
|
+
boolean: "Bool",
|
|
79
|
+
};
|
|
80
|
+
/** A Swift string literal with the escapes Swift needs. */
|
|
81
|
+
function swiftString(value) {
|
|
82
|
+
const escaped = value
|
|
83
|
+
.replace(/\\/g, "\\\\")
|
|
84
|
+
.replace(/"/g, '\\"')
|
|
85
|
+
.replace(/\n/g, "\\n")
|
|
86
|
+
.replace(/\r/g, "\\r")
|
|
87
|
+
.replace(/\t/g, "\\t")
|
|
88
|
+
.replace(/\0/g, "\\0");
|
|
89
|
+
return `"${escaped}"`;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Render a full Swift type declaration for a ROOT schema (e.g. a workflow's
|
|
93
|
+
* `<Key>Input`/`<Key>Output`), including every nested type it needs. Returns the
|
|
94
|
+
* Swift source for the type only — the CLI adds the import/banner (Phase 2).
|
|
95
|
+
*/
|
|
96
|
+
export function renderSwiftRootType(rootTypeName, schema, ctx) {
|
|
97
|
+
const state = {
|
|
98
|
+
sourceFile: ctx?.sourceFile,
|
|
99
|
+
path: rootTypeName,
|
|
100
|
+
fail(message) {
|
|
101
|
+
throw new SwiftCodegenError(message, {
|
|
102
|
+
sourceFile: this.sourceFile,
|
|
103
|
+
schemaPath: this.path,
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
const d = describeSchema(schema);
|
|
108
|
+
// A qualifying `oneOf` becomes a root enum with associated values.
|
|
109
|
+
if (d.oneOf) {
|
|
110
|
+
if (d.oneOf.kind === "invalid") {
|
|
111
|
+
state.fail(`swift codegen: ${d.oneOf.reason}`);
|
|
112
|
+
}
|
|
113
|
+
return renderOneOfEnum(rootTypeName, d.oneOf, 0, state);
|
|
114
|
+
}
|
|
115
|
+
// A root scalar `enum` (all strings) becomes a top-level String enum.
|
|
116
|
+
const rootEnum = renderStringEnumIfApplicable(d, rootTypeName, 0, state);
|
|
117
|
+
if (rootEnum)
|
|
118
|
+
return rootEnum;
|
|
119
|
+
// A root object becomes a struct.
|
|
120
|
+
if (d.singularType === "object" && d.object) {
|
|
121
|
+
return renderStruct(rootTypeName, d.object, 0, state);
|
|
122
|
+
}
|
|
123
|
+
// Anything else (scalar, array, union, un-descended) is a typealias to the
|
|
124
|
+
// reference type. Nested type declarations (e.g. an array's element struct)
|
|
125
|
+
// are emitted before the alias.
|
|
126
|
+
const ref = renderTypeRef(schema, rootTypeName, 0, state);
|
|
127
|
+
const alias = `${ACCESS} typealias ${rootTypeName} = ${optionalize(ref)}`;
|
|
128
|
+
return ref.decls.length > 0 ? `${ref.decls.join("\n\n")}\n\n${alias}` : alias;
|
|
129
|
+
}
|
|
130
|
+
/** Apply `?` to a reference type when the node is nullable. */
|
|
131
|
+
function optionalize(ref) {
|
|
132
|
+
return ref.nullable ? `${ref.expr}?` : ref.expr;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Render a schema node as a Swift type reference, accumulating any nested type
|
|
136
|
+
* declarations. `nameHint` is the base name for a nested named type (usually the
|
|
137
|
+
* property name). `indent` is where the nested declarations are placed.
|
|
138
|
+
*/
|
|
139
|
+
function renderTypeRef(schema, nameHint, indent, state) {
|
|
140
|
+
const d = describeSchema(schema);
|
|
141
|
+
// Discriminated union → a named enum type.
|
|
142
|
+
if (d.oneOf) {
|
|
143
|
+
if (d.oneOf.kind === "invalid") {
|
|
144
|
+
state.fail(`swift codegen: ${d.oneOf.reason}`);
|
|
145
|
+
}
|
|
146
|
+
const typeName = structTypeName(nameHint);
|
|
147
|
+
const decl = renderOneOfEnum(typeName, d.oneOf, indent, {
|
|
148
|
+
...state,
|
|
149
|
+
path: `${state.path}.${nameHint}`,
|
|
150
|
+
});
|
|
151
|
+
return { expr: typeName, nullable: false, decls: [decl] };
|
|
152
|
+
}
|
|
153
|
+
// Scalar `enum`.
|
|
154
|
+
if (d.enumValues) {
|
|
155
|
+
return renderEnumRef(d, nameHint, indent, state);
|
|
156
|
+
}
|
|
157
|
+
// Type union (e.g. `["string","null"]`).
|
|
158
|
+
if (d.isUnion) {
|
|
159
|
+
return renderUnionRef(d);
|
|
160
|
+
}
|
|
161
|
+
// No usable single type, not an enum/union/oneOf → un-descended → JSONValue.
|
|
162
|
+
if (d.singularType === null) {
|
|
163
|
+
return { expr: "JSONValue", nullable: false, decls: [] };
|
|
164
|
+
}
|
|
165
|
+
if (d.singularType === "object") {
|
|
166
|
+
if (d.object) {
|
|
167
|
+
const typeName = structTypeName(nameHint);
|
|
168
|
+
const decl = renderStruct(typeName, d.object, indent, {
|
|
169
|
+
...state,
|
|
170
|
+
path: `${state.path}.${nameHint}`,
|
|
171
|
+
});
|
|
172
|
+
return { expr: typeName, nullable: false, decls: [decl] };
|
|
173
|
+
}
|
|
174
|
+
// Open object with no declared properties → an untyped dictionary.
|
|
175
|
+
return { expr: "[String: JSONValue]", nullable: false, decls: [] };
|
|
176
|
+
}
|
|
177
|
+
if (d.singularType === "array") {
|
|
178
|
+
const items = d.array ? d.array.items : undefined;
|
|
179
|
+
// Missing or tuple `items` (array of schemas) is not descended → [JSONValue].
|
|
180
|
+
if (!items || typeof items !== "object" || Array.isArray(items)) {
|
|
181
|
+
return { expr: "[JSONValue]", nullable: false, decls: [] };
|
|
182
|
+
}
|
|
183
|
+
const elem = renderTypeRef(items, `${nameHint}Item`, indent, {
|
|
184
|
+
...state,
|
|
185
|
+
path: `${state.path}.${nameHint}[]`,
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
expr: `[${optionalize(elem)}]`,
|
|
189
|
+
nullable: false,
|
|
190
|
+
decls: elem.decls,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
// `null` singular type, or an unknown/unmodeled type name → JSONValue.
|
|
194
|
+
const scalar = SCALAR_SWIFT[d.singularType];
|
|
195
|
+
if (scalar)
|
|
196
|
+
return { expr: scalar, nullable: false, decls: [] };
|
|
197
|
+
return {
|
|
198
|
+
expr: "JSONValue",
|
|
199
|
+
nullable: d.singularType === "null",
|
|
200
|
+
decls: [],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Render a scalar `enum` node. All-string members → a nested `String` enum;
|
|
205
|
+
* an empty reachable set → `never` (codegen error); anything else (mixed-type
|
|
206
|
+
* or non-string scalars) → `JSONValue` (Fork 4).
|
|
207
|
+
*/
|
|
208
|
+
function renderEnumRef(d, nameHint, indent, state) {
|
|
209
|
+
const decl = renderStringEnumIfApplicable(d, enumTypeName(nameHint), indent, state);
|
|
210
|
+
if (decl) {
|
|
211
|
+
return { expr: enumTypeName(nameHint), nullable: false, decls: [decl] };
|
|
212
|
+
}
|
|
213
|
+
// Not an all-string enum: mixed-type or non-string scalar values fall back to
|
|
214
|
+
// JSONValue (faithful to TS `unknown`). A `null` member makes the node
|
|
215
|
+
// nullable, which JSONValue already covers.
|
|
216
|
+
const hasNull = (d.enumValues ?? []).includes(null);
|
|
217
|
+
return { expr: "JSONValue", nullable: hasNull, decls: [] };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* If `d` is a scalar `enum` whose reachable members (after the type gate) are
|
|
221
|
+
* ALL strings, return a nested `String` enum declaration named `typeName`.
|
|
222
|
+
* Returns `null` when it is not an all-string enum. Throws on an uninhabitable
|
|
223
|
+
* (`never`) enum.
|
|
224
|
+
*/
|
|
225
|
+
function renderStringEnumIfApplicable(d, typeName, indent, state) {
|
|
226
|
+
if (!d.enumValues)
|
|
227
|
+
return null;
|
|
228
|
+
const allowedTypes = d.types;
|
|
229
|
+
const candidates = allowedTypes.length > 0
|
|
230
|
+
? d.enumValues.filter((v) => allowedTypes.some((t) => isType(v, t)))
|
|
231
|
+
: d.enumValues;
|
|
232
|
+
if (candidates.length === 0) {
|
|
233
|
+
// No enum member satisfies the declared type gate: the runtime rejects
|
|
234
|
+
// every value. This is `never` on the TS side; a Swift field that can never
|
|
235
|
+
// be constructed is a strict codegen error (Fork 4).
|
|
236
|
+
state.fail("swift codegen: enum has no value satisfying its declared type (uninhabitable / never)");
|
|
237
|
+
}
|
|
238
|
+
const allStrings = candidates.every((v) => typeof v === "string");
|
|
239
|
+
if (!allStrings)
|
|
240
|
+
return null;
|
|
241
|
+
const cases = enumCaseNames(candidates);
|
|
242
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
243
|
+
const inner = INDENT_UNIT.repeat(indent + 1);
|
|
244
|
+
const lines = [];
|
|
245
|
+
// Conformance list matches the Swift model codegen's nested enums
|
|
246
|
+
// (`String, Codable, CaseIterable, Sendable`). A String-raw enum is implicitly
|
|
247
|
+
// Equatable, so an enclosing struct's synthesized Equatable still works.
|
|
248
|
+
lines.push(`${pad}${ACCESS} enum ${typeName}: String, Codable, CaseIterable, Sendable {`);
|
|
249
|
+
for (const c of cases) {
|
|
250
|
+
lines.push(`${inner}case ${c.name} = ${swiftString(c.raw)}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push(`${pad}}`);
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Render a type union (`type: [...]`). A single non-null scalar (optionally with
|
|
257
|
+
* `null`) → that scalar (nullable if `null` present). Multiple distinct scalars,
|
|
258
|
+
* or any object/array member → `JSONValue` (no untagged union in Swift).
|
|
259
|
+
*/
|
|
260
|
+
function renderUnionRef(d) {
|
|
261
|
+
const hasNull = d.types.includes("null");
|
|
262
|
+
const nonNull = d.types.filter((t) => t !== "null");
|
|
263
|
+
const mapped = [];
|
|
264
|
+
for (const t of nonNull) {
|
|
265
|
+
const s = SCALAR_SWIFT[t];
|
|
266
|
+
if (!s) {
|
|
267
|
+
// object / array / unknown member → not representable as a Swift union.
|
|
268
|
+
return { expr: "JSONValue", nullable: hasNull, decls: [] };
|
|
269
|
+
}
|
|
270
|
+
if (!mapped.includes(s))
|
|
271
|
+
mapped.push(s);
|
|
272
|
+
}
|
|
273
|
+
if (mapped.length === 0) {
|
|
274
|
+
// `["null"]` only → JSONValue (which includes null).
|
|
275
|
+
return { expr: "JSONValue", nullable: true, decls: [] };
|
|
276
|
+
}
|
|
277
|
+
if (mapped.length === 1) {
|
|
278
|
+
return { expr: mapped[0], nullable: hasNull, decls: [] };
|
|
279
|
+
}
|
|
280
|
+
// Multi-scalar non-null union (e.g. ["string","number"]) → JSONValue (Fork 4).
|
|
281
|
+
return { expr: "JSONValue", nullable: hasNull, decls: [] };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Render an object descriptor as a Swift `struct`. `required[]` drives
|
|
285
|
+
* optionality; an open object gains an `extra: [String: JSONValue]` catch-all.
|
|
286
|
+
* Emits a public memberwise initializer always, `CodingKeys` when a wire key
|
|
287
|
+
* isn't its Swift name (or when custom Codable is needed), and custom
|
|
288
|
+
* `init(from:)`/`encode(to:)` when required-nullable properties or the
|
|
289
|
+
* open-object catch-all require it.
|
|
290
|
+
*/
|
|
291
|
+
function renderStruct(typeName, objectDesc, indent, state, forcedRequired = []) {
|
|
292
|
+
const { properties, required, additionalPropertiesClosed } = objectDesc;
|
|
293
|
+
const requiredSet = new Set([
|
|
294
|
+
...(Array.isArray(required)
|
|
295
|
+
? required.filter((k) => typeof k === "string")
|
|
296
|
+
: []),
|
|
297
|
+
...forcedRequired,
|
|
298
|
+
]);
|
|
299
|
+
const isOpen = !additionalPropertiesClosed;
|
|
300
|
+
const nestedDecls = [];
|
|
301
|
+
const usedTypeNames = new Set();
|
|
302
|
+
const props = [];
|
|
303
|
+
const usedSwiftNames = new Set();
|
|
304
|
+
const recordName = (name) => {
|
|
305
|
+
const bare = unescapeIdentifier(name);
|
|
306
|
+
if (usedSwiftNames.has(bare)) {
|
|
307
|
+
state.fail(`swift codegen: two properties map to the same Swift name \`${bare}\``);
|
|
308
|
+
}
|
|
309
|
+
usedSwiftNames.add(bare);
|
|
310
|
+
};
|
|
311
|
+
for (const wireKey of Object.keys(properties)) {
|
|
312
|
+
const ref = renderTypeRef(properties[wireKey], wireKey, indent + 1, {
|
|
313
|
+
...state,
|
|
314
|
+
path: `${state.path}.${wireKey}`,
|
|
315
|
+
});
|
|
316
|
+
for (const decl of ref.decls) {
|
|
317
|
+
// Nested type names live in the struct body; a collision is a codegen error.
|
|
318
|
+
const declName = declTypeName(decl);
|
|
319
|
+
if (declName) {
|
|
320
|
+
if (usedTypeNames.has(declName)) {
|
|
321
|
+
state.fail(`swift codegen: two nested types resolve to the same name \`${declName}\``);
|
|
322
|
+
}
|
|
323
|
+
usedTypeNames.add(declName);
|
|
324
|
+
}
|
|
325
|
+
nestedDecls.push(decl);
|
|
326
|
+
}
|
|
327
|
+
const swiftName = swiftPropertyName(wireKey);
|
|
328
|
+
recordName(swiftName);
|
|
329
|
+
const isRequired = requiredSet.has(wireKey);
|
|
330
|
+
props.push({
|
|
331
|
+
wireKey,
|
|
332
|
+
swiftName,
|
|
333
|
+
type: ref.expr,
|
|
334
|
+
optionalType: !isRequired || ref.nullable,
|
|
335
|
+
requiredNullable: isRequired && ref.nullable,
|
|
336
|
+
requiredNonNull: isRequired && !ref.nullable,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
// A key listed in `required` but absent from `properties` has no child schema,
|
|
340
|
+
// yet the validator still rejects an object missing it. Emit it as a required
|
|
341
|
+
// `JSONValue` member so the generated type demands the key too.
|
|
342
|
+
for (const wireKey of requiredSet) {
|
|
343
|
+
if (Object.prototype.hasOwnProperty.call(properties, wireKey))
|
|
344
|
+
continue;
|
|
345
|
+
const swiftName = swiftPropertyName(wireKey);
|
|
346
|
+
recordName(swiftName);
|
|
347
|
+
props.push({
|
|
348
|
+
wireKey,
|
|
349
|
+
swiftName,
|
|
350
|
+
type: "JSONValue",
|
|
351
|
+
optionalType: false,
|
|
352
|
+
requiredNullable: false,
|
|
353
|
+
requiredNonNull: true,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// An open object gains a catch-all stored property. Its Swift name must not
|
|
357
|
+
// collide with a modelled property (a wire key literally named `extra` is
|
|
358
|
+
// plausible), so pick the first free candidate and reserve it. If every
|
|
359
|
+
// candidate is taken, fail with a named error rather than emit a struct with
|
|
360
|
+
// two `var extra` declarations that will not compile.
|
|
361
|
+
let catchAllName = "";
|
|
362
|
+
if (isOpen) {
|
|
363
|
+
const candidates = ["extra", "additionalProperties", "extraProperties"];
|
|
364
|
+
catchAllName = candidates.find((c) => !usedSwiftNames.has(c)) ?? "";
|
|
365
|
+
if (catchAllName === "") {
|
|
366
|
+
state.fail(`swift codegen: cannot name the open-object catch-all for \`${typeName}\` — ` +
|
|
367
|
+
`properties already use every candidate name (${candidates.join(", ")})`);
|
|
368
|
+
}
|
|
369
|
+
usedSwiftNames.add(catchAllName);
|
|
370
|
+
}
|
|
371
|
+
const needsCustomCodable = isOpen || props.some((p) => p.requiredNullable);
|
|
372
|
+
const needsCodingKeys = needsCustomCodable ||
|
|
373
|
+
props.some((p) => unescapeIdentifier(p.swiftName) !== p.wireKey);
|
|
374
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
375
|
+
const b = INDENT_UNIT.repeat(indent + 1);
|
|
376
|
+
const out = [];
|
|
377
|
+
out.push(`${pad}${ACCESS} struct ${typeName}: ${CONFORMANCES} {`);
|
|
378
|
+
// Nested type declarations first (already rendered at indent + 1).
|
|
379
|
+
for (const decl of nestedDecls) {
|
|
380
|
+
out.push(decl);
|
|
381
|
+
}
|
|
382
|
+
if (nestedDecls.length > 0)
|
|
383
|
+
out.push("");
|
|
384
|
+
// Stored properties.
|
|
385
|
+
for (const p of props) {
|
|
386
|
+
out.push(`${b}${ACCESS} var ${p.swiftName}: ${p.type}${p.optionalType ? "?" : ""}`);
|
|
387
|
+
}
|
|
388
|
+
if (isOpen) {
|
|
389
|
+
out.push(`${b}/// Extra keys the schema does not model, preserved round-trip.`);
|
|
390
|
+
out.push(`${b}${ACCESS} var ${catchAllName}: [String: JSONValue]`);
|
|
391
|
+
}
|
|
392
|
+
out.push("");
|
|
393
|
+
// Public memberwise initializer (synthesized memberwise init is not public
|
|
394
|
+
// across module boundaries — binding item 4).
|
|
395
|
+
out.push(renderMemberwiseInit(props, isOpen, catchAllName, indent + 1));
|
|
396
|
+
// CodingKeys.
|
|
397
|
+
if (needsCodingKeys) {
|
|
398
|
+
out.push("");
|
|
399
|
+
out.push(renderCodingKeys(props, indent + 1));
|
|
400
|
+
}
|
|
401
|
+
// Custom Codable when required-nullable or open-object catch-all needs it.
|
|
402
|
+
if (needsCustomCodable) {
|
|
403
|
+
out.push("");
|
|
404
|
+
out.push(renderCustomDecodeEncode(props, isOpen, catchAllName, indent + 1));
|
|
405
|
+
}
|
|
406
|
+
out.push(`${pad}}`);
|
|
407
|
+
return out.join("\n");
|
|
408
|
+
}
|
|
409
|
+
function renderMemberwiseInit(props, isOpen, catchAllName, indent) {
|
|
410
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
411
|
+
const body = INDENT_UNIT.repeat(indent + 1);
|
|
412
|
+
const params = [];
|
|
413
|
+
for (const p of props) {
|
|
414
|
+
const type = `${p.type}${p.optionalType ? "?" : ""}`;
|
|
415
|
+
// Optional-typed params default to nil for ergonomics.
|
|
416
|
+
params.push(`${p.swiftName}: ${type}${p.optionalType ? " = nil" : ""}`);
|
|
417
|
+
}
|
|
418
|
+
if (isOpen)
|
|
419
|
+
params.push(`${catchAllName}: [String: JSONValue] = [:]`);
|
|
420
|
+
const lines = [];
|
|
421
|
+
if (params.length === 0) {
|
|
422
|
+
lines.push(`${pad}${ACCESS} init() {}`);
|
|
423
|
+
return lines.join("\n");
|
|
424
|
+
}
|
|
425
|
+
lines.push(`${pad}${ACCESS} init(`);
|
|
426
|
+
params.forEach((param, i) => {
|
|
427
|
+
lines.push(`${body}${param}${i < params.length - 1 ? "," : ""}`);
|
|
428
|
+
});
|
|
429
|
+
lines.push(`${pad}) {`);
|
|
430
|
+
for (const p of props) {
|
|
431
|
+
lines.push(`${body}self.${p.swiftName} = ${p.swiftName}`);
|
|
432
|
+
}
|
|
433
|
+
if (isOpen)
|
|
434
|
+
lines.push(`${body}self.${catchAllName} = ${catchAllName}`);
|
|
435
|
+
lines.push(`${pad}}`);
|
|
436
|
+
return lines.join("\n");
|
|
437
|
+
}
|
|
438
|
+
function renderCodingKeys(props, indent) {
|
|
439
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
440
|
+
const body = INDENT_UNIT.repeat(indent + 1);
|
|
441
|
+
const lines = [];
|
|
442
|
+
lines.push(`${pad}${ACCESS} enum CodingKeys: String, CodingKey {`);
|
|
443
|
+
for (const p of props) {
|
|
444
|
+
const bare = unescapeIdentifier(p.swiftName);
|
|
445
|
+
const rhs = bare !== p.wireKey ? ` = ${swiftString(p.wireKey)}` : "";
|
|
446
|
+
lines.push(`${body}case ${p.swiftName}${rhs}`);
|
|
447
|
+
}
|
|
448
|
+
lines.push(`${pad}}`);
|
|
449
|
+
return lines.join("\n");
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Custom `init(from:)`/`encode(to:)` for a struct that needs explicit-null
|
|
453
|
+
* encoding (required-nullable) or an open-object `extra` catch-all.
|
|
454
|
+
*/
|
|
455
|
+
function renderCustomDecodeEncode(props, isOpen, catchAllName, indent) {
|
|
456
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
457
|
+
const b = INDENT_UNIT.repeat(indent + 1);
|
|
458
|
+
const b2 = INDENT_UNIT.repeat(indent + 2);
|
|
459
|
+
const lines = [];
|
|
460
|
+
// Decode.
|
|
461
|
+
lines.push(`${pad}${ACCESS} init(from decoder: Decoder) throws {`);
|
|
462
|
+
lines.push(`${b}let container = try decoder.container(keyedBy: CodingKeys.self)`);
|
|
463
|
+
for (const p of props) {
|
|
464
|
+
if (p.requiredNonNull) {
|
|
465
|
+
lines.push(`${b}self.${p.swiftName} = try container.decode(${p.type}.self, forKey: .${p.swiftName})`);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// Optional or required-nullable: lenient decode (a missing key → nil).
|
|
469
|
+
lines.push(`${b}self.${p.swiftName} = try container.decodeIfPresent(${p.type}.self, forKey: .${p.swiftName})`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (isOpen) {
|
|
473
|
+
lines.push(`${b}let dynamic = try decoder.container(keyedBy: DynamicCodingKey.self)`);
|
|
474
|
+
lines.push(`${b}var collected: [String: JSONValue] = [:]`);
|
|
475
|
+
lines.push(`${b}for key in dynamic.allKeys where !Self.knownKeys.contains(key.stringValue) {`);
|
|
476
|
+
lines.push(`${b2}collected[key.stringValue] = try dynamic.decode(JSONValue.self, forKey: key)`);
|
|
477
|
+
lines.push(`${b}}`);
|
|
478
|
+
lines.push(`${b}self.${catchAllName} = collected`);
|
|
479
|
+
}
|
|
480
|
+
lines.push(`${pad}}`);
|
|
481
|
+
lines.push("");
|
|
482
|
+
// Encode.
|
|
483
|
+
lines.push(`${pad}${ACCESS} func encode(to encoder: Encoder) throws {`);
|
|
484
|
+
lines.push(`${b}var container = encoder.container(keyedBy: CodingKeys.self)`);
|
|
485
|
+
for (const p of props) {
|
|
486
|
+
if (p.requiredNonNull || p.requiredNullable) {
|
|
487
|
+
// Required: always encode the key. For required-nullable this writes an
|
|
488
|
+
// EXPLICIT null when the value is nil (so server required-key validation
|
|
489
|
+
// still sees the key).
|
|
490
|
+
lines.push(`${b}try container.encode(${p.swiftName}, forKey: .${p.swiftName})`);
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
// Pure optional: omit the key when nil.
|
|
494
|
+
lines.push(`${b}try container.encodeIfPresent(${p.swiftName}, forKey: .${p.swiftName})`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (isOpen) {
|
|
498
|
+
lines.push(`${b}var dynamic = encoder.container(keyedBy: DynamicCodingKey.self)`);
|
|
499
|
+
lines.push(`${b}for (key, value) in ${catchAllName} where !Self.knownKeys.contains(key) {`);
|
|
500
|
+
lines.push(`${b2}try dynamic.encode(value, forKey: DynamicCodingKey(stringValue: key))`);
|
|
501
|
+
lines.push(`${b}}`);
|
|
502
|
+
}
|
|
503
|
+
lines.push(`${pad}}`);
|
|
504
|
+
if (isOpen) {
|
|
505
|
+
const wireKeys = props.map((p) => swiftString(p.wireKey)).join(", ");
|
|
506
|
+
lines.push("");
|
|
507
|
+
lines.push(`${pad}private static let knownKeys: Set<String> = [${wireKeys}]`);
|
|
508
|
+
lines.push("");
|
|
509
|
+
lines.push(`${pad}private struct DynamicCodingKey: CodingKey {`);
|
|
510
|
+
lines.push(`${b}var stringValue: String`);
|
|
511
|
+
lines.push(`${b}var intValue: Int? { nil }`);
|
|
512
|
+
lines.push(`${b}init(stringValue: String) { self.stringValue = stringValue }`);
|
|
513
|
+
lines.push(`${b}init?(intValue: Int) { nil }`);
|
|
514
|
+
lines.push(`${pad}}`);
|
|
515
|
+
}
|
|
516
|
+
return lines.join("\n");
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Render a valid `oneOf` discriminated union as a Swift `enum` with associated
|
|
520
|
+
* values plus a generated `init(from:)`/`encode(to:)`. Each branch `struct`
|
|
521
|
+
* carries the discriminator (forced required) as a single-case `String` enum, so
|
|
522
|
+
* decoding a branch rejects a conflicting discriminator value and encoding
|
|
523
|
+
* re-emits the tag (binding item 4).
|
|
524
|
+
*/
|
|
525
|
+
function renderOneOfEnum(typeName, oneOf, indent, state) {
|
|
526
|
+
const pad = INDENT_UNIT.repeat(indent);
|
|
527
|
+
const b = INDENT_UNIT.repeat(indent + 1);
|
|
528
|
+
const b2 = INDENT_UNIT.repeat(indent + 2);
|
|
529
|
+
const b3 = INDENT_UNIT.repeat(indent + 3);
|
|
530
|
+
const { discriminant, members } = oneOf;
|
|
531
|
+
// Assign each branch a case name (deduped) and a branch struct type name.
|
|
532
|
+
const usedCaseNames = new Map();
|
|
533
|
+
const usedBranchTypes = new Set();
|
|
534
|
+
const branches = members.map((m) => {
|
|
535
|
+
const valueStr = String(m.discriminantValue);
|
|
536
|
+
// Case name derived from the discriminator value, deduped. Dedup on the
|
|
537
|
+
// UN-escaped base and re-escape once, so a reserved-word discriminator
|
|
538
|
+
// (`default`) that collides gets `default_2`, not the uncompilable
|
|
539
|
+
// `` `default`_2 `` (suffix outside the backticks).
|
|
540
|
+
const bareCase = unescapeIdentifier(enumCaseIdentifier(valueStr));
|
|
541
|
+
const count = usedCaseNames.get(bareCase) ?? 0;
|
|
542
|
+
usedCaseNames.set(bareCase, count + 1);
|
|
543
|
+
const caseName = escapeIfReserved(count === 0 ? bareCase : `${bareCase}_${count + 1}`);
|
|
544
|
+
let branchType = structTypeName(`${valueStr}Branch`);
|
|
545
|
+
while (usedBranchTypes.has(branchType))
|
|
546
|
+
branchType = `${branchType}_`;
|
|
547
|
+
usedBranchTypes.add(branchType);
|
|
548
|
+
return { caseName, branchType, member: m };
|
|
549
|
+
});
|
|
550
|
+
const lines = [];
|
|
551
|
+
lines.push(`${pad}${ACCESS} enum ${typeName}: ${CONFORMANCES} {`);
|
|
552
|
+
for (const br of branches) {
|
|
553
|
+
lines.push(`${b}case ${br.caseName}(${br.branchType})`);
|
|
554
|
+
}
|
|
555
|
+
lines.push("");
|
|
556
|
+
// Nested branch structs. Each renders from its member object descriptor with
|
|
557
|
+
// the discriminant forced required.
|
|
558
|
+
for (const br of branches) {
|
|
559
|
+
const md = describeSchema(br.member.schema);
|
|
560
|
+
if (!md.object) {
|
|
561
|
+
// Guaranteed by the descriptor (every member is `type: "object"`), but be
|
|
562
|
+
// defensive rather than emit broken Swift.
|
|
563
|
+
state.fail(`swift codegen: oneOf branch for ${discriminant}=${String(br.member.discriminantValue)} is not an object`);
|
|
564
|
+
}
|
|
565
|
+
const decl = renderStruct(br.branchType, md.object, indent + 1, { ...state, path: `${state.path}.${br.branchType}` }, [discriminant]);
|
|
566
|
+
lines.push(decl);
|
|
567
|
+
lines.push("");
|
|
568
|
+
}
|
|
569
|
+
// Discriminator coding key.
|
|
570
|
+
const tagKind = discriminatorTagKind(members.map((m) => m.discriminantValue));
|
|
571
|
+
lines.push(`${b}private enum DiscriminatorKey: String, CodingKey {`);
|
|
572
|
+
lines.push(`${b2}case tag = ${swiftString(discriminant)}`);
|
|
573
|
+
lines.push(`${b}}`);
|
|
574
|
+
lines.push("");
|
|
575
|
+
// init(from:) — read the tag, select the branch (which re-validates the tag).
|
|
576
|
+
lines.push(`${b}${ACCESS} init(from decoder: Decoder) throws {`);
|
|
577
|
+
lines.push(`${b2}let container = try decoder.container(keyedBy: DiscriminatorKey.self)`);
|
|
578
|
+
if (tagKind.kind === "scalar") {
|
|
579
|
+
lines.push(`${b2}let tag = try container.decode(${tagKind.swiftType}.self, forKey: .tag)`);
|
|
580
|
+
lines.push(`${b2}switch tag {`);
|
|
581
|
+
for (const br of branches) {
|
|
582
|
+
lines.push(`${b2}case ${tagKind.literal(br.member.discriminantValue)}:`);
|
|
583
|
+
lines.push(`${b3}self = .${br.caseName}(try ${br.branchType}(from: decoder))`);
|
|
584
|
+
}
|
|
585
|
+
lines.push(`${b2}default:`);
|
|
586
|
+
lines.push(`${b3}throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: ${swiftString(`Unknown ${discriminant} discriminator for ${typeName}`)}))`);
|
|
587
|
+
lines.push(`${b2}}`);
|
|
588
|
+
}
|
|
589
|
+
else {
|
|
590
|
+
// Mixed-type discriminator values → decode as JSONValue and compare.
|
|
591
|
+
lines.push(`${b2}let tag = try container.decode(JSONValue.self, forKey: .tag)`);
|
|
592
|
+
branches.forEach((br, i) => {
|
|
593
|
+
const kw = i === 0 ? "if" : "} else if";
|
|
594
|
+
lines.push(`${b2}${kw} tag == ${jsonValueLiteral(br.member.discriminantValue)} {`);
|
|
595
|
+
lines.push(`${b3}self = .${br.caseName}(try ${br.branchType}(from: decoder))`);
|
|
596
|
+
});
|
|
597
|
+
lines.push(`${b2}} else {`);
|
|
598
|
+
lines.push(`${b3}throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: ${swiftString(`Unknown ${discriminant} discriminator for ${typeName}`)}))`);
|
|
599
|
+
lines.push(`${b2}}`);
|
|
600
|
+
}
|
|
601
|
+
lines.push(`${b}}`);
|
|
602
|
+
lines.push("");
|
|
603
|
+
// encode(to:) — delegate to the branch (which re-emits the tag).
|
|
604
|
+
lines.push(`${b}${ACCESS} func encode(to encoder: Encoder) throws {`);
|
|
605
|
+
lines.push(`${b2}switch self {`);
|
|
606
|
+
for (const br of branches) {
|
|
607
|
+
lines.push(`${b2}case .${br.caseName}(let value):`);
|
|
608
|
+
lines.push(`${b3}try value.encode(to: encoder)`);
|
|
609
|
+
}
|
|
610
|
+
lines.push(`${b2}}`);
|
|
611
|
+
lines.push(`${b}}`);
|
|
612
|
+
lines.push(`${pad}}`);
|
|
613
|
+
return lines.join("\n");
|
|
614
|
+
}
|
|
615
|
+
/** Determine how to decode + match a set of discriminator literals. */
|
|
616
|
+
function discriminatorTagKind(values) {
|
|
617
|
+
if (values.every((v) => typeof v === "string")) {
|
|
618
|
+
return { kind: "scalar", swiftType: "String", literal: (v) => swiftString(v) };
|
|
619
|
+
}
|
|
620
|
+
if (values.every((v) => typeof v === "number")) {
|
|
621
|
+
return { kind: "scalar", swiftType: "Double", literal: (v) => `${v}` };
|
|
622
|
+
}
|
|
623
|
+
if (values.every((v) => typeof v === "boolean")) {
|
|
624
|
+
return { kind: "scalar", swiftType: "Bool", literal: (v) => `${v}` };
|
|
625
|
+
}
|
|
626
|
+
return { kind: "mixed" };
|
|
627
|
+
}
|
|
628
|
+
/** A `JSONValue` literal for a scalar discriminator (mixed-type branch). */
|
|
629
|
+
function jsonValueLiteral(v) {
|
|
630
|
+
if (typeof v === "string")
|
|
631
|
+
return `JSONValue.string(${swiftString(v)})`;
|
|
632
|
+
if (typeof v === "number")
|
|
633
|
+
return `JSONValue.number(${v})`;
|
|
634
|
+
if (typeof v === "boolean")
|
|
635
|
+
return `JSONValue.bool(${v})`;
|
|
636
|
+
return "JSONValue.null";
|
|
637
|
+
}
|
|
638
|
+
/** Extract the declared type name from a `struct`/`enum` declaration's first line. */
|
|
639
|
+
function declTypeName(decl) {
|
|
640
|
+
const firstLine = decl.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
641
|
+
const m = firstLine.match(/\b(?:struct|enum)\s+([A-Za-z_][A-Za-z0-9_]*)/);
|
|
642
|
+
return m ? m[1] : null;
|
|
643
|
+
}
|
|
644
|
+
//# sourceMappingURL=schemaToSwift.js.map
|