@workos/oagen-emitters 0.18.3 → 0.19.0
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +16 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-1ckLMpgo.mjs → plugin-BXDPA9pJ.mjs} +581 -172
- package/dist/plugin-BXDPA9pJ.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/docs/sdk-architecture/rust.md +2 -2
- package/package.json +5 -5
- package/src/dotnet/enums.ts +11 -5
- package/src/dotnet/fixtures.ts +5 -2
- package/src/dotnet/index.ts +2 -1
- package/src/dotnet/models.ts +41 -10
- package/src/dotnet/naming.ts +10 -0
- package/src/dotnet/resources.ts +3 -3
- package/src/dotnet/tests.ts +8 -4
- package/src/go/fixtures.ts +4 -2
- package/src/go/index.ts +4 -0
- package/src/go/models.ts +4 -2
- package/src/go/naming.ts +10 -0
- package/src/go/resources.ts +22 -9
- package/src/go/tests.ts +3 -3
- package/src/kotlin/enums.ts +21 -11
- package/src/kotlin/index.ts +2 -1
- package/src/kotlin/models.ts +24 -9
- package/src/kotlin/naming.ts +11 -0
- package/src/kotlin/resources.ts +2 -2
- package/src/kotlin/tests.ts +7 -3
- package/src/node/enums.ts +8 -5
- package/src/node/field-plan.ts +3 -3
- package/src/node/index.ts +2 -1
- package/src/node/models.ts +69 -22
- package/src/node/naming.ts +10 -0
- package/src/node/options.ts +45 -1
- package/src/node/resources.ts +67 -18
- package/src/node/tests.ts +302 -31
- package/src/php/enums.ts +18 -5
- package/src/php/index.ts +13 -4
- package/src/php/models.ts +22 -10
- package/src/php/naming.ts +10 -0
- package/src/php/resources.ts +6 -4
- package/src/php/tests.ts +17 -5
- package/src/python/enums.ts +39 -28
- package/src/python/fixtures.ts +4 -3
- package/src/python/index.ts +2 -1
- package/src/python/models.ts +39 -24
- package/src/python/naming.ts +10 -0
- package/src/python/resources.ts +3 -3
- package/src/python/tests.ts +14 -9
- package/src/ruby/enums.ts +28 -19
- package/src/ruby/index.ts +2 -1
- package/src/ruby/models.ts +33 -19
- package/src/ruby/naming.ts +10 -0
- package/src/ruby/rbi.ts +20 -7
- package/src/ruby/resources.ts +2 -2
- package/src/ruby/tests.ts +6 -3
- package/src/rust/enums.ts +9 -1
- package/src/rust/index.ts +2 -1
- package/src/rust/models.ts +100 -15
- package/src/rust/naming.ts +10 -0
- package/src/rust/resources.ts +14 -3
- package/src/rust/tests.ts +2 -2
- package/src/shared/file-header.ts +13 -0
- package/src/shared/resolved-ops.ts +47 -0
- package/test/rust/models.test.ts +49 -0
- package/test/shared/synthetic-enum-seed.test.ts +79 -0
- package/dist/plugin-1ckLMpgo.mjs.map +0 -1
|
@@ -75,6 +75,49 @@ function groupByMount(ctx) {
|
|
|
75
75
|
return groups;
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
+
* Like {@link groupByMount}, but for a scoped (`--services`) run returns ONLY the
|
|
79
|
+
* mount groups the run selected (`ctx.scopedServices`, POST-MOUNT names). When
|
|
80
|
+
* scoping is inactive the full set is returned unchanged.
|
|
81
|
+
*
|
|
82
|
+
* Use this for PER-SERVICE resource/test emission. Do NOT use it for
|
|
83
|
+
* aggregate/barrel files (Rust `mod.rs`, Ruby `client.rbi`, the root client) —
|
|
84
|
+
* those must continue to list every service, so they keep calling
|
|
85
|
+
* {@link groupByMount} over the full set; otherwise a scoped run would drop
|
|
86
|
+
* sibling modules and break the build/type-check.
|
|
87
|
+
*/
|
|
88
|
+
function scopedMountGroups(ctx) {
|
|
89
|
+
const groups = groupByMount(ctx);
|
|
90
|
+
const scope = ctx.scopedServices;
|
|
91
|
+
if (!scope || scope.size === 0) return groups;
|
|
92
|
+
return new Map([...groups].filter(([mountName]) => scope.has(mountName)));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* True when a POST-MOUNT service name should be emitted in the current run.
|
|
96
|
+
* Inactive scoping (no `ctx.scopedServices`) ⇒ everything is in scope. Use this
|
|
97
|
+
* for inline per-service gates (e.g. manifest loops keyed by `getMountTarget`).
|
|
98
|
+
*/
|
|
99
|
+
function isMountInScope(mountName, ctx) {
|
|
100
|
+
const scope = ctx.scopedServices;
|
|
101
|
+
return !scope || scope.size === 0 || scope.has(mountName);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* True when a MODEL's per-model FILE should be written in the current run (FR-1.4).
|
|
105
|
+
* A scoped run sets `ctx.scopedModelNames` to the models reachable from the
|
|
106
|
+
* selected services; out-of-scope models are left untouched on disk. Inactive
|
|
107
|
+
* scoping ⇒ everything is in scope. NOTE: gate only the per-model FILE write —
|
|
108
|
+
* the model must still appear in barrels/indexes (built from the full set) so the
|
|
109
|
+
* untouched on-disk file stays importable.
|
|
110
|
+
*/
|
|
111
|
+
function isModelInScope(modelName, ctx) {
|
|
112
|
+
const scope = ctx.scopedModelNames;
|
|
113
|
+
return !scope || scope.has(modelName);
|
|
114
|
+
}
|
|
115
|
+
/** Like {@link isModelInScope} but for an ENUM's per-enum file (`ctx.scopedEnumNames`). */
|
|
116
|
+
function isEnumInScope(enumName, ctx) {
|
|
117
|
+
const scope = ctx.scopedEnumNames;
|
|
118
|
+
return !scope || scope.has(enumName);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
78
121
|
* Get the mount target for an IR service.
|
|
79
122
|
* Checks the first resolved operation that belongs to this service.
|
|
80
123
|
* Falls back to PascalCase of the service name if no resolved ops exist.
|
|
@@ -3637,6 +3680,29 @@ function isHandOwnedType(ctx, name) {
|
|
|
3637
3680
|
if (!configured || configured.length === 0) return false;
|
|
3638
3681
|
return configured.includes(name);
|
|
3639
3682
|
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Resolve the Node operation override for an operation, keyed by "METHOD /path".
|
|
3685
|
+
*/
|
|
3686
|
+
function operationOverrideFor$1(ctx, op) {
|
|
3687
|
+
return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
3688
|
+
}
|
|
3689
|
+
/**
|
|
3690
|
+
* `planOperation` plus the Node `responseModel` override. When an operation
|
|
3691
|
+
* override supplies `responseModel`, the resolved response model name is
|
|
3692
|
+
* replaced so the resource and its generated test reference the desired wire
|
|
3693
|
+
* type and deserializer. Use this everywhere the Node emitter would otherwise
|
|
3694
|
+
* call `planOperation(op)` directly so resource and test stay in lockstep.
|
|
3695
|
+
*/
|
|
3696
|
+
function planOperationFor(op, ctx) {
|
|
3697
|
+
const plan = planOperation(op);
|
|
3698
|
+
const responseModel = operationOverrideFor$1(ctx, op)?.responseModel;
|
|
3699
|
+
if (responseModel && responseModel !== plan.responseModelName) return {
|
|
3700
|
+
...plan,
|
|
3701
|
+
responseModelName: responseModel,
|
|
3702
|
+
isModelResponse: true
|
|
3703
|
+
};
|
|
3704
|
+
return plan;
|
|
3705
|
+
}
|
|
3640
3706
|
//#endregion
|
|
3641
3707
|
//#region src/node/live-surface.ts
|
|
3642
3708
|
const SRC_DIR = "src";
|
|
@@ -4632,7 +4698,7 @@ function generateEnums$7(enums, ctx) {
|
|
|
4632
4698
|
lines.push(`export type ${enumDef.name} =`);
|
|
4633
4699
|
lines.push(` (typeof ${enumDef.name})[keyof typeof ${enumDef.name}];`);
|
|
4634
4700
|
}
|
|
4635
|
-
files.push({
|
|
4701
|
+
if (isEnumInScope(enumDef.name, ctx)) files.push({
|
|
4636
4702
|
path: `src/${dirName}/interfaces/${fileName$3(enumDef.name)}.interface.ts`,
|
|
4637
4703
|
content: lines.join("\n"),
|
|
4638
4704
|
skipIfExists: !hasNewValues
|
|
@@ -4913,7 +4979,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
|
|
|
4913
4979
|
const irDomainFields = /* @__PURE__ */ new Set();
|
|
4914
4980
|
for (const field of model.fields) {
|
|
4915
4981
|
irWireFields.add(wireFieldName(field.name));
|
|
4916
|
-
irDomainFields.add(fieldName$6(field.name));
|
|
4982
|
+
irDomainFields.add(fieldName$6(field.domainName ?? field.name));
|
|
4917
4983
|
}
|
|
4918
4984
|
for (const [wireField2, fieldDef] of Object.entries(baselineResponse.fields)) {
|
|
4919
4985
|
if (fieldDef.optional) continue;
|
|
@@ -4957,7 +5023,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
|
|
|
4957
5023
|
return false;
|
|
4958
5024
|
}
|
|
4959
5025
|
function planDeserializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
|
|
4960
|
-
const domain = fieldName$6(field.name);
|
|
5026
|
+
const domain = fieldName$6(field.domainName ?? field.name);
|
|
4961
5027
|
const wireAccess = `response.${wireFieldName(field.name)}`;
|
|
4962
5028
|
const skip = skipFormatFields.has(field.name);
|
|
4963
5029
|
const baselineDomainField = baselineDomain?.fields?.[domain];
|
|
@@ -4993,7 +5059,7 @@ function planDeserializeGuard(field, expr, wireAccess, effectivelyOptional, isNe
|
|
|
4993
5059
|
}
|
|
4994
5060
|
function planSerializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
|
|
4995
5061
|
const wire = wireFieldName(field.name);
|
|
4996
|
-
const domain = fieldName$6(field.name);
|
|
5062
|
+
const domain = fieldName$6(field.domainName ?? field.name);
|
|
4997
5063
|
const domainAccess = `model.${domain}`;
|
|
4998
5064
|
const skip = skipFormatFields.has(field.name);
|
|
4999
5065
|
const baselineWireField = baselineResponse?.fields?.[wire];
|
|
@@ -5533,9 +5599,6 @@ function existingInterfaceBarrelExports(ctx, serviceDir, stem) {
|
|
|
5533
5599
|
const escapedStem = stem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5534
5600
|
return new RegExp(`export\\s+(?:type\\s+)?(?:\\*|\\{[^}]+\\})\\s+from\\s+['"]\\./${escapedStem}['"]`).test(content);
|
|
5535
5601
|
}
|
|
5536
|
-
function operationOverrideFor$1(ctx, op) {
|
|
5537
|
-
return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
5538
|
-
}
|
|
5539
5602
|
function baselineMethodFor$1(service, method, ctx) {
|
|
5540
5603
|
const serviceClass = resolveResourceClassName$3(service, ctx);
|
|
5541
5604
|
return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
|
|
@@ -5561,10 +5624,11 @@ function optionsObjectParam$1(method) {
|
|
|
5561
5624
|
const [param] = method.params;
|
|
5562
5625
|
if (param.name !== "options") return void 0;
|
|
5563
5626
|
if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
|
|
5564
|
-
|
|
5627
|
+
const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
|
|
5628
|
+
if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
|
|
5565
5629
|
return {
|
|
5566
5630
|
name: "options",
|
|
5567
|
-
type
|
|
5631
|
+
type,
|
|
5568
5632
|
optional: param.optional === true,
|
|
5569
5633
|
generated: false
|
|
5570
5634
|
};
|
|
@@ -5765,14 +5829,14 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
|
|
|
5765
5829
|
const resolvedLookup = buildResolvedLookup(ctx);
|
|
5766
5830
|
const plans = service.operations.map((op) => ({
|
|
5767
5831
|
op,
|
|
5768
|
-
plan:
|
|
5832
|
+
plan: planOperationFor(op, ctx),
|
|
5769
5833
|
method: resolveMethodName$6(op, service, ctx)
|
|
5770
5834
|
}));
|
|
5771
5835
|
for (const { op, plan, method } of plans) {
|
|
5772
5836
|
const resolvedOp = lookupResolved(op, resolvedLookup);
|
|
5773
5837
|
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
|
|
5774
5838
|
if (!optionInfo?.generated) continue;
|
|
5775
|
-
if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
|
|
5839
|
+
if (op.pathParams.length === 0 && baselineTypeSourceFile(ctx, optionInfo.type)) continue;
|
|
5776
5840
|
const optionsName = optionInfo.type;
|
|
5777
5841
|
const optionFileStem = `${fileName$3(optionsName)}.interface`;
|
|
5778
5842
|
const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
|
|
@@ -5815,7 +5879,8 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
|
|
|
5815
5879
|
}
|
|
5816
5880
|
headerParts.push(` ${name}${opt}: ${type};`);
|
|
5817
5881
|
};
|
|
5818
|
-
|
|
5882
|
+
const optionsPathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
5883
|
+
for (const param of op.pathParams) pushField(optionsPathFieldMap?.[fieldName$6(param.name)] ?? fieldName$6(param.name), true, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
|
|
5819
5884
|
for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
|
|
5820
5885
|
if (bodyInfo?.kind === "model") {
|
|
5821
5886
|
const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
|
|
@@ -5843,12 +5908,13 @@ function generateResources$7(services, ctx) {
|
|
|
5843
5908
|
if (services.length === 0) return [];
|
|
5844
5909
|
const files = [];
|
|
5845
5910
|
const mountGroups = groupByMount(ctx);
|
|
5846
|
-
const mergedServices = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
5911
|
+
const mergedServices = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
5847
5912
|
name,
|
|
5848
5913
|
operations: group.operations
|
|
5849
5914
|
})) : services;
|
|
5850
5915
|
const topLevelEnumNames = new Set(ctx.spec.enums.map((e) => e.name));
|
|
5851
5916
|
for (const service of mergedServices) {
|
|
5917
|
+
if (!isMountInScope(service.name, ctx)) continue;
|
|
5852
5918
|
const isOwnedService = isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx));
|
|
5853
5919
|
if (!isOwnedService && isServiceCoveredByExisting(service, ctx)) {
|
|
5854
5920
|
if (!hasMethodsAbsentFromBaseline(service, ctx)) continue;
|
|
@@ -5875,6 +5941,7 @@ function generateResources$7(services, ctx) {
|
|
|
5875
5941
|
}
|
|
5876
5942
|
}
|
|
5877
5943
|
for (const service of mergedServices) {
|
|
5944
|
+
if (!isMountInScope(service.name, ctx)) continue;
|
|
5878
5945
|
if (!isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx)) && isServiceCoveredByExisting(service, ctx) && !hasMethodsAbsentFromBaseline(service, ctx)) continue;
|
|
5879
5946
|
files.push(...generateOptionsInterfaces(service, ctx, topLevelEnumNames));
|
|
5880
5947
|
}
|
|
@@ -5887,7 +5954,7 @@ function generateResourceClass(service, ctx) {
|
|
|
5887
5954
|
const resourcePath = `src/${serviceDir}/${fileName$3(resolvedName)}.ts`;
|
|
5888
5955
|
let plans = service.operations.map((op) => ({
|
|
5889
5956
|
op,
|
|
5890
|
-
plan:
|
|
5957
|
+
plan: planOperationFor(op, ctx),
|
|
5891
5958
|
method: resolveMethodName$6(op, service, ctx)
|
|
5892
5959
|
}));
|
|
5893
5960
|
deduplicateMethodNames(plans, ctx);
|
|
@@ -5996,6 +6063,7 @@ function generateResourceClass(service, ctx) {
|
|
|
5996
6063
|
const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
|
|
5997
6064
|
if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
|
|
5998
6065
|
const importedTypeNames = /* @__PURE__ */ new Set();
|
|
6066
|
+
if (needsPaginationOptionsImport) importedTypeNames.add("PaginationOptions");
|
|
5999
6067
|
for (const optionType of optionObjectTypes) {
|
|
6000
6068
|
if (isValidTypeIdentifier(optionType)) {
|
|
6001
6069
|
if (importedTypeNames.has(optionType)) continue;
|
|
@@ -6554,7 +6622,14 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
|
|
|
6554
6622
|
lines.push(" }");
|
|
6555
6623
|
return true;
|
|
6556
6624
|
}
|
|
6557
|
-
|
|
6625
|
+
{
|
|
6626
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
|
|
6627
|
+
renderOptionsObjectDestructure(lines, pathBindings);
|
|
6628
|
+
const emptyBodyArg = httpMethodNeedsBody(op.httpMethod) ? ", {}" : "";
|
|
6629
|
+
lines.push(` await this.workos.${op.httpMethod}(${pathStr}${emptyBodyArg}${queryOptionsArg});`);
|
|
6630
|
+
lines.push(" }");
|
|
6631
|
+
return true;
|
|
6632
|
+
}
|
|
6558
6633
|
}
|
|
6559
6634
|
function renderOptionsObjectDestructure(lines, pathBindings, restName) {
|
|
6560
6635
|
if (pathBindings.length > 0 && restName) lines.push(` const { ${pathBindings.join(", ")}, ...${restName} } = options;`);
|
|
@@ -6574,7 +6649,8 @@ function bodyArgExprWithParam(bodyExpr, paramName) {
|
|
|
6574
6649
|
return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
|
|
6575
6650
|
}
|
|
6576
6651
|
function buildOptionsObjectPathBindings(op, optionType, ctx) {
|
|
6577
|
-
|
|
6652
|
+
const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
6653
|
+
return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx, pathFieldMap));
|
|
6578
6654
|
}
|
|
6579
6655
|
/**
|
|
6580
6656
|
* Map spec path-param names (e.g. `omId`) to the SDK field name exposed on
|
|
@@ -6585,14 +6661,17 @@ function buildOptionsObjectPathBindings(op, optionType, ctx) {
|
|
|
6585
6661
|
*/
|
|
6586
6662
|
function buildOptionsObjectPathParamMap(op, optionType, ctx) {
|
|
6587
6663
|
const map = /* @__PURE__ */ new Map();
|
|
6664
|
+
const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
6588
6665
|
for (const param of op.pathParams) {
|
|
6589
6666
|
const localName = fieldName$6(param.name);
|
|
6590
|
-
const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx);
|
|
6667
|
+
const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap);
|
|
6591
6668
|
if (sdkField !== localName) map.set(param.name, sdkField);
|
|
6592
6669
|
}
|
|
6593
6670
|
return map;
|
|
6594
6671
|
}
|
|
6595
|
-
function resolveOptionsObjectField$1(localName, optionType, ctx) {
|
|
6672
|
+
function resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap) {
|
|
6673
|
+
const mapped = pathFieldMap?.[localName];
|
|
6674
|
+
if (mapped) return mapped;
|
|
6596
6675
|
const fields = ctx.apiSurface?.interfaces?.[optionType]?.fields;
|
|
6597
6676
|
if (!fields) return localName;
|
|
6598
6677
|
if (fields[localName]) return localName;
|
|
@@ -7421,7 +7500,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7421
7500
|
"",
|
|
7422
7501
|
...aliasExports
|
|
7423
7502
|
] : [...aliasExports];
|
|
7424
|
-
files.push({
|
|
7503
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
7425
7504
|
path: aliasPath,
|
|
7426
7505
|
content: aliasLines.join("\n"),
|
|
7427
7506
|
overwriteExisting: true
|
|
@@ -7548,7 +7627,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7548
7627
|
else {
|
|
7549
7628
|
lines.push(`export interface ${domainName}${typeParams} {`);
|
|
7550
7629
|
for (const field of model.fields) {
|
|
7551
|
-
const domainFieldName = fieldName$6(field.name);
|
|
7630
|
+
const domainFieldName = fieldName$6(field.domainName ?? field.name);
|
|
7552
7631
|
if (seenDomainFields.has(domainFieldName)) continue;
|
|
7553
7632
|
seenDomainFields.add(domainFieldName);
|
|
7554
7633
|
if (field.description || field.deprecated || field.readOnly || field.writeOnly || field.default !== void 0) {
|
|
@@ -7565,7 +7644,10 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7565
7644
|
const responseBaselineField = baselineResponse?.fields?.[domainWireField];
|
|
7566
7645
|
const domainResponseOptionalMismatch = baselineField && !baselineField.optional && responseBaselineField && responseBaselineField.optional;
|
|
7567
7646
|
const readonlyPrefix = field.readOnly ? "readonly " : "";
|
|
7568
|
-
if (
|
|
7647
|
+
if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
|
|
7648
|
+
const opt = baselineField.optional ? "?" : "";
|
|
7649
|
+
lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
|
|
7650
|
+
} else if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
|
|
7569
7651
|
const opt = baselineField.optional ? "?" : "";
|
|
7570
7652
|
lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${baselineField.type};`);
|
|
7571
7653
|
} else {
|
|
@@ -7588,7 +7670,10 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7588
7670
|
if (seenWireFields.has(wireField)) continue;
|
|
7589
7671
|
seenWireFields.add(wireField);
|
|
7590
7672
|
const baselineField = baselineResponse?.fields?.[wireField];
|
|
7591
|
-
if (
|
|
7673
|
+
if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
|
|
7674
|
+
const opt = baselineField.optional ? "?" : "";
|
|
7675
|
+
lines.push(` ${wireField}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
|
|
7676
|
+
} else if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
|
|
7592
7677
|
const opt = baselineField.optional ? "?" : "";
|
|
7593
7678
|
lines.push(` ${wireField}${opt}: ${baselineField.type};`);
|
|
7594
7679
|
} else {
|
|
@@ -7673,7 +7758,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7673
7758
|
for (const [alias, typeExpr] of typeDecls) if (new RegExp(`\\b${alias}\\b`).test(bodyText)) usedDecls.push(`type ${alias} = ${typeExpr};`);
|
|
7674
7759
|
if (usedDecls.length > 0) lines.splice(typeDeclInsertIdx, 0, ...usedDecls, "");
|
|
7675
7760
|
}
|
|
7676
|
-
files.push({
|
|
7761
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
7677
7762
|
path: filePath,
|
|
7678
7763
|
content: pruneUnusedImports(lines).join("\n"),
|
|
7679
7764
|
overwriteExisting: true
|
|
@@ -7786,7 +7871,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
7786
7871
|
if (!canonSkipDeserialize) parts.push(`deserialize${canonDomainName} as deserialize${domainName}`);
|
|
7787
7872
|
if (!canonSkipSerialize) parts.push(`serialize${canonDomainName} as serialize${domainName}`);
|
|
7788
7873
|
const reexportContent = `export { ${parts.join(", ")} } from '${rel}';`;
|
|
7789
|
-
files.push({
|
|
7874
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
7790
7875
|
path: serializerPath,
|
|
7791
7876
|
content: reexportContent,
|
|
7792
7877
|
overwriteExisting: true
|
|
@@ -7814,7 +7899,7 @@ function generateSerializers(models, ctx, shared) {
|
|
|
7814
7899
|
responseReachableModels,
|
|
7815
7900
|
ctx
|
|
7816
7901
|
}), ...emitSerializerBody(model, domainName, responseName, typeParams, baselineDomain, baselineResponse, skipFormatFields, shouldSkipSerialize, shouldSkipDeserialize, ctx)];
|
|
7817
|
-
files.push({
|
|
7902
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
7818
7903
|
path: serializerPath,
|
|
7819
7904
|
content: pruneUnusedImports(lines).join("\n"),
|
|
7820
7905
|
overwriteExisting: true
|
|
@@ -8031,6 +8116,19 @@ function hasSpecificIRType(ref) {
|
|
|
8031
8116
|
default: return false;
|
|
8032
8117
|
}
|
|
8033
8118
|
}
|
|
8119
|
+
/** True when a baseline field type references one of the model's generic
|
|
8120
|
+
* param identifiers (collected as `unresolvableNames` — bare type names that
|
|
8121
|
+
* resolve to nothing importable, which for a generic model are its params). */
|
|
8122
|
+
function typeReferencesUnresolvable(type, unresolvable) {
|
|
8123
|
+
if (unresolvable.size === 0) return false;
|
|
8124
|
+
const names = type.match(/\b[A-Z][a-zA-Z0-9]*\b/g);
|
|
8125
|
+
return !!names && names.some((n) => unresolvable.has(n));
|
|
8126
|
+
}
|
|
8127
|
+
/** Rewrite a baseline field type so references to the model's (renamed)
|
|
8128
|
+
* generic param resolve to the emitted `GenericType` param. */
|
|
8129
|
+
function substituteGenericParam(type, unresolvable) {
|
|
8130
|
+
return type.replace(/\b[A-Z][a-zA-Z0-9]*\b/g, (name) => unresolvable.has(name) ? "GenericType" : name);
|
|
8131
|
+
}
|
|
8034
8132
|
function renderTypeParams(model, genericDefaults) {
|
|
8035
8133
|
if (!model.typeParams?.length) {
|
|
8036
8134
|
if (genericDefaults?.has(model.name)) return "<GenericType extends Record<string, unknown> = Record<string, unknown>>";
|
|
@@ -8557,10 +8655,11 @@ function optionsObjectParam(method) {
|
|
|
8557
8655
|
const [param] = method.params;
|
|
8558
8656
|
if (param.name !== "options") return void 0;
|
|
8559
8657
|
if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
|
|
8560
|
-
|
|
8658
|
+
const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
|
|
8659
|
+
if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
|
|
8561
8660
|
return {
|
|
8562
8661
|
name: param.name,
|
|
8563
|
-
type
|
|
8662
|
+
type
|
|
8564
8663
|
};
|
|
8565
8664
|
}
|
|
8566
8665
|
function configuredOptionsMethod(ctx, op) {
|
|
@@ -8607,7 +8706,7 @@ function generateTests$7(spec, ctx) {
|
|
|
8607
8706
|
const mountGroups = groupByMount(ctx);
|
|
8608
8707
|
const mountAccessors = /* @__PURE__ */ new Map();
|
|
8609
8708
|
for (const r of ctx.resolvedOperations ?? []) if (!mountAccessors.has(r.mountOn)) mountAccessors.set(r.mountOn, servicePropertyName$4(r.mountOn));
|
|
8610
|
-
const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
8709
|
+
const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
8611
8710
|
name,
|
|
8612
8711
|
operations: group.operations
|
|
8613
8712
|
})) : spec.services.map((s) => ({
|
|
@@ -8618,6 +8717,7 @@ function generateTests$7(spec, ctx) {
|
|
|
8618
8717
|
if (ctx.apiSurface?.classes?.["WorkOS"]?.methods) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].methods)) baselineWorkOSProps.add(name);
|
|
8619
8718
|
if (ctx.apiSurface?.classes?.["WorkOS"]?.properties) for (const name of Object.keys(ctx.apiSurface.classes["WorkOS"].properties)) baselineWorkOSProps.add(name);
|
|
8620
8719
|
for (const { name: mountName, operations } of testEntries) {
|
|
8720
|
+
if (!isMountInScope(mountName, ctx)) continue;
|
|
8621
8721
|
if (operations.length === 0) continue;
|
|
8622
8722
|
const mergedService = {
|
|
8623
8723
|
name: mountName,
|
|
@@ -8625,6 +8725,7 @@ function generateTests$7(spec, ctx) {
|
|
|
8625
8725
|
};
|
|
8626
8726
|
const ops = isNodeOwnedService(ctx, mountName, resolveResourceClassName$3(mergedService, ctx)) ? operations : uncoveredOperations(mergedService, ctx);
|
|
8627
8727
|
if (ops.length === 0) continue;
|
|
8728
|
+
const ignoredMethodNames = ignoredResourceMethodNames(ctx, `src/${resolveResourceDir(mergedService, ctx)}/${fileName$3(resolveResourceClassName$3(mergedService, ctx))}.ts`);
|
|
8628
8729
|
const propName = mountAccessors.get(mountName) ?? servicePropertyName$4(mountName);
|
|
8629
8730
|
if (ctx.apiSurface && baselineWorkOSProps.size > 0 && !baselineWorkOSProps.has(propName)) continue;
|
|
8630
8731
|
if (resolveResourceClassName$3(mergedService, ctx) !== mountName) continue;
|
|
@@ -8632,23 +8733,31 @@ function generateTests$7(spec, ctx) {
|
|
|
8632
8733
|
...mergedService,
|
|
8633
8734
|
operations: ops
|
|
8634
8735
|
} : mergedService;
|
|
8635
|
-
files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors));
|
|
8736
|
+
files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors, ignoredMethodNames));
|
|
8636
8737
|
}
|
|
8637
8738
|
const serializerTests = generateSerializerTests(spec, ctx);
|
|
8638
8739
|
for (const f of serializerTests) files.push(f);
|
|
8639
8740
|
return files;
|
|
8640
8741
|
}
|
|
8641
|
-
function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
8742
|
+
function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors, ignoredMethodNames = /* @__PURE__ */ new Set()) {
|
|
8642
8743
|
const resolvedName = resolveResourceClassName$3(service, ctx);
|
|
8643
8744
|
const serviceDir = resolveResourceDir(service, ctx);
|
|
8644
8745
|
const serviceClass = resolvedName;
|
|
8645
8746
|
const serviceProp = mountAccessors?.get(service.name) ?? servicePropertyName$4(resolveServiceName(service, ctx));
|
|
8646
8747
|
const testPath = `src/${serviceDir}/${fileName$3(resolvedName)}.spec.ts`;
|
|
8647
|
-
const
|
|
8748
|
+
const allPlans = service.operations.map((op) => ({
|
|
8648
8749
|
op,
|
|
8649
|
-
plan:
|
|
8750
|
+
plan: planOperationFor(op, ctx),
|
|
8650
8751
|
method: resolveMethodName$6(op, service, ctx)
|
|
8651
8752
|
}));
|
|
8753
|
+
const resolvedLookup = buildResolvedLookup(ctx);
|
|
8754
|
+
const preservedScopes = methodsWithPreservedTestBlocks(ctx, testPath);
|
|
8755
|
+
const plans = allPlans.filter((p) => {
|
|
8756
|
+
if (preservedScopes.has(p.method)) return true;
|
|
8757
|
+
if (ignoredMethodNames.has(p.method)) return false;
|
|
8758
|
+
if (lookupResolved(p.op, resolvedLookup)?.urlBuilder) return false;
|
|
8759
|
+
return true;
|
|
8760
|
+
});
|
|
8652
8761
|
if (ctx.overlayLookup?.methodByOperation) {
|
|
8653
8762
|
const methodOrder = /* @__PURE__ */ new Map();
|
|
8654
8763
|
let pos = 0;
|
|
@@ -8695,7 +8804,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
|
8695
8804
|
lines.push("");
|
|
8696
8805
|
lines.push("const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');");
|
|
8697
8806
|
lines.push("");
|
|
8698
|
-
const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(plans, modelMap, ctx);
|
|
8807
|
+
const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(service, allPlans, plans, existingTestIgnoreText(ctx, testPath), modelMap, ctx);
|
|
8699
8808
|
for (const line of helperLines) lines.push(line);
|
|
8700
8809
|
lines.push(`describe('${serviceClass}', () => {`);
|
|
8701
8810
|
lines.push(" beforeEach(() => fetch.resetMocks());");
|
|
@@ -8711,6 +8820,20 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
|
8711
8820
|
lines.push(" });");
|
|
8712
8821
|
}
|
|
8713
8822
|
lines.push("});");
|
|
8823
|
+
const body = lines.join("\n");
|
|
8824
|
+
const enumImportLines = [];
|
|
8825
|
+
for (const [name, info] of Object.entries(ctx.apiSurface?.enums ?? {})) {
|
|
8826
|
+
if (!new RegExp(`\\b${name}\\.[A-Za-z_$]`).test(body)) continue;
|
|
8827
|
+
if (new RegExp(`import\\b[^;]*\\b${name}\\b[^;]*from`).test(body)) continue;
|
|
8828
|
+
const sourceFile = info.sourceFile;
|
|
8829
|
+
const spec = sourceFile ? relativeImport(testPath, sourceFile).replace(/\.ts$/, "") : "./interfaces";
|
|
8830
|
+
enumImportLines.push(`import { ${name} } from '${spec}';`);
|
|
8831
|
+
}
|
|
8832
|
+
if (enumImportLines.length > 0) {
|
|
8833
|
+
const anchor = lines.indexOf("import { WorkOS } from '../workos';");
|
|
8834
|
+
const at = anchor >= 0 ? anchor + 1 : 0;
|
|
8835
|
+
lines.splice(at, 0, ...enumImportLines);
|
|
8836
|
+
}
|
|
8714
8837
|
return {
|
|
8715
8838
|
path: testPath,
|
|
8716
8839
|
content: lines.join("\n"),
|
|
@@ -8729,11 +8852,81 @@ function pathParamTestValue(param, paramName) {
|
|
|
8729
8852
|
if (name) return `test_${fieldName$6(name)}`;
|
|
8730
8853
|
return "test_id";
|
|
8731
8854
|
}
|
|
8732
|
-
|
|
8855
|
+
/** Render an example value as a valid TS literal expression.
|
|
8856
|
+
* Objects and nested arrays go through JSON.stringify so map-typed params
|
|
8857
|
+
* (e.g. providerQueryParams) don't coerce to the literal text `[object Object]`.
|
|
8858
|
+
*/
|
|
8859
|
+
function renderExampleLiteral(value) {
|
|
8860
|
+
if (typeof value === "string") return `'${value}'`;
|
|
8861
|
+
if (Array.isArray(value)) return `[${value.map(renderExampleLiteral).join(", ")}]`;
|
|
8862
|
+
if (value !== null && typeof value === "object") return JSON.stringify(value);
|
|
8863
|
+
return String(value);
|
|
8864
|
+
}
|
|
8865
|
+
/** Resolve the enum members a query-param test value must satisfy. An enum is
|
|
8866
|
+
* expressed several ways: an inline `EnumRef` with `values`, an `enum`/`model`
|
|
8867
|
+
* ref by name, or — when the IR flattened the param to a bare string but the
|
|
8868
|
+
* hand-written options interface still types the field as an enum — the
|
|
8869
|
+
* baseline options field type (e.g. `ConnectionType`). */
|
|
8870
|
+
/** All valid values for an enum named `name`. The extracted SDK surface is
|
|
8871
|
+
* consulted first: when an options field is typed with a hand-written enum
|
|
8872
|
+
* (e.g. `ConnectionType` from `connection-type.enum.ts`), that enum — not a
|
|
8873
|
+
* same-named IR enum that may carry extra members like `Pending` — is what
|
|
8874
|
+
* the generated test must type-check against. Falls back to the IR spec. */
|
|
8875
|
+
function enumValuesByName(name, ctx) {
|
|
8876
|
+
const members = ctx?.apiSurface?.enums?.[name]?.members;
|
|
8877
|
+
if (members && Object.keys(members).length > 0) return Object.values(members);
|
|
8878
|
+
const specValues = ctx?.spec.enums.find((e) => e.name === name)?.values;
|
|
8879
|
+
if (specValues?.length) return specValues.map((v) => v.value);
|
|
8880
|
+
}
|
|
8881
|
+
function resolveParamEnumValues(param, optionFieldType, ctx) {
|
|
8882
|
+
if (optionFieldType) {
|
|
8883
|
+
const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
|
|
8884
|
+
if (/^[A-Za-z_$][\w$]*$/.test(bare)) {
|
|
8885
|
+
const values = enumValuesByName(bare, ctx);
|
|
8886
|
+
if (values) return values;
|
|
8887
|
+
}
|
|
8888
|
+
}
|
|
8889
|
+
if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) {
|
|
8890
|
+
const values = enumValuesByName(param.type.name, ctx);
|
|
8891
|
+
if (values) return values;
|
|
8892
|
+
}
|
|
8893
|
+
if (param.type.kind === "enum" && param.type.values?.length) return param.type.values;
|
|
8894
|
+
}
|
|
8895
|
+
/** When an options field is typed with a real (nominal) TS `enum` from the SDK
|
|
8896
|
+
* surface, a string literal won't type-check — the value must be a member
|
|
8897
|
+
* reference (`ConnectionType.ADFSSAML`). Returns the enum's name + members so
|
|
8898
|
+
* the caller can both render the reference and import the right declaration. */
|
|
8899
|
+
function resolveRealEnum(param, optionFieldType, ctx) {
|
|
8900
|
+
const candidates = [];
|
|
8901
|
+
if (optionFieldType) {
|
|
8902
|
+
const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
|
|
8903
|
+
if (/^[A-Za-z_$][\w$]*$/.test(bare)) candidates.push(bare);
|
|
8904
|
+
}
|
|
8905
|
+
if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) candidates.push(param.type.name);
|
|
8906
|
+
for (const name of candidates) {
|
|
8907
|
+
const members = ctx?.apiSurface?.enums?.[name]?.members;
|
|
8908
|
+
if (members && Object.keys(members).length > 0) return {
|
|
8909
|
+
name,
|
|
8910
|
+
members
|
|
8911
|
+
};
|
|
8912
|
+
}
|
|
8913
|
+
return null;
|
|
8914
|
+
}
|
|
8915
|
+
function queryParamTestValue(param, modelMap, ctx, optionFieldType) {
|
|
8916
|
+
const realEnum = resolveRealEnum(param, optionFieldType, ctx);
|
|
8917
|
+
if (realEnum) {
|
|
8918
|
+
const entries = Object.entries(realEnum.members);
|
|
8919
|
+
const [memberKey] = (typeof param.example === "string" ? entries.find(([, v]) => v === param.example) : void 0) ?? entries[0];
|
|
8920
|
+
return `${realEnum.name}.${memberKey}`;
|
|
8921
|
+
}
|
|
8922
|
+
const enumValues = resolveParamEnumValues(param, optionFieldType, ctx);
|
|
8923
|
+
if (enumValues?.length) {
|
|
8924
|
+
const valid = typeof param.example === "string" && enumValues.includes(param.example) ? param.example : enumValues[0];
|
|
8925
|
+
return typeof valid === "string" ? `'${valid}'` : String(valid);
|
|
8926
|
+
}
|
|
8733
8927
|
if (param.example !== void 0) {
|
|
8734
|
-
if (Array.isArray(param.example)) return `[${param.example.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(", ")}]`;
|
|
8735
8928
|
if (param.type.kind === "primitive" && param.type.format === "date-time" && typeof param.example === "string") return `new Date('${param.example}')`;
|
|
8736
|
-
return
|
|
8929
|
+
return renderExampleLiteral(param.example);
|
|
8737
8930
|
}
|
|
8738
8931
|
return fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
|
|
8739
8932
|
}
|
|
@@ -8899,9 +9092,10 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
8899
9092
|
const optionParam = optionsObjectParam(baselineMethod);
|
|
8900
9093
|
if (!optionParam) return null;
|
|
8901
9094
|
const entries = [];
|
|
9095
|
+
const pathFieldMap = ctx ? operationOverrideFor(ctx, op)?.pathFieldMap : void 0;
|
|
8902
9096
|
for (const param of op.pathParams) {
|
|
8903
9097
|
const localName = fieldName$6(param.name);
|
|
8904
|
-
const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
|
|
9098
|
+
const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx, pathFieldMap);
|
|
8905
9099
|
entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
|
|
8906
9100
|
}
|
|
8907
9101
|
if (plan.isPaginated) entries.push("order: 'desc'");
|
|
@@ -8913,7 +9107,8 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
8913
9107
|
].includes(param.name)) : op.queryParams).filter((param) => !param.deprecated);
|
|
8914
9108
|
for (const param of queryParams) {
|
|
8915
9109
|
const localName = fieldName$6(param.name);
|
|
8916
|
-
const
|
|
9110
|
+
const optionFieldType = ctx?.apiSurface?.interfaces?.[optionParam.type]?.fields?.[localName]?.type;
|
|
9111
|
+
const value = queryParamTestValue(param, modelMap, ctx, optionFieldType);
|
|
8917
9112
|
entries.push(`${localName}: ${value}`);
|
|
8918
9113
|
}
|
|
8919
9114
|
if (plan.hasBody) {
|
|
@@ -8941,7 +9136,9 @@ function objectLiteralEntries(literal) {
|
|
|
8941
9136
|
const body = trimmed.slice(1, -1).trim();
|
|
8942
9137
|
return body ? body.split(",").map((entry) => entry.trim()) : [];
|
|
8943
9138
|
}
|
|
8944
|
-
function resolveOptionsObjectField(localName, optionType, ctx) {
|
|
9139
|
+
function resolveOptionsObjectField(localName, optionType, ctx, pathFieldMap) {
|
|
9140
|
+
const mapped = pathFieldMap?.[localName];
|
|
9141
|
+
if (mapped) return mapped;
|
|
8945
9142
|
const fields = ctx?.apiSurface?.interfaces?.[optionType]?.fields;
|
|
8946
9143
|
if (!fields) return localName;
|
|
8947
9144
|
if (fields[localName]) return localName;
|
|
@@ -8956,20 +9153,85 @@ function resolveOptionsObjectField(localName, optionType, ctx) {
|
|
|
8956
9153
|
* Generate per-entity assertion helper functions for models used in 2+ tests.
|
|
8957
9154
|
* Returns { lines, helpers } where helpers is a Set of helper function names.
|
|
8958
9155
|
*/
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
9156
|
+
/** Describe-scope names (i.e. method names) that have an `@oagen-ignore` block
|
|
9157
|
+
* nested inside them in the existing test file. Such a `describe` must keep
|
|
9158
|
+
* being emitted even when the method is hand-owned — otherwise the engine has
|
|
9159
|
+
* no `describe` to re-nest the preserved block under and orphans it to the top
|
|
9160
|
+
* of the file (out of `beforeEach` scope), which hangs at runtime. Mirrors the
|
|
9161
|
+
* engine's `findContainingDescribeScope`. */
|
|
9162
|
+
function methodsWithPreservedTestBlocks(ctx, relPath) {
|
|
9163
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
9164
|
+
const root = ctx.outputDir ?? ctx.targetDir;
|
|
9165
|
+
if (!root) return scopes;
|
|
9166
|
+
let content;
|
|
9167
|
+
try {
|
|
9168
|
+
content = fs.readFileSync(path.join(root, relPath), "utf8");
|
|
9169
|
+
} catch {
|
|
9170
|
+
return scopes;
|
|
9171
|
+
}
|
|
9172
|
+
const lines = content.split("\n");
|
|
9173
|
+
const indentOf = (l) => l.length - l.trimStart().length;
|
|
9174
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9175
|
+
if (!lines[i].includes("@oagen-ignore-start")) continue;
|
|
9176
|
+
if (/^\S/.test(lines[i])) continue;
|
|
9177
|
+
const markerIndent = indentOf(lines[i]);
|
|
9178
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
9179
|
+
if (indentOf(lines[j]) >= markerIndent) continue;
|
|
9180
|
+
const m = lines[j].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
|
|
9181
|
+
if (m) {
|
|
9182
|
+
scopes.add(m[2]);
|
|
9183
|
+
break;
|
|
9184
|
+
}
|
|
9185
|
+
if (/^\s*(?:export\s+)?class\s+\w+/.test(lines[j])) break;
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
return scopes;
|
|
9189
|
+
}
|
|
9190
|
+
/** Concatenated text of the existing test file's `@oagen-ignore` regions, so
|
|
9191
|
+
* helper generation can see which `expect<Model>()` helpers preserved
|
|
9192
|
+
* hand-written test blocks still reference. */
|
|
9193
|
+
function existingTestIgnoreText(ctx, relPath) {
|
|
9194
|
+
const root = ctx.outputDir ?? ctx.targetDir;
|
|
9195
|
+
if (!root) return "";
|
|
9196
|
+
let content;
|
|
9197
|
+
try {
|
|
9198
|
+
content = fs.readFileSync(path.join(root, relPath), "utf8");
|
|
9199
|
+
} catch {
|
|
9200
|
+
return "";
|
|
9201
|
+
}
|
|
9202
|
+
return [...content.matchAll(/@oagen-ignore-start[\s\S]*?@oagen-ignore-end/g)].map((m) => m[0]).join("\n");
|
|
9203
|
+
}
|
|
9204
|
+
function generateEntityHelpers(service, allPlans, renderedPlans, ignoreRegionText, modelMap, ctx) {
|
|
9205
|
+
const responseModelOf = (entry) => {
|
|
9206
|
+
const { op, plan } = entry;
|
|
8963
9207
|
if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
|
|
8964
|
-
modelName = op.pagination.itemType.name;
|
|
9208
|
+
let modelName = op.pagination.itemType.name;
|
|
8965
9209
|
const rawModel = modelMap.get(modelName);
|
|
8966
9210
|
if (rawModel) {
|
|
8967
9211
|
const unwrapped = unwrapListModel$4(rawModel, modelMap);
|
|
8968
9212
|
if (unwrapped) modelName = unwrapped.name;
|
|
8969
9213
|
}
|
|
8970
|
-
|
|
9214
|
+
return modelName;
|
|
9215
|
+
}
|
|
9216
|
+
return plan.responseModelName ?? null;
|
|
9217
|
+
};
|
|
9218
|
+
const modelUsage = /* @__PURE__ */ new Map();
|
|
9219
|
+
for (const entry of allPlans) {
|
|
9220
|
+
const modelName = responseModelOf(entry);
|
|
8971
9221
|
if (modelName) modelUsage.set(modelName, (modelUsage.get(modelName) ?? 0) + 1);
|
|
8972
9222
|
}
|
|
9223
|
+
const renderedModels = /* @__PURE__ */ new Set();
|
|
9224
|
+
for (const entry of renderedPlans) {
|
|
9225
|
+
const modelName = responseModelOf(entry);
|
|
9226
|
+
if (!modelName) continue;
|
|
9227
|
+
if (entry.plan.isPaginated && entry.op.pagination?.itemType.kind === "model") {
|
|
9228
|
+
const baselineMethod = optionsMethodFor(service, entry.method, entry.op, entry.plan, ctx);
|
|
9229
|
+
const baselineItemType = autoPaginatableItemType(baselineMethod?.returnType);
|
|
9230
|
+
const generatedItemType = resolveInterfaceName(modelName, ctx);
|
|
9231
|
+
if (Boolean(baselineMethod?.returnType && !baselineItemType) || Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType)) continue;
|
|
9232
|
+
}
|
|
9233
|
+
renderedModels.add(modelName);
|
|
9234
|
+
}
|
|
8973
9235
|
const lines = [];
|
|
8974
9236
|
const helpers = /* @__PURE__ */ new Set();
|
|
8975
9237
|
for (const [modelName, count] of modelUsage) {
|
|
@@ -8980,6 +9242,7 @@ function generateEntityHelpers(plans, modelMap, ctx) {
|
|
|
8980
9242
|
if (assertions.length === 0) continue;
|
|
8981
9243
|
const helperName = `expect${resolveInterfaceName(modelName, ctx)}`;
|
|
8982
9244
|
if (helpers.has(helperName)) continue;
|
|
9245
|
+
if (!(renderedModels.has(modelName) || ignoreRegionText.includes(`${helperName}(`))) continue;
|
|
8983
9246
|
helpers.add(helperName);
|
|
8984
9247
|
lines.push(`function ${helperName}(result: any) {`);
|
|
8985
9248
|
for (const assertion of assertions) lines.push(` ${assertion}`);
|
|
@@ -9009,7 +9272,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
|
|
|
9009
9272
|
const assertions = [];
|
|
9010
9273
|
for (const field of model.fields) {
|
|
9011
9274
|
if (!field.required) continue;
|
|
9012
|
-
const domainField = fieldName$6(field.name);
|
|
9275
|
+
const domainField = fieldName$6(field.domainName ?? field.name);
|
|
9013
9276
|
const isDateTime = isDateTimeFieldType(field.type);
|
|
9014
9277
|
const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
|
|
9015
9278
|
if (field.example !== void 0) {
|
|
@@ -10075,6 +10338,21 @@ function withNodeOperationOverrides(ctx) {
|
|
|
10075
10338
|
return next;
|
|
10076
10339
|
}
|
|
10077
10340
|
//#endregion
|
|
10341
|
+
//#region src/shared/file-header.ts
|
|
10342
|
+
/**
|
|
10343
|
+
* Canonical "do not edit" banner text shared by every emitter's
|
|
10344
|
+
* `fileHeader()`. The text is comment-syntax agnostic — each emitter prefixes
|
|
10345
|
+
* it with the appropriate comment marker for its language (`//`, `#`, etc.).
|
|
10346
|
+
*
|
|
10347
|
+
* Keeping this in one place prevents the wording from drifting between
|
|
10348
|
+
* languages (Rust previously emitted a different banner).
|
|
10349
|
+
*
|
|
10350
|
+
* Go intentionally does NOT use this constant: its header must match the
|
|
10351
|
+
* standard `^// Code generated .* DO NOT EDIT\.$` marker that Go tooling
|
|
10352
|
+
* relies on to recognize generated files. See `src/go/index.ts`.
|
|
10353
|
+
*/
|
|
10354
|
+
const AUTOGEN_NOTICE = "This file is auto-generated by oagen. Do not edit.";
|
|
10355
|
+
//#endregion
|
|
10078
10356
|
//#region src/node/index.ts
|
|
10079
10357
|
/**
|
|
10080
10358
|
* Cache live-surface per ctx — every emitter method receives the same ctx in
|
|
@@ -10620,7 +10898,7 @@ const nodeEmitter = {
|
|
|
10620
10898
|
return {};
|
|
10621
10899
|
},
|
|
10622
10900
|
fileHeader() {
|
|
10623
|
-
return
|
|
10901
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
10624
10902
|
},
|
|
10625
10903
|
formatCommand(targetDir) {
|
|
10626
10904
|
const hasPrettier = fs$1.existsSync(path$1.join(targetDir, ".prettierrc"));
|
|
@@ -10688,6 +10966,15 @@ function fieldName$5(name) {
|
|
|
10688
10966
|
return toSnakeCase(name);
|
|
10689
10967
|
}
|
|
10690
10968
|
/**
|
|
10969
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
10970
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
10971
|
+
* a friendlier name. The wire name (still derived from `field.name`) is
|
|
10972
|
+
* unaffected, so the API contract is preserved.
|
|
10973
|
+
*/
|
|
10974
|
+
function domainFieldName$5(field) {
|
|
10975
|
+
return toSnakeCase(field.domainName ?? field.name);
|
|
10976
|
+
}
|
|
10977
|
+
/**
|
|
10691
10978
|
* Python builtins that should not be shadowed by parameter names.
|
|
10692
10979
|
* When a path/query param name collides, suffix with underscore.
|
|
10693
10980
|
*/
|
|
@@ -11230,6 +11517,7 @@ function generateEnums$6(enums, ctx) {
|
|
|
11230
11517
|
const aliasOf = placement.enumAliases;
|
|
11231
11518
|
for (const enumDef of enums) {
|
|
11232
11519
|
const dirName = resolveDir(enumToService.get(enumDef.name));
|
|
11520
|
+
const enumInScope = isEnumInScope(enumDef.name, ctx);
|
|
11233
11521
|
const canonicalName = aliasOf.get(enumDef.name);
|
|
11234
11522
|
if (canonicalName) {
|
|
11235
11523
|
if (fileName$2(enumDef.name) === fileName$2(canonicalName)) continue;
|
|
@@ -11256,7 +11544,7 @@ function generateEnums$6(enums, ctx) {
|
|
|
11256
11544
|
lines.push(" raise AttributeError(f\"module {__name__!r} has no attribute {name!r}\")");
|
|
11257
11545
|
}
|
|
11258
11546
|
lines.push(`__all__ = ["${aliasCls}"]`);
|
|
11259
|
-
files.push({
|
|
11547
|
+
if (enumInScope) files.push({
|
|
11260
11548
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
|
|
11261
11549
|
content: lines.join("\n"),
|
|
11262
11550
|
integrateTarget: true,
|
|
@@ -11287,7 +11575,7 @@ function generateEnums$6(enums, ctx) {
|
|
|
11287
11575
|
`__all__ = ["${aliasName}"]`
|
|
11288
11576
|
].join("\n");
|
|
11289
11577
|
}
|
|
11290
|
-
files.push({
|
|
11578
|
+
if (enumInScope) files.push({
|
|
11291
11579
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
|
|
11292
11580
|
content: compatContent,
|
|
11293
11581
|
integrateTarget: true,
|
|
@@ -11386,24 +11674,26 @@ function generateEnums$6(enums, ctx) {
|
|
|
11386
11674
|
lines.push("");
|
|
11387
11675
|
lines.push(`${cls}Literal: TypeAlias = Literal[${uniqueValues.map((v) => typeof v.value === "string" ? `"${v.value}"` : typeof v.value === "boolean" ? v.value ? "True" : "False" : String(v.value)).join(", ")}]`);
|
|
11388
11676
|
}
|
|
11389
|
-
|
|
11390
|
-
|
|
11391
|
-
|
|
11392
|
-
|
|
11393
|
-
|
|
11394
|
-
|
|
11395
|
-
|
|
11396
|
-
|
|
11397
|
-
|
|
11398
|
-
|
|
11399
|
-
|
|
11400
|
-
|
|
11401
|
-
|
|
11402
|
-
|
|
11403
|
-
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11677
|
+
if (enumInScope) {
|
|
11678
|
+
files.push({
|
|
11679
|
+
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(enumDef.name)}.py`,
|
|
11680
|
+
content: lines.join("\n"),
|
|
11681
|
+
integrateTarget: true,
|
|
11682
|
+
overwriteExisting: true
|
|
11683
|
+
});
|
|
11684
|
+
for (const aliasName of compatAliases.get(enumDef.name) ?? []) files.push({
|
|
11685
|
+
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(aliasName)}.py`,
|
|
11686
|
+
content: [
|
|
11687
|
+
"from typing import TypeAlias",
|
|
11688
|
+
`from .${fileName$2(enumDef.name)} import ${cls}`,
|
|
11689
|
+
"",
|
|
11690
|
+
`${aliasName}: TypeAlias = ${cls}`,
|
|
11691
|
+
`__all__ = ["${aliasName}"]`
|
|
11692
|
+
].join("\n"),
|
|
11693
|
+
integrateTarget: true,
|
|
11694
|
+
overwriteExisting: true
|
|
11695
|
+
});
|
|
11696
|
+
}
|
|
11407
11697
|
}
|
|
11408
11698
|
return files;
|
|
11409
11699
|
}
|
|
@@ -11540,7 +11830,7 @@ function generateModels$6(models, ctx) {
|
|
|
11540
11830
|
dispLines.push(" if dispatch_cls is not None:");
|
|
11541
11831
|
dispLines.push(` return cast("${variantTypeName}", dispatch_cls.from_dict(data))`);
|
|
11542
11832
|
dispLines.push(` return ${unknownClassName}.from_dict(data)`);
|
|
11543
|
-
files.push({
|
|
11833
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
11544
11834
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
|
|
11545
11835
|
content: dispLines.join("\n"),
|
|
11546
11836
|
integrateTarget: true,
|
|
@@ -11571,7 +11861,7 @@ function generateModels$6(models, ctx) {
|
|
|
11571
11861
|
else lines.push(`from ${ctx.namespace}.${dirToModule(canonicalDir)}.models.${fileName$2(canonicalName)} import ${canonicalClassName}`);
|
|
11572
11862
|
lines.push("");
|
|
11573
11863
|
lines.push(`${modelClassName}: TypeAlias = ${canonicalClassName}`);
|
|
11574
|
-
files.push({
|
|
11864
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
11575
11865
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
|
|
11576
11866
|
content: lines.join("\n"),
|
|
11577
11867
|
integrateTarget: true,
|
|
@@ -11585,7 +11875,7 @@ function generateModels$6(models, ctx) {
|
|
|
11585
11875
|
}
|
|
11586
11876
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
11587
11877
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
11588
|
-
const pyName =
|
|
11878
|
+
const pyName = domainFieldName$5(f);
|
|
11589
11879
|
if (seenFieldNames.has(pyName)) return false;
|
|
11590
11880
|
seenFieldNames.add(pyName);
|
|
11591
11881
|
return true;
|
|
@@ -11647,7 +11937,7 @@ function generateModels$6(models, ctx) {
|
|
|
11647
11937
|
return typeStr;
|
|
11648
11938
|
};
|
|
11649
11939
|
for (const field of requiredFields) {
|
|
11650
|
-
const pyFieldName =
|
|
11940
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11651
11941
|
const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
|
|
11652
11942
|
if (field.description || field.deprecated) {
|
|
11653
11943
|
const parts = [];
|
|
@@ -11658,7 +11948,7 @@ function generateModels$6(models, ctx) {
|
|
|
11658
11948
|
} else lines.push(` ${pyFieldName}: ${pyType}`);
|
|
11659
11949
|
}
|
|
11660
11950
|
for (const field of optionalFields) {
|
|
11661
|
-
const pyFieldName =
|
|
11951
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11662
11952
|
const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
|
|
11663
11953
|
if (field.description || field.deprecated) {
|
|
11664
11954
|
const parts = [];
|
|
@@ -11676,7 +11966,7 @@ function generateModels$6(models, ctx) {
|
|
|
11676
11966
|
const preludeLines = [];
|
|
11677
11967
|
const fieldAssignmentLines = [];
|
|
11678
11968
|
for (const field of [...requiredFields, ...optionalFields]) {
|
|
11679
|
-
const pyFieldName =
|
|
11969
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11680
11970
|
const wireKey = field.name;
|
|
11681
11971
|
const isRequired = !isOptionalField(model.name, field, ctx);
|
|
11682
11972
|
const discPrelude = renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, modelClassName, isRequired);
|
|
@@ -11704,7 +11994,7 @@ function generateModels$6(models, ctx) {
|
|
|
11704
11994
|
lines.push(" \"\"\"Serialize to a dictionary.\"\"\"");
|
|
11705
11995
|
lines.push(" result: Dict[str, Any] = {}");
|
|
11706
11996
|
for (const field of [...requiredFields, ...optionalFields]) {
|
|
11707
|
-
const pyFieldName =
|
|
11997
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11708
11998
|
const wireKey = field.name;
|
|
11709
11999
|
const isRequired = !isOptionalField(model.name, field, ctx);
|
|
11710
12000
|
const isNullable = field.type.kind === "nullable";
|
|
@@ -11725,7 +12015,7 @@ function generateModels$6(models, ctx) {
|
|
|
11725
12015
|
}
|
|
11726
12016
|
}
|
|
11727
12017
|
lines.push(" return result");
|
|
11728
|
-
files.push({
|
|
12018
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
11729
12019
|
path: `src/${ctx.namespace}/${dirName}/models/${fileName$2(model.name)}.py`,
|
|
11730
12020
|
content: lines.join("\n"),
|
|
11731
12021
|
integrateTarget: true,
|
|
@@ -12893,8 +13183,8 @@ function generateResources$6(services, ctx) {
|
|
|
12893
13183
|
const resolvedLookup = buildResolvedLookup(ctx);
|
|
12894
13184
|
const files = [];
|
|
12895
13185
|
const mountDirMap = buildMountDirMap$1(ctx);
|
|
12896
|
-
const mountGroups =
|
|
12897
|
-
const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
13186
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
13187
|
+
const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
12898
13188
|
name,
|
|
12899
13189
|
operations: group.operations
|
|
12900
13190
|
})) : services.map((s) => ({
|
|
@@ -13580,7 +13870,7 @@ function generateModelFixture$4(model, modelMap, enumMap) {
|
|
|
13580
13870
|
const fixture = {};
|
|
13581
13871
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
13582
13872
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
13583
|
-
const pyName =
|
|
13873
|
+
const pyName = domainFieldName$5(f);
|
|
13584
13874
|
if (seenFieldNames.has(pyName)) return false;
|
|
13585
13875
|
seenFieldNames.add(pyName);
|
|
13586
13876
|
return true;
|
|
@@ -13703,8 +13993,8 @@ function generateTests$6(spec, ctx) {
|
|
|
13703
13993
|
overwriteExisting: true
|
|
13704
13994
|
});
|
|
13705
13995
|
const accessPaths = buildServiceAccessPaths$3(spec.services, ctx);
|
|
13706
|
-
const mountGroups =
|
|
13707
|
-
const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
13996
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
13997
|
+
const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
13708
13998
|
name,
|
|
13709
13999
|
operations: group.operations,
|
|
13710
14000
|
resolvedOps: group.resolvedOps
|
|
@@ -14376,16 +14666,16 @@ function pickAssertableFields(modelName, spec, maxFields = 2) {
|
|
|
14376
14666
|
if (typeof val === "string") {
|
|
14377
14667
|
if (val.includes("\"") || val.includes("'") || val.includes("{") || val.includes("\\") || val.includes("\n")) continue;
|
|
14378
14668
|
results.push({
|
|
14379
|
-
field:
|
|
14669
|
+
field: domainFieldName$5(f),
|
|
14380
14670
|
value: `"${val}"`
|
|
14381
14671
|
});
|
|
14382
14672
|
} else if (typeof val === "boolean") results.push({
|
|
14383
|
-
field:
|
|
14673
|
+
field: domainFieldName$5(f),
|
|
14384
14674
|
value: val ? "True" : "False",
|
|
14385
14675
|
isBool: true
|
|
14386
14676
|
});
|
|
14387
14677
|
else if (typeof val === "number") results.push({
|
|
14388
|
-
field:
|
|
14678
|
+
field: domainFieldName$5(f),
|
|
14389
14679
|
value: String(val)
|
|
14390
14680
|
});
|
|
14391
14681
|
}
|
|
@@ -14445,7 +14735,7 @@ function buildTestArgs$1(op, spec, hiddenParams) {
|
|
|
14445
14735
|
].includes(param.name)) continue;
|
|
14446
14736
|
if (plan.hasBody && op.requestBody?.kind === "model") {
|
|
14447
14737
|
const rbName = op.requestBody.name;
|
|
14448
|
-
if (spec.models.find((m) => m.name === rbName)?.fields.some((f) =>
|
|
14738
|
+
if (spec.models.find((m) => m.name === rbName)?.fields.some((f) => domainFieldName$5(f) === fieldName$5(param.name))) continue;
|
|
14449
14739
|
}
|
|
14450
14740
|
if (param.required && !pathParamNames.has(fieldName$5(param.name))) args.push(`${fieldName$5(param.name)}=${generateTestValue$1(param.type, param.name)}`);
|
|
14451
14741
|
}
|
|
@@ -14704,7 +14994,7 @@ function generateModelRoundTripTests(spec, ctx) {
|
|
|
14704
14994
|
if (model.discriminator) continue;
|
|
14705
14995
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
14706
14996
|
const dedupFields = model.fields.filter((f) => {
|
|
14707
|
-
const pyName =
|
|
14997
|
+
const pyName = domainFieldName$5(f);
|
|
14708
14998
|
if (seenFieldNames.has(pyName)) return false;
|
|
14709
14999
|
seenFieldNames.add(pyName);
|
|
14710
15000
|
return true;
|
|
@@ -14896,7 +15186,7 @@ const pythonEmitter = {
|
|
|
14896
15186
|
return buildOperationsMap$6(spec, ctx);
|
|
14897
15187
|
},
|
|
14898
15188
|
fileHeader() {
|
|
14899
|
-
return
|
|
15189
|
+
return `# ${AUTOGEN_NOTICE}`;
|
|
14900
15190
|
},
|
|
14901
15191
|
formatCommand(_targetDir) {
|
|
14902
15192
|
return {
|
|
@@ -14997,6 +15287,15 @@ function resolveMethodName$4(op, _service, ctx) {
|
|
|
14997
15287
|
function fieldName$4(name) {
|
|
14998
15288
|
return toCamelCase(name);
|
|
14999
15289
|
}
|
|
15290
|
+
/**
|
|
15291
|
+
* camelCase DOMAIN property name for a model field, honoring a `domainName`
|
|
15292
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
15293
|
+
* a friendlier PHP property name. The wire key (see {@link wireName}) still
|
|
15294
|
+
* derives from `field.name`. No-op when `domainName` is unset.
|
|
15295
|
+
*/
|
|
15296
|
+
function domainFieldName$4(field) {
|
|
15297
|
+
return fieldName$4(field.domainName ?? field.name);
|
|
15298
|
+
}
|
|
15000
15299
|
/** snake_case name for fixtures and other snake_case contexts. */
|
|
15001
15300
|
function snakeName(name) {
|
|
15002
15301
|
return toSnakeCase(stripUrnPrefix(name));
|
|
@@ -15131,14 +15430,14 @@ function generateModels$5(models, ctx) {
|
|
|
15131
15430
|
const optionalFields = model.fields.filter((f) => !f.required);
|
|
15132
15431
|
const seenNames = /* @__PURE__ */ new Set();
|
|
15133
15432
|
const allFields = [...requiredFields, ...optionalFields].filter((f) => {
|
|
15134
|
-
const phpName =
|
|
15433
|
+
const phpName = domainFieldName$4(f);
|
|
15135
15434
|
if (seenNames.has(phpName)) return false;
|
|
15136
15435
|
seenNames.add(phpName);
|
|
15137
15436
|
return true;
|
|
15138
15437
|
});
|
|
15139
15438
|
for (let i = 0; i < allFields.length; i++) {
|
|
15140
15439
|
const field = allFields[i];
|
|
15141
|
-
const phpName =
|
|
15440
|
+
const phpName = domainFieldName$4(field);
|
|
15142
15441
|
const phpType = mapTypeRef$5(field.type);
|
|
15143
15442
|
const isOptional = !field.required;
|
|
15144
15443
|
const comma = i < allFields.length - 1 ? "," : ",";
|
|
@@ -15165,7 +15464,7 @@ function generateModels$5(models, ctx) {
|
|
|
15165
15464
|
lines.push(` return new self(`);
|
|
15166
15465
|
for (let i = 0; i < allFields.length; i++) {
|
|
15167
15466
|
const field = allFields[i];
|
|
15168
|
-
const phpName =
|
|
15467
|
+
const phpName = domainFieldName$4(field);
|
|
15169
15468
|
const wireName = field.name;
|
|
15170
15469
|
const comma = i < allFields.length - 1 ? "," : ",";
|
|
15171
15470
|
const accessor = generateFromArrayAccessor(field.type, wireName, field.required);
|
|
@@ -15178,7 +15477,7 @@ function generateModels$5(models, ctx) {
|
|
|
15178
15477
|
lines.push(" {");
|
|
15179
15478
|
lines.push(" return [");
|
|
15180
15479
|
for (const field of allFields) {
|
|
15181
|
-
const phpName =
|
|
15480
|
+
const phpName = domainFieldName$4(field);
|
|
15182
15481
|
const wireName = field.name;
|
|
15183
15482
|
const serialized = generateToArrayValue(field.type, `$this->${phpName}`, !field.required);
|
|
15184
15483
|
lines.push(` '${wireName}' => ${serialized},`);
|
|
@@ -15186,7 +15485,7 @@ function generateModels$5(models, ctx) {
|
|
|
15186
15485
|
lines.push(" ];");
|
|
15187
15486
|
lines.push(" }");
|
|
15188
15487
|
lines.push("}");
|
|
15189
|
-
files.push({
|
|
15488
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
15190
15489
|
path: `lib/Resource/${name}.php`,
|
|
15191
15490
|
content: lines.join("\n"),
|
|
15192
15491
|
overwriteExisting: true
|
|
@@ -15344,6 +15643,7 @@ function generateEnums$5(enums, ctx) {
|
|
|
15344
15643
|
const canonical = resolveEnumName$1(e.name);
|
|
15345
15644
|
if (emittedCanonical.has(canonical)) continue;
|
|
15346
15645
|
emittedCanonical.add(canonical);
|
|
15646
|
+
const enumInScope = enums.some((other) => resolveEnumName$1(other.name) === canonical && isEnumInScope(other.name, ctx));
|
|
15347
15647
|
const name = className$4(canonical);
|
|
15348
15648
|
e.values.every((v) => typeof v.value === "string");
|
|
15349
15649
|
const backingType = e.values.every((v) => typeof v.value === "number" && Number.isInteger(v.value)) ? "int" : "string";
|
|
@@ -15369,7 +15669,7 @@ function generateEnums$5(enums, ctx) {
|
|
|
15369
15669
|
else lines.push(` case ${caseName} = ${val.value};`);
|
|
15370
15670
|
}
|
|
15371
15671
|
lines.push("}");
|
|
15372
|
-
files.push({
|
|
15672
|
+
if (enumInScope) files.push({
|
|
15373
15673
|
path: `lib/Resource/${name}.php`,
|
|
15374
15674
|
content: lines.join("\n"),
|
|
15375
15675
|
overwriteExisting: true
|
|
@@ -15510,8 +15810,8 @@ function generateResources$5(services, ctx) {
|
|
|
15510
15810
|
if (services.length === 0) return [];
|
|
15511
15811
|
const files = [];
|
|
15512
15812
|
const modelMap = new Map(ctx.spec.models.map((m) => [m.name, m]));
|
|
15513
|
-
const mountGroups =
|
|
15514
|
-
const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
15813
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
15814
|
+
const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
15515
15815
|
name,
|
|
15516
15816
|
operations: group.operations
|
|
15517
15817
|
})) : services.map((s) => ({
|
|
@@ -16281,9 +16581,9 @@ function generatePrimitiveValue$2(type, format, name, modelName) {
|
|
|
16281
16581
|
*/
|
|
16282
16582
|
function generateTests$5(spec, ctx) {
|
|
16283
16583
|
const files = [];
|
|
16284
|
-
const mountGroupsFromResolved =
|
|
16584
|
+
const mountGroupsFromResolved = scopedMountGroups(ctx);
|
|
16285
16585
|
const mountGroups = /* @__PURE__ */ new Map();
|
|
16286
|
-
if (mountGroupsFromResolved.size > 0) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
|
|
16586
|
+
if (mountGroupsFromResolved.size > 0 || ctx.scopedServices?.size) for (const [target, group] of mountGroupsFromResolved) mountGroups.set(target, group.resolvedOps.map((r) => ({
|
|
16287
16587
|
op: r.operation,
|
|
16288
16588
|
service: r.service,
|
|
16289
16589
|
resolvedOp: r
|
|
@@ -16602,7 +16902,7 @@ function emitFieldHydrationAssertions(lines, modelName, resultVar, fixtureVar, c
|
|
|
16602
16902
|
const assertFields = [candidates.find((f) => f.name === "id"), candidates.filter((f) => f.name !== "id")[0]].filter(Boolean);
|
|
16603
16903
|
for (const f of assertFields) {
|
|
16604
16904
|
if (!f) continue;
|
|
16605
|
-
const phpProp =
|
|
16905
|
+
const phpProp = domainFieldName$4(f);
|
|
16606
16906
|
lines.push(` $this->assertSame(${fixtureVar}['${f.name}'], ${resultVar}->${phpProp});`);
|
|
16607
16907
|
}
|
|
16608
16908
|
}
|
|
@@ -16733,9 +17033,17 @@ function ensureTrailingNewlines$5(files) {
|
|
|
16733
17033
|
* classes (no sum types), so a discriminated base whose IR fields the
|
|
16734
17034
|
* parser stripped (post-allOf-aware detection) gets its original fields
|
|
16735
17035
|
* restored to avoid silently dropping variant data.
|
|
16736
|
-
|
|
16737
|
-
|
|
16738
|
-
|
|
17036
|
+
*
|
|
17037
|
+
* `enums` is forwarded to seed `enrichModelsFromSpec`'s collision set: an
|
|
17038
|
+
* inline oneOf enum whose synthetic name (`Parent_field`) snake-collapses
|
|
17039
|
+
* onto an existing IR enum (e.g. `DataIntegrationAccessTokenResponse_error`
|
|
17040
|
+
* vs `DataIntegrationAccessTokenResponseError`) must NOT spawn a duplicate
|
|
17041
|
+
* synthetic. Otherwise both collapse to the same `lib/Resource/X.php` path
|
|
17042
|
+
* and the later writer wins by array order — which differs between a full
|
|
17043
|
+
* and a scoped (`--services`) run, producing a non-deterministic case order.
|
|
17044
|
+
*/
|
|
17045
|
+
function enrichModelsForPhp(models, enums) {
|
|
17046
|
+
const enriched = enrichModelsFromSpec(models, enums);
|
|
16739
17047
|
const originalByName = new Map(models.map((m) => [m.name, m]));
|
|
16740
17048
|
return enriched.map((m) => {
|
|
16741
17049
|
if (m.discriminator && m.fields.length === 0) {
|
|
@@ -16752,7 +17060,7 @@ const phpEmitter = {
|
|
|
16752
17060
|
language: "php",
|
|
16753
17061
|
generateModels(models, ctx) {
|
|
16754
17062
|
ensureNamingInitialized(ctx);
|
|
16755
|
-
return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models), ctx));
|
|
17063
|
+
return ensureTrailingNewlines$5(generateModels$5(enrichModelsForPhp(models, ctx.spec.enums), ctx));
|
|
16756
17064
|
},
|
|
16757
17065
|
generateEnums(enums, ctx) {
|
|
16758
17066
|
ensureNamingInitialized(ctx);
|
|
@@ -16782,7 +17090,7 @@ const phpEmitter = {
|
|
|
16782
17090
|
return buildOperationsMap$5(spec, ctx);
|
|
16783
17091
|
},
|
|
16784
17092
|
fileHeader() {
|
|
16785
|
-
return
|
|
17093
|
+
return `<?php\n\ndeclare(strict_types=1);\n\n// ${AUTOGEN_NOTICE}`;
|
|
16786
17094
|
},
|
|
16787
17095
|
formatCommand(targetDir) {
|
|
16788
17096
|
if (fs$1.existsSync(path$1.join(targetDir, ".php-cs-fixer.dist.php")) || fs$1.existsSync(path$1.join(targetDir, ".php-cs-fixer.php"))) return {
|
|
@@ -16845,6 +17153,15 @@ function methodName$3(name) {
|
|
|
16845
17153
|
function fieldName$3(name) {
|
|
16846
17154
|
return applyAcronyms(toPascalCase(name));
|
|
16847
17155
|
}
|
|
17156
|
+
/**
|
|
17157
|
+
* PascalCase domain field name for a model field, honoring a `domainName`
|
|
17158
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
17159
|
+
* a friendlier name. The wire name (the `json:"..."` struct tag) still derives
|
|
17160
|
+
* from `field.name`.
|
|
17161
|
+
*/
|
|
17162
|
+
function domainFieldName$3(field) {
|
|
17163
|
+
return applyAcronyms(toPascalCase(field.domainName ?? field.name));
|
|
17164
|
+
}
|
|
16848
17165
|
/** Lower-camel identifier with Go acronym conventions preserved. */
|
|
16849
17166
|
function unexportedName(name) {
|
|
16850
17167
|
const exported = className$3(name);
|
|
@@ -17093,7 +17410,7 @@ function generateModels$4(models, ctx) {
|
|
|
17093
17410
|
lines.push(`type ${structName} struct {`);
|
|
17094
17411
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
17095
17412
|
for (const field of model.fields) {
|
|
17096
|
-
const goFieldName =
|
|
17413
|
+
const goFieldName = domainFieldName$3(field);
|
|
17097
17414
|
if (seenFieldNames.has(goFieldName)) continue;
|
|
17098
17415
|
seenFieldNames.add(goFieldName);
|
|
17099
17416
|
const goType = !field.required ? makeOptional$2(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
|
|
@@ -17680,8 +17997,8 @@ function resolveResourceClassName$1(service, ctx) {
|
|
|
17680
17997
|
function generateResources$4(services, ctx) {
|
|
17681
17998
|
if (services.length === 0) return [];
|
|
17682
17999
|
const files = [];
|
|
17683
|
-
const mountGroups =
|
|
17684
|
-
const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
18000
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
18001
|
+
const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
17685
18002
|
name,
|
|
17686
18003
|
operations: group.operations
|
|
17687
18004
|
})) : services.map((s) => ({
|
|
@@ -17907,7 +18224,7 @@ function generateParamsStruct(mountName, method, op, plan, ctx, resolvedOp) {
|
|
|
17907
18224
|
if (bodyModel) for (const field of bodyModel.fields) {
|
|
17908
18225
|
if (hidden.has(field.name)) continue;
|
|
17909
18226
|
if (groupedParams.has(field.name)) continue;
|
|
17910
|
-
const goField =
|
|
18227
|
+
const goField = domainFieldName$3(field);
|
|
17911
18228
|
if (emittedFields.has(goField)) continue;
|
|
17912
18229
|
emittedFields.add(goField);
|
|
17913
18230
|
const goType = !field.required ? makeOptional$1(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
|
|
@@ -18245,7 +18562,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
|
|
|
18245
18562
|
if (hidden.has(field.name)) continue;
|
|
18246
18563
|
if (groupedParamNames.has(field.name)) continue;
|
|
18247
18564
|
if (!field.required) continue;
|
|
18248
|
-
const goField =
|
|
18565
|
+
const goField = domainFieldName$3(field);
|
|
18249
18566
|
const goType = mapTypeRef$4(field.type);
|
|
18250
18567
|
lines.push(`\t${goField} ${goType} \`json:"${field.name}"\``);
|
|
18251
18568
|
}
|
|
@@ -18257,7 +18574,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
|
|
|
18257
18574
|
if (hidden.has(field.name)) continue;
|
|
18258
18575
|
if (groupedParamNames.has(field.name)) continue;
|
|
18259
18576
|
if (field.required) continue;
|
|
18260
|
-
const goField =
|
|
18577
|
+
const goField = domainFieldName$3(field);
|
|
18261
18578
|
const goType = makeOptional$1(mapTypeRef$4(field.type));
|
|
18262
18579
|
lines.push(`\t${goField} ${goType} \`json:"${field.name},omitempty"\``);
|
|
18263
18580
|
}
|
|
@@ -18278,7 +18595,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
|
|
|
18278
18595
|
if (paramsType && bodyModel) for (const field of bodyModel.fields) {
|
|
18279
18596
|
if (hidden.has(field.name)) continue;
|
|
18280
18597
|
if (!field.required) continue;
|
|
18281
|
-
const goField =
|
|
18598
|
+
const goField = domainFieldName$3(field);
|
|
18282
18599
|
lines.push(`\t\t${goField}: params.${goField},`);
|
|
18283
18600
|
}
|
|
18284
18601
|
lines.push(" }");
|
|
@@ -18289,7 +18606,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
|
|
|
18289
18606
|
if (paramsType && bodyModel) for (const field of bodyModel.fields) {
|
|
18290
18607
|
if (hidden.has(field.name)) continue;
|
|
18291
18608
|
if (field.required) continue;
|
|
18292
|
-
const goField =
|
|
18609
|
+
const goField = domainFieldName$3(field);
|
|
18293
18610
|
lines.push(`\tbody.${goField} = params.${goField}`);
|
|
18294
18611
|
}
|
|
18295
18612
|
const queryArg = op.queryParams.filter((qp) => !hidden.has(qp.name)).length > 0 ? "params" : "nil";
|
|
@@ -18716,7 +19033,7 @@ function generateModelFixture$2(model, modelMap, enumMap) {
|
|
|
18716
19033
|
const fixture = {};
|
|
18717
19034
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
18718
19035
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
18719
|
-
const goName =
|
|
19036
|
+
const goName = domainFieldName$3(f);
|
|
18720
19037
|
if (seenFieldNames.has(goName)) return false;
|
|
18721
19038
|
seenFieldNames.add(goName);
|
|
18722
19039
|
return true;
|
|
@@ -18841,8 +19158,8 @@ function generateTests$4(spec, ctx) {
|
|
|
18841
19158
|
headerPlacement: "skip"
|
|
18842
19159
|
});
|
|
18843
19160
|
const accessPaths = buildServiceAccessPaths$1(spec.services, ctx);
|
|
18844
|
-
const mountGroups =
|
|
18845
|
-
const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
19161
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
19162
|
+
const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
18846
19163
|
name,
|
|
18847
19164
|
operations: group.operations
|
|
18848
19165
|
})) : spec.services.map((s) => ({
|
|
@@ -19456,6 +19773,15 @@ function methodName$2(name) {
|
|
|
19456
19773
|
function fieldName$2(name) {
|
|
19457
19774
|
return toPascalCase(name);
|
|
19458
19775
|
}
|
|
19776
|
+
/**
|
|
19777
|
+
* PascalCase domain property name for a model field, honoring a `domainName`
|
|
19778
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
19779
|
+
* a friendlier C# property name. The wire/serialization key (the
|
|
19780
|
+
* `[JsonPropertyName("...")]` value) still derives from `field.name`.
|
|
19781
|
+
*/
|
|
19782
|
+
function domainFieldName$2(field) {
|
|
19783
|
+
return toPascalCase(field.domainName ?? field.name);
|
|
19784
|
+
}
|
|
19459
19785
|
function trimAsyncSuffix(name) {
|
|
19460
19786
|
return name.endsWith("Async") ? name.slice(0, -5) : name;
|
|
19461
19787
|
}
|
|
@@ -19822,7 +20148,7 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19822
20148
|
const baseClassName = modelClassName(model.name);
|
|
19823
20149
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
19824
20150
|
for (const field of model.fields) {
|
|
19825
|
-
let csName =
|
|
20151
|
+
let csName = domainFieldName$2(field);
|
|
19826
20152
|
if (csName === baseClassName) csName = `${csName}Value`;
|
|
19827
20153
|
fieldMap.set(csName, mapTypeRef$3(field.type));
|
|
19828
20154
|
}
|
|
@@ -19838,7 +20164,9 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19838
20164
|
const fieldTypes = model.fields.map((f) => mapTypeRef$3(f.type));
|
|
19839
20165
|
const needsCollections = fieldTypes.some((t) => t.startsWith("List<") || t.startsWith("Dictionary<"));
|
|
19840
20166
|
const needsSystem = fieldTypes.some((t) => t.includes("DateTimeOffset"));
|
|
19841
|
-
const
|
|
20167
|
+
const hasClassNameCollision = model.fields.some((f) => domainFieldName$2(f) === csClassName);
|
|
20168
|
+
const hasDomainRename = model.fields.some((f) => domainFieldName$2(f) !== fieldName$2(f.name));
|
|
20169
|
+
const needsJsonAttrs = hasClassNameCollision || hasDomainRename || model.fields.some((f) => f.required && isEnumRef(f.type));
|
|
19842
20170
|
lines.push(`namespace ${ctx.namespacePascal}`);
|
|
19843
20171
|
lines.push("{");
|
|
19844
20172
|
if (needsSystem) lines.push(" using System;");
|
|
@@ -19864,9 +20192,10 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19864
20192
|
const dictObjectFields = [];
|
|
19865
20193
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
19866
20194
|
for (const field of model.fields) {
|
|
19867
|
-
let csFieldName =
|
|
20195
|
+
let csFieldName = domainFieldName$2(field);
|
|
19868
20196
|
const collidesWithClassName = csFieldName === csClassName;
|
|
19869
20197
|
if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
|
|
20198
|
+
const hasDomainOverride = domainFieldName$2(field) !== fieldName$2(field.name);
|
|
19870
20199
|
if (seenFieldNames.has(csFieldName)) continue;
|
|
19871
20200
|
seenFieldNames.add(csFieldName);
|
|
19872
20201
|
let useNewModifier = false;
|
|
@@ -19913,7 +20242,7 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19913
20242
|
const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
|
|
19914
20243
|
lines.push(...emitJsonPropertyAttributes(field.name, {
|
|
19915
20244
|
isRequiredEnum,
|
|
19916
|
-
explicitWireName: collidesWithClassName
|
|
20245
|
+
explicitWireName: collidesWithClassName || hasDomainOverride
|
|
19917
20246
|
}));
|
|
19918
20247
|
const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
|
|
19919
20248
|
if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
|
|
@@ -19973,7 +20302,7 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19973
20302
|
}
|
|
19974
20303
|
lines.push(" }");
|
|
19975
20304
|
lines.push("}");
|
|
19976
|
-
files.push({
|
|
20305
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
19977
20306
|
path: `Entities/${csClassName}.cs`,
|
|
19978
20307
|
content: lines.join("\n"),
|
|
19979
20308
|
overwriteExisting: true
|
|
@@ -20217,7 +20546,7 @@ function generateEnums$3(enums, ctx) {
|
|
|
20217
20546
|
}
|
|
20218
20547
|
lines.push(" }");
|
|
20219
20548
|
lines.push("}");
|
|
20220
|
-
files.push({
|
|
20549
|
+
if (isEnumInScope(enumDef.name, ctx)) files.push({
|
|
20221
20550
|
path: `Enums/${typeName}.cs`,
|
|
20222
20551
|
content: lines.join("\n"),
|
|
20223
20552
|
overwriteExisting: true
|
|
@@ -20474,8 +20803,8 @@ function resolveResourceClassName(service, ctx) {
|
|
|
20474
20803
|
function generateResources$3(services, ctx) {
|
|
20475
20804
|
if (services.length === 0) return [];
|
|
20476
20805
|
const files = [];
|
|
20477
|
-
const mountGroups =
|
|
20478
|
-
const entries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
20806
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
20807
|
+
const entries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
20479
20808
|
name,
|
|
20480
20809
|
operations: group.operations
|
|
20481
20810
|
})) : services.map((s) => ({
|
|
@@ -21219,7 +21548,7 @@ function generateModelFixture$1(model, modelMap, enumMap) {
|
|
|
21219
21548
|
const fixture = {};
|
|
21220
21549
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
21221
21550
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
21222
|
-
const csName =
|
|
21551
|
+
const csName = domainFieldName$2(f);
|
|
21223
21552
|
if (seenFieldNames.has(csName)) return false;
|
|
21224
21553
|
seenFieldNames.add(csName);
|
|
21225
21554
|
return true;
|
|
@@ -21286,8 +21615,8 @@ function generateTests$3(spec, ctx) {
|
|
|
21286
21615
|
content: fixture.content,
|
|
21287
21616
|
headerPlacement: "skip"
|
|
21288
21617
|
});
|
|
21289
|
-
const mountGroups =
|
|
21290
|
-
const testEntries = mountGroups.size > 0 ? [...mountGroups].map(([name, group]) => ({
|
|
21618
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
21619
|
+
const testEntries = mountGroups.size > 0 || ctx.scopedServices?.size ? [...mountGroups].map(([name, group]) => ({
|
|
21291
21620
|
name,
|
|
21292
21621
|
operations: group.operations
|
|
21293
21622
|
})) : spec.services.map((s) => ({
|
|
@@ -21740,7 +22069,7 @@ function buildFixtureAssertions(model, spec) {
|
|
|
21740
22069
|
if (field.type.kind !== "primitive" || field.type.type !== "string") continue;
|
|
21741
22070
|
if (field.type.format === "date-time" || field.type.format === "date") continue;
|
|
21742
22071
|
if (field.type.format === "binary") continue;
|
|
21743
|
-
const csField =
|
|
22072
|
+
const csField = domainFieldName$2(field);
|
|
21744
22073
|
const val = fixture[field.name];
|
|
21745
22074
|
if (typeof val === "string" && val.length > 0) assertions.push(`Assert.Equal(${csStringLiteral(val)}, result.${csField});`);
|
|
21746
22075
|
else assertions.push(`Assert.NotEmpty(result.${csField});`);
|
|
@@ -21999,7 +22328,7 @@ const dotnetEmitter = {
|
|
|
21999
22328
|
return buildOperationsMap$3(spec, fixNamespace(ctx));
|
|
22000
22329
|
},
|
|
22001
22330
|
fileHeader() {
|
|
22002
|
-
return
|
|
22331
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
22003
22332
|
},
|
|
22004
22333
|
formatCommand(targetDir) {
|
|
22005
22334
|
const workspace = findDotnetWorkspace(targetDir);
|
|
@@ -22062,6 +22391,16 @@ function propertyName(name) {
|
|
|
22062
22391
|
if (camel === "object") return "objectType";
|
|
22063
22392
|
return escapeReserved(camel);
|
|
22064
22393
|
}
|
|
22394
|
+
/**
|
|
22395
|
+
* camelCase domain property name for a MODEL field, honoring a `domainName`
|
|
22396
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
22397
|
+
* a friendlier name. The wire key (the `@JsonProperty("...")` argument) still
|
|
22398
|
+
* derives from `field.name`. No-op when `domainName` is unset, so it is also
|
|
22399
|
+
* safe on params. Only apply to model fields.
|
|
22400
|
+
*/
|
|
22401
|
+
function domainPropertyName(field) {
|
|
22402
|
+
return propertyName(field.domainName ?? field.name);
|
|
22403
|
+
}
|
|
22065
22404
|
/** Lower-case Kotlin package segment for a service / mount group. */
|
|
22066
22405
|
function packageSegment(name) {
|
|
22067
22406
|
return toPascalCase(name).toLowerCase();
|
|
@@ -22255,7 +22594,7 @@ const enumCanonicalMap = /* @__PURE__ */ new Map();
|
|
|
22255
22594
|
* shortest PascalCase name becomes canonical and the rest emit `typealias`
|
|
22256
22595
|
* files pointing at the canonical class.
|
|
22257
22596
|
*/
|
|
22258
|
-
function generateEnums$2(enums,
|
|
22597
|
+
function generateEnums$2(enums, ctx) {
|
|
22259
22598
|
if (enums.length === 0) return [];
|
|
22260
22599
|
enumCanonicalMap.clear();
|
|
22261
22600
|
const hashGroups = /* @__PURE__ */ new Map();
|
|
@@ -22292,6 +22631,7 @@ function generateEnums$2(enums, _ctx) {
|
|
|
22292
22631
|
for (const enumDef of enums) {
|
|
22293
22632
|
if (enumDef.values.length === 0) continue;
|
|
22294
22633
|
const typeName = canonicalEnumTypeName(enumDef);
|
|
22634
|
+
const enumInScope = isEnumInScope(enumDef.name, ctx);
|
|
22295
22635
|
const canonicalName = sharedSortEmitters.has(enumDef.name) ? void 0 : aliasOf.get(enumDef.name) ?? enumCanonicalMap.get(enumDef.name);
|
|
22296
22636
|
if (canonicalName) {
|
|
22297
22637
|
const canonicalType = className$1(canonicalName);
|
|
@@ -22304,7 +22644,7 @@ function generateEnums$2(enums, _ctx) {
|
|
|
22304
22644
|
aliasLine,
|
|
22305
22645
|
""
|
|
22306
22646
|
].join("\n");
|
|
22307
|
-
files.push({
|
|
22647
|
+
if (enumInScope) files.push({
|
|
22308
22648
|
path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
|
|
22309
22649
|
content: aliasContent,
|
|
22310
22650
|
overwriteExisting: true
|
|
@@ -22360,7 +22700,7 @@ function generateEnums$2(enums, _ctx) {
|
|
|
22360
22700
|
}
|
|
22361
22701
|
lines.push("}");
|
|
22362
22702
|
lines.push("");
|
|
22363
|
-
files.push({
|
|
22703
|
+
if (enumInScope) files.push({
|
|
22364
22704
|
path: `${KOTLIN_SRC_PREFIX$3}${ENUMS_DIR}/${typeName}.kt`,
|
|
22365
22705
|
content: lines.join("\n"),
|
|
22366
22706
|
overwriteExisting: true
|
|
@@ -22557,8 +22897,9 @@ function generateModels$2(models, ctx) {
|
|
|
22557
22897
|
for (const model of models) {
|
|
22558
22898
|
if (skipAsListWrapper(model) || skipAsListMetadata(model)) continue;
|
|
22559
22899
|
const typeName = className$1(model.name);
|
|
22900
|
+
const modelInScope = isModelInScope(model.name, ctx);
|
|
22560
22901
|
if (model.fields.length === 0 && discriminatedUnions.has(typeName)) {
|
|
22561
|
-
files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
|
|
22902
|
+
if (modelInScope) files.push(emitSealedUnion(typeName, discriminatedUnions.get(typeName)));
|
|
22562
22903
|
continue;
|
|
22563
22904
|
}
|
|
22564
22905
|
const canonical = aliasOf.get(model.name);
|
|
@@ -22572,14 +22913,14 @@ function generateModels$2(models, ctx) {
|
|
|
22572
22913
|
`typealias ${typeName} = ${canonicalType}`,
|
|
22573
22914
|
""
|
|
22574
22915
|
].join("\n");
|
|
22575
|
-
files.push({
|
|
22916
|
+
if (modelInScope) files.push({
|
|
22576
22917
|
path: `${KOTLIN_SRC_PREFIX$2}${MODELS_DIR}/${typeName}.kt`,
|
|
22577
22918
|
content: aliasContent,
|
|
22578
22919
|
overwriteExisting: true
|
|
22579
22920
|
});
|
|
22580
22921
|
continue;
|
|
22581
22922
|
}
|
|
22582
|
-
files.push(emitDataClass(model));
|
|
22923
|
+
if (modelInScope) files.push(emitDataClass(model));
|
|
22583
22924
|
}
|
|
22584
22925
|
const eventMapping = [];
|
|
22585
22926
|
for (const model of models) {
|
|
@@ -22764,7 +23105,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
|
|
|
22764
23105
|
const lines = [];
|
|
22765
23106
|
for (const rawField of fields) {
|
|
22766
23107
|
const field = promoteFieldType$1(rawField);
|
|
22767
|
-
const kotlinName =
|
|
23108
|
+
const kotlinName = domainPropertyName(field);
|
|
22768
23109
|
if (seen.has(kotlinName)) continue;
|
|
22769
23110
|
seen.add(kotlinName);
|
|
22770
23111
|
const baseType = mapTypeRef$2(field.type);
|
|
@@ -23273,7 +23614,7 @@ function promoteFieldType(f) {
|
|
|
23273
23614
|
*/
|
|
23274
23615
|
function generateResources$2(services, ctx) {
|
|
23275
23616
|
if (services.length === 0) return [];
|
|
23276
|
-
const mountGroups =
|
|
23617
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
23277
23618
|
if (mountGroups.size === 0) return [];
|
|
23278
23619
|
const files = [];
|
|
23279
23620
|
const resolvedLookup = buildResolvedLookup(ctx);
|
|
@@ -24181,7 +24522,7 @@ function promoteIso8601TypeRef(type, description) {
|
|
|
24181
24522
|
*/
|
|
24182
24523
|
function generateTests$2(spec, ctx) {
|
|
24183
24524
|
const files = [];
|
|
24184
|
-
const mountGroups =
|
|
24525
|
+
const mountGroups = scopedMountGroups(ctx);
|
|
24185
24526
|
const resolvedLookup = buildResolvedLookup(ctx);
|
|
24186
24527
|
const exportedClasses = buildExportedClassNameSet$1(ctx);
|
|
24187
24528
|
for (const [mountName, group] of mountGroups) {
|
|
@@ -24618,7 +24959,7 @@ function buildResponseAssertions(responseModelName, ctx) {
|
|
|
24618
24959
|
for (const field of model.fields) {
|
|
24619
24960
|
if (!field.required) continue;
|
|
24620
24961
|
if (assertions.length >= MAX_RESPONSE_ASSERTIONS) break;
|
|
24621
|
-
const ktProp =
|
|
24962
|
+
const ktProp = domainPropertyName(field);
|
|
24622
24963
|
const type = field.type;
|
|
24623
24964
|
if (type.kind === "primitive") {
|
|
24624
24965
|
if (type.format === "date-time") continue;
|
|
@@ -25062,7 +25403,7 @@ const kotlinEmitter = {
|
|
|
25062
25403
|
return buildOperationsMap$2(spec, ctx);
|
|
25063
25404
|
},
|
|
25064
25405
|
fileHeader() {
|
|
25065
|
-
return
|
|
25406
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
25066
25407
|
},
|
|
25067
25408
|
formatCommand(targetDir) {
|
|
25068
25409
|
if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
|
|
@@ -25114,6 +25455,15 @@ function fieldName$1(name) {
|
|
|
25114
25455
|
return toSnakeCase(name);
|
|
25115
25456
|
}
|
|
25116
25457
|
/**
|
|
25458
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
25459
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
25460
|
+
* a friendlier name. The wire key (still derived from `field.name`) is what
|
|
25461
|
+
* gets sent/received over the wire — only the domain attr/accessor name changes.
|
|
25462
|
+
*/
|
|
25463
|
+
function domainFieldName$1(field) {
|
|
25464
|
+
return toSnakeCase(field.domainName ?? field.name);
|
|
25465
|
+
}
|
|
25466
|
+
/**
|
|
25117
25467
|
* Ruby reserved words that cannot be used as parameter names.
|
|
25118
25468
|
* When a path/query param name collides, suffix with underscore.
|
|
25119
25469
|
*/
|
|
@@ -25334,7 +25684,7 @@ function generateModels$1(models, ctx) {
|
|
|
25334
25684
|
lines.push("module WorkOS");
|
|
25335
25685
|
lines.push(` ${cls} = ${canonCls}`);
|
|
25336
25686
|
lines.push("end");
|
|
25337
|
-
files.push({
|
|
25687
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
25338
25688
|
path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
|
|
25339
25689
|
content: lines.join("\n"),
|
|
25340
25690
|
integrateTarget: true,
|
|
@@ -25344,7 +25694,7 @@ function generateModels$1(models, ctx) {
|
|
|
25344
25694
|
}
|
|
25345
25695
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
25346
25696
|
const fields = model.fields.filter((f) => {
|
|
25347
|
-
const n =
|
|
25697
|
+
const n = domainFieldName$1(f);
|
|
25348
25698
|
if (seenFieldNames.has(n)) return false;
|
|
25349
25699
|
seenFieldNames.add(n);
|
|
25350
25700
|
return true;
|
|
@@ -25358,7 +25708,7 @@ function generateModels$1(models, ctx) {
|
|
|
25358
25708
|
lines.push(" HASH_ATTRS = {");
|
|
25359
25709
|
for (let i = 0; i < fields.length; i++) {
|
|
25360
25710
|
const field = fields[i];
|
|
25361
|
-
const fname =
|
|
25711
|
+
const fname = domainFieldName$1(field);
|
|
25362
25712
|
const sep = i === fields.length - 1 ? "" : ",";
|
|
25363
25713
|
lines.push(` ${rubyHashLiteralKey(field.name)} :${fname}${sep}`);
|
|
25364
25714
|
}
|
|
@@ -25367,13 +25717,13 @@ function generateModels$1(models, ctx) {
|
|
|
25367
25717
|
if (deprecatedFields.length > 0) {
|
|
25368
25718
|
for (const f of deprecatedFields) {
|
|
25369
25719
|
const desc = f.description ? ` ${f.description.split("\n")[0].trim()}` : "";
|
|
25370
|
-
lines.push(` # @!attribute ${
|
|
25720
|
+
lines.push(` # @!attribute ${domainFieldName$1(f)}`);
|
|
25371
25721
|
lines.push(` # @deprecated${desc}`);
|
|
25372
25722
|
}
|
|
25373
25723
|
lines.push("");
|
|
25374
25724
|
}
|
|
25375
25725
|
if (accessorFields.length > 0) {
|
|
25376
|
-
const attrs = accessorFields.map((f) => `:${
|
|
25726
|
+
const attrs = accessorFields.map((f) => `:${domainFieldName$1(f)}`);
|
|
25377
25727
|
if (attrs.length === 1) lines.push(` attr_accessor ${attrs[0]}`);
|
|
25378
25728
|
else {
|
|
25379
25729
|
lines.push(` attr_accessor \\`);
|
|
@@ -25385,7 +25735,7 @@ function generateModels$1(models, ctx) {
|
|
|
25385
25735
|
lines.push("");
|
|
25386
25736
|
}
|
|
25387
25737
|
for (const f of deprecatedFields) {
|
|
25388
|
-
const fname =
|
|
25738
|
+
const fname = domainFieldName$1(f);
|
|
25389
25739
|
lines.push(` def ${fname}`);
|
|
25390
25740
|
lines.push(` warn "[DEPRECATION] \\\`${fname}\\\` is deprecated and will be removed in a future version.", uplevel: 1`);
|
|
25391
25741
|
lines.push(` @${fname}`);
|
|
@@ -25399,14 +25749,14 @@ function generateModels$1(models, ctx) {
|
|
|
25399
25749
|
lines.push(" def initialize(json)");
|
|
25400
25750
|
lines.push(" hash = self.class.normalize(json)");
|
|
25401
25751
|
for (const field of fields) {
|
|
25402
|
-
const fname =
|
|
25752
|
+
const fname = domainFieldName$1(field);
|
|
25403
25753
|
const rawKey = field.name;
|
|
25404
25754
|
lines.push(` ${deserializeAssignment(fname, rawKey, field.type, field.required, enumNames, modelNames)}`);
|
|
25405
25755
|
}
|
|
25406
25756
|
lines.push(" end");
|
|
25407
25757
|
lines.push(" end");
|
|
25408
25758
|
lines.push("end");
|
|
25409
|
-
files.push({
|
|
25759
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
25410
25760
|
path: `lib/workos/${dirFor(model.name)}/${file}.rb`,
|
|
25411
25761
|
content: lines.join("\n"),
|
|
25412
25762
|
integrateTarget: true,
|
|
@@ -25515,6 +25865,7 @@ function generateEnums$1(enums, ctx) {
|
|
|
25515
25865
|
const aliasOf = collectEnumAliasOf(enums);
|
|
25516
25866
|
for (const enumDef of enums) {
|
|
25517
25867
|
const cls = className(enumDef.name);
|
|
25868
|
+
const enumInScope = isEnumInScope(enumDef.name, ctx);
|
|
25518
25869
|
const canonicalName = aliasOf.get(enumDef.name);
|
|
25519
25870
|
if (canonicalName) {
|
|
25520
25871
|
const canonicalCls = className(canonicalName);
|
|
@@ -25524,7 +25875,7 @@ function generateEnums$1(enums, ctx) {
|
|
|
25524
25875
|
lines.push(` ${cls} = ${canonicalCls}`);
|
|
25525
25876
|
lines.push(" end");
|
|
25526
25877
|
lines.push("end");
|
|
25527
|
-
files.push({
|
|
25878
|
+
if (enumInScope) files.push({
|
|
25528
25879
|
path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
|
|
25529
25880
|
content: lines.join("\n"),
|
|
25530
25881
|
integrateTarget: true,
|
|
@@ -25553,7 +25904,7 @@ function generateEnums$1(enums, ctx) {
|
|
|
25553
25904
|
lines.push(" end");
|
|
25554
25905
|
lines.push(" end");
|
|
25555
25906
|
lines.push("end");
|
|
25556
|
-
files.push({
|
|
25907
|
+
if (enumInScope) files.push({
|
|
25557
25908
|
path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
|
|
25558
25909
|
content: lines.join("\n"),
|
|
25559
25910
|
integrateTarget: true,
|
|
@@ -25590,7 +25941,7 @@ function generateEnums$1(enums, ctx) {
|
|
|
25590
25941
|
lines.push(" end");
|
|
25591
25942
|
lines.push(" end");
|
|
25592
25943
|
lines.push("end");
|
|
25593
|
-
files.push({
|
|
25944
|
+
if (enumInScope) files.push({
|
|
25594
25945
|
path: `lib/workos/types/${fileName(enumDef.name)}.rb`,
|
|
25595
25946
|
content: lines.join("\n"),
|
|
25596
25947
|
integrateTarget: true,
|
|
@@ -25968,7 +26319,7 @@ function emitInlineVariantRbi(v) {
|
|
|
25968
26319
|
*/
|
|
25969
26320
|
function generateResources$1(services, ctx) {
|
|
25970
26321
|
const files = [];
|
|
25971
|
-
const groups =
|
|
26322
|
+
const groups = scopedMountGroups(ctx);
|
|
25972
26323
|
const lookup = buildResolvedLookup(ctx);
|
|
25973
26324
|
const modelNames = new Set(ctx.spec.models.map((m) => m.name));
|
|
25974
26325
|
const enumNames = new Set(ctx.spec.enums.map((e) => e.name));
|
|
@@ -26746,7 +27097,7 @@ function generateClientClass(spec, ctx) {
|
|
|
26746
27097
|
*/
|
|
26747
27098
|
function generateTests$1(spec, ctx) {
|
|
26748
27099
|
const files = [];
|
|
26749
|
-
const groups =
|
|
27100
|
+
const groups = scopedMountGroups(ctx);
|
|
26750
27101
|
const models = spec.models;
|
|
26751
27102
|
const modelByName = /* @__PURE__ */ new Map();
|
|
26752
27103
|
for (const m of models) modelByName.set(m.name, m);
|
|
@@ -26895,7 +27246,7 @@ function generateModelRoundTripTest(spec) {
|
|
|
26895
27246
|
const dedupFields = /* @__PURE__ */ new Set();
|
|
26896
27247
|
for (const f of model.fields) {
|
|
26897
27248
|
const wireName = f.name;
|
|
26898
|
-
const rubyFieldName =
|
|
27249
|
+
const rubyFieldName = domainFieldName$1(f);
|
|
26899
27250
|
if (dedupFields.has(rubyFieldName)) continue;
|
|
26900
27251
|
dedupFields.add(rubyFieldName);
|
|
26901
27252
|
const stub = roundTripStub(f.type, enumNames);
|
|
@@ -27224,7 +27575,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
27224
27575
|
lines.push("");
|
|
27225
27576
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
27226
27577
|
for (const f of model.fields) {
|
|
27227
|
-
const fname =
|
|
27578
|
+
const fname = domainFieldName$1(f);
|
|
27228
27579
|
if (seenFieldNames.has(fname)) continue;
|
|
27229
27580
|
seenFieldNames.add(fname);
|
|
27230
27581
|
const sorbetType = f.required ? mapSorbetType(f.type) : wrapNilable(mapSorbetType(f.type));
|
|
@@ -27242,7 +27593,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
27242
27593
|
lines.push(" def to_json(*args); end");
|
|
27243
27594
|
lines.push(" end");
|
|
27244
27595
|
lines.push("end");
|
|
27245
|
-
files.push({
|
|
27596
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
27246
27597
|
path: `rbi/workos/${fileName(model.name)}.rbi`,
|
|
27247
27598
|
content: lines.join("\n"),
|
|
27248
27599
|
integrateTarget: true,
|
|
@@ -27258,6 +27609,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
27258
27609
|
const groupOwners = buildGroupOwnerMap(ctx);
|
|
27259
27610
|
const exportedClasses = buildExportedClassNameSet(ctx);
|
|
27260
27611
|
for (const [mountTarget, group] of groups) {
|
|
27612
|
+
if (!isMountInScope(mountTarget, ctx)) continue;
|
|
27261
27613
|
const resolvedTarget = resolveServiceTarget(mountTarget, exportedClasses);
|
|
27262
27614
|
const cls = className(resolvedTarget);
|
|
27263
27615
|
const lines = [];
|
|
@@ -27501,7 +27853,7 @@ const rubyEmitter = {
|
|
|
27501
27853
|
return buildOperationsMap$1(spec, ctx);
|
|
27502
27854
|
},
|
|
27503
27855
|
fileHeader() {
|
|
27504
|
-
return `# frozen_string_literal: true\n\n#
|
|
27856
|
+
return `# frozen_string_literal: true\n\n# ${AUTOGEN_NOTICE}`;
|
|
27505
27857
|
},
|
|
27506
27858
|
formatCommand(targetDir) {
|
|
27507
27859
|
return {
|
|
@@ -27587,6 +27939,15 @@ function methodName(name) {
|
|
|
27587
27939
|
function fieldName(name) {
|
|
27588
27940
|
return escapeKeyword(toSnakeCase(name));
|
|
27589
27941
|
}
|
|
27942
|
+
/**
|
|
27943
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
27944
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
27945
|
+
* a friendlier identifier. The wire name (and thus the `#[serde(rename = ...)]`
|
|
27946
|
+
* key) still derives from `field.name`.
|
|
27947
|
+
*/
|
|
27948
|
+
function domainFieldName(field) {
|
|
27949
|
+
return escapeKeyword(toSnakeCase(field.domainName ?? field.name));
|
|
27950
|
+
}
|
|
27590
27951
|
/** PascalCase enum variant. */
|
|
27591
27952
|
function variantName(value) {
|
|
27592
27953
|
const pascal = toPascalCase(String(value));
|
|
@@ -27858,15 +28219,17 @@ function generateModels(models, ctx, registry) {
|
|
|
27858
28219
|
const files = [];
|
|
27859
28220
|
const moduleNames = [];
|
|
27860
28221
|
const seen = /* @__PURE__ */ new Set();
|
|
28222
|
+
const taggedVariantFields = collectTaggedVariantFields(models);
|
|
27861
28223
|
for (const model of models) {
|
|
27862
28224
|
const mod = moduleName(model.name);
|
|
27863
28225
|
if (seen.has(mod)) continue;
|
|
27864
28226
|
seen.add(mod);
|
|
27865
28227
|
moduleNames.push(mod);
|
|
27866
28228
|
const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
|
|
27867
|
-
|
|
28229
|
+
const content = renderModel(model, registry, taggedVariantFields.get(model.name));
|
|
28230
|
+
if (isModelInScope(model.name, ctx)) files.push({
|
|
27868
28231
|
path,
|
|
27869
|
-
content
|
|
28232
|
+
content,
|
|
27870
28233
|
overwriteExisting: true
|
|
27871
28234
|
});
|
|
27872
28235
|
}
|
|
@@ -27878,7 +28241,43 @@ function generateModels(models, ctx, registry) {
|
|
|
27878
28241
|
});
|
|
27879
28242
|
return files;
|
|
27880
28243
|
}
|
|
27881
|
-
|
|
28244
|
+
/**
|
|
28245
|
+
* Walk every model field and record which models are arms of an
|
|
28246
|
+
* internally-tagged union, mapped to that union's discriminator property.
|
|
28247
|
+
*/
|
|
28248
|
+
function collectTaggedVariantFields(models) {
|
|
28249
|
+
const out = /* @__PURE__ */ new Map();
|
|
28250
|
+
const visit = (ref) => {
|
|
28251
|
+
switch (ref.kind) {
|
|
28252
|
+
case "union":
|
|
28253
|
+
if (ref.discriminator?.property) for (const variant of ref.variants) {
|
|
28254
|
+
const name = variantModelName(variant);
|
|
28255
|
+
if (name) out.set(name, ref.discriminator.property);
|
|
28256
|
+
}
|
|
28257
|
+
ref.variants.forEach(visit);
|
|
28258
|
+
break;
|
|
28259
|
+
case "array":
|
|
28260
|
+
visit(ref.items);
|
|
28261
|
+
break;
|
|
28262
|
+
case "nullable":
|
|
28263
|
+
visit(ref.inner);
|
|
28264
|
+
break;
|
|
28265
|
+
case "map":
|
|
28266
|
+
visit(ref.valueType);
|
|
28267
|
+
break;
|
|
28268
|
+
default: break;
|
|
28269
|
+
}
|
|
28270
|
+
};
|
|
28271
|
+
for (const model of models) for (const field of model.fields) visit(field.type);
|
|
28272
|
+
return out;
|
|
28273
|
+
}
|
|
28274
|
+
/** Resolve the underlying model name of a union arm, unwrapping a nullable. */
|
|
28275
|
+
function variantModelName(ref) {
|
|
28276
|
+
if (ref.kind === "model") return ref.name;
|
|
28277
|
+
if (ref.kind === "nullable") return variantModelName(ref.inner);
|
|
28278
|
+
return null;
|
|
28279
|
+
}
|
|
28280
|
+
function renderModel(model, registry, tagField) {
|
|
27882
28281
|
const lines = [];
|
|
27883
28282
|
lines.push(HEADER_PLACEHOLDER);
|
|
27884
28283
|
lines.push("#[allow(unused_imports)]");
|
|
@@ -27890,7 +28289,7 @@ function renderModel(model, registry) {
|
|
|
27890
28289
|
if (model.description) lines.push(...docComment$1(model.description));
|
|
27891
28290
|
lines.push("#[derive(Debug, Clone, Serialize, Deserialize)]");
|
|
27892
28291
|
const resolvedNames = resolveFieldNames(model.fields);
|
|
27893
|
-
const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry));
|
|
28292
|
+
const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry, tagField));
|
|
27894
28293
|
if (fieldLines.length === 0) lines.push(`pub struct ${typeName(model.name)} {}`);
|
|
27895
28294
|
else {
|
|
27896
28295
|
lines.push(`pub struct ${typeName(model.name)} {`);
|
|
@@ -27900,17 +28299,20 @@ function renderModel(model, registry) {
|
|
|
27900
28299
|
return lines.filter((l) => l !== HEADER_PLACEHOLDER).join("\n") + "\n";
|
|
27901
28300
|
}
|
|
27902
28301
|
/**
|
|
27903
|
-
* Resolve unique Rust identifiers for struct fields.
|
|
27904
|
-
*
|
|
27905
|
-
* `
|
|
27906
|
-
*
|
|
27907
|
-
*
|
|
28302
|
+
* Resolve unique Rust identifiers for struct fields. The domain identifier
|
|
28303
|
+
* honors a `fieldHints` override (`domainName`, e.g. wire `connection_type` →
|
|
28304
|
+
* domain `type`); the wire name (and the `#[serde(rename = ...)]` key emitted
|
|
28305
|
+
* in `renderField`) still derives from `f.name`. Multiple names can collide
|
|
28306
|
+
* after snake-casing (e.g. `integration_type` and `integrationType` both
|
|
28307
|
+
* become `integration_type`). Subsequent collisions get a numeric suffix so
|
|
28308
|
+
* the struct compiles; serde `rename` preserves the original wire name in every
|
|
28309
|
+
* case.
|
|
27908
28310
|
*/
|
|
27909
28311
|
function resolveFieldNames(fields) {
|
|
27910
28312
|
const used = /* @__PURE__ */ new Set();
|
|
27911
28313
|
const out = [];
|
|
27912
28314
|
for (const f of fields) {
|
|
27913
|
-
const base =
|
|
28315
|
+
const base = domainFieldName(f);
|
|
27914
28316
|
let candidate = base;
|
|
27915
28317
|
let suffix = 2;
|
|
27916
28318
|
while (used.has(candidate)) {
|
|
@@ -27922,7 +28324,7 @@ function resolveFieldNames(fields) {
|
|
|
27922
28324
|
}
|
|
27923
28325
|
return out;
|
|
27924
28326
|
}
|
|
27925
|
-
function renderField(field, rustField, modelName, registry) {
|
|
28327
|
+
function renderField(field, rustField, modelName, registry, tagField) {
|
|
27926
28328
|
const lines = [];
|
|
27927
28329
|
const hasDescription = !!field.description;
|
|
27928
28330
|
if (hasDescription) for (const c of docComment$1(field.description)) lines.push(` ${c}`);
|
|
@@ -27937,8 +28339,13 @@ function renderField(field, rustField, modelName, registry) {
|
|
|
27937
28339
|
});
|
|
27938
28340
|
if ((!field.required || field.type.kind === "nullable") && !baseType.startsWith("Option<")) baseType = makeOptional(baseType);
|
|
27939
28341
|
baseType = applySecretRedaction(baseType, field.name);
|
|
27940
|
-
if (
|
|
27941
|
-
|
|
28342
|
+
if (tagField === field.name) {
|
|
28343
|
+
const args = rename ? `rename = "${rename}", default, skip_serializing` : "default, skip_serializing";
|
|
28344
|
+
lines.push(` #[serde(${args})]`);
|
|
28345
|
+
} else {
|
|
28346
|
+
if (rename) lines.push(` #[serde(rename = "${rename}")]`);
|
|
28347
|
+
if (baseType.startsWith("Option<")) lines.push(" #[serde(skip_serializing_if = \"Option::is_none\", default)]");
|
|
28348
|
+
}
|
|
27942
28349
|
if (field.deprecated) lines.push(" #[deprecated]");
|
|
27943
28350
|
lines.push(` pub ${rustField}: ${baseType},`);
|
|
27944
28351
|
return lines.join("\n");
|
|
@@ -27981,7 +28388,7 @@ function formatDefault$1(value) {
|
|
|
27981
28388
|
* variant and re-serialize as the canonical wire string.
|
|
27982
28389
|
* - `Display`, `FromStr`, and `AsRef<str>` are implemented for ergonomics.
|
|
27983
28390
|
*/
|
|
27984
|
-
function generateEnums(enums,
|
|
28391
|
+
function generateEnums(enums, ctx) {
|
|
27985
28392
|
const files = [];
|
|
27986
28393
|
const seen = /* @__PURE__ */ new Set();
|
|
27987
28394
|
const moduleNames = [];
|
|
@@ -27991,6 +28398,7 @@ function generateEnums(enums, _ctx) {
|
|
|
27991
28398
|
if (seen.has(mod)) continue;
|
|
27992
28399
|
seen.add(mod);
|
|
27993
28400
|
moduleNames.push(mod);
|
|
28401
|
+
if (!isEnumInScope(e.name, ctx)) continue;
|
|
27994
28402
|
files.push({
|
|
27995
28403
|
path: `src/enums/${mod}.rs`,
|
|
27996
28404
|
content: renderEnum(e),
|
|
@@ -28150,6 +28558,7 @@ function generateResources(_services, ctx, registry) {
|
|
|
28150
28558
|
module: basename,
|
|
28151
28559
|
struct
|
|
28152
28560
|
});
|
|
28561
|
+
if (!isMountInScope(mountName, ctx)) continue;
|
|
28153
28562
|
files.push({
|
|
28154
28563
|
path: `src/resources/${basename}.rs`,
|
|
28155
28564
|
content: renderMountGroup(mountName, group.resolvedOps, ctx, registry, lookup),
|
|
@@ -28520,7 +28929,7 @@ function registerSyntheticBody(op, paramsName, bodyGroupParamNames, bodyGroupFie
|
|
|
28520
28929
|
if (!f.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
|
|
28521
28930
|
rust = applySecretRedaction(rust, f.name);
|
|
28522
28931
|
return {
|
|
28523
|
-
rustName:
|
|
28932
|
+
rustName: domainFieldName(f),
|
|
28524
28933
|
wireName: f.name,
|
|
28525
28934
|
rustType: rust,
|
|
28526
28935
|
required: !!f.required && !rust.startsWith("Option<"),
|
|
@@ -29298,7 +29707,7 @@ function generateTests(spec, ctx) {
|
|
|
29298
29707
|
content: renderCommon(ctx),
|
|
29299
29708
|
overwriteExisting: true
|
|
29300
29709
|
});
|
|
29301
|
-
const groups =
|
|
29710
|
+
const groups = scopedMountGroups(ctx);
|
|
29302
29711
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
29303
29712
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
29304
29713
|
for (const [mountName, group] of groups) {
|
|
@@ -29875,7 +30284,7 @@ const rustEmitter = {
|
|
|
29875
30284
|
return buildOperationsMap(spec, ctx);
|
|
29876
30285
|
},
|
|
29877
30286
|
fileHeader() {
|
|
29878
|
-
return
|
|
30287
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
29879
30288
|
},
|
|
29880
30289
|
formatCommand(_targetDir) {
|
|
29881
30290
|
return {
|
|
@@ -29930,4 +30339,4 @@ const workosEmittersPlugin = {
|
|
|
29930
30339
|
//#endregion
|
|
29931
30340
|
export { fieldName$2 as A, servicePropertyName$2 as B, apiClassName as C, dotnetEmitter as D, propertyName as E, fieldName$3 as F, fieldName$5 as H, methodName$3 as I, trimMountedResourceFromMethod$2 as L, trimMountedResourceFromMethod$1 as M, goEmitter as N, appendAsyncSuffix as O, className$3 as P, phpEmitter as R, kotlinEmitter as S, packageSegment as T, safeParamName$1 as U, pythonEmitter as V, nodeEmitter as W, rubyEmitter as _, rustExtractor as a, resolveServiceTarget as b, pythonExtractor as c, rustEmitter as d, fieldName as f, typeName as g, resourceAccessorName as h, kotlinExtractor as i, methodName$2 as j, className$2 as k, rubyExtractor as l, moduleName as m, elixirExtractor as n, goExtractor as o, methodName as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u, buildExportedClassNameSet as v, methodName$1 as w, safeParamName as x, fieldName$1 as y, fieldName$4 as z };
|
|
29932
30341
|
|
|
29933
|
-
//# sourceMappingURL=plugin-
|
|
30342
|
+
//# sourceMappingURL=plugin-BXDPA9pJ.mjs.map
|