@workos/oagen-emitters 0.18.2 → 0.18.4
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 +17 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-bqfwowQ3.mjs → plugin-Cciic50q.mjs} +457 -101
- package/dist/plugin-Cciic50q.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/docs/sdk-architecture/rust.md +2 -2
- package/package.json +3 -3
- package/src/dotnet/fixtures.ts +17 -3
- package/src/dotnet/index.ts +2 -1
- package/src/dotnet/models.ts +30 -5
- package/src/dotnet/naming.ts +10 -0
- package/src/dotnet/tests.ts +5 -1
- 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 +19 -6
- package/src/kotlin/index.ts +2 -1
- package/src/kotlin/models.ts +5 -2
- package/src/kotlin/naming.ts +11 -0
- package/src/kotlin/tests.ts +5 -1
- package/src/node/field-plan.ts +3 -3
- package/src/node/index.ts +2 -1
- package/src/node/models.ts +40 -1
- package/src/node/naming.ts +10 -0
- package/src/node/options.ts +45 -1
- package/src/node/resources.ts +76 -19
- package/src/node/tests.ts +296 -30
- package/src/php/index.ts +2 -1
- package/src/php/models.ts +11 -5
- package/src/php/naming.ts +10 -0
- package/src/php/tests.ts +11 -2
- package/src/python/fixtures.ts +4 -3
- package/src/python/index.ts +2 -1
- package/src/python/models.ts +12 -6
- package/src/python/naming.ts +10 -0
- package/src/python/tests.ts +11 -6
- package/src/ruby/index.ts +2 -1
- package/src/ruby/models.ts +10 -7
- package/src/ruby/naming.ts +10 -0
- package/src/ruby/rbi.ts +3 -1
- package/src/ruby/tests.ts +4 -1
- package/src/rust/index.ts +2 -1
- package/src/rust/models.ts +87 -15
- package/src/rust/naming.ts +10 -0
- package/src/rust/resources.ts +6 -2
- package/src/shared/file-header.ts +13 -0
- package/test/node/resources.test.ts +31 -2
- package/test/rust/models.test.ts +49 -0
- package/dist/plugin-bqfwowQ3.mjs.map +0 -1
|
@@ -3637,6 +3637,29 @@ function isHandOwnedType(ctx, name) {
|
|
|
3637
3637
|
if (!configured || configured.length === 0) return false;
|
|
3638
3638
|
return configured.includes(name);
|
|
3639
3639
|
}
|
|
3640
|
+
/**
|
|
3641
|
+
* Resolve the Node operation override for an operation, keyed by "METHOD /path".
|
|
3642
|
+
*/
|
|
3643
|
+
function operationOverrideFor$1(ctx, op) {
|
|
3644
|
+
return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
3645
|
+
}
|
|
3646
|
+
/**
|
|
3647
|
+
* `planOperation` plus the Node `responseModel` override. When an operation
|
|
3648
|
+
* override supplies `responseModel`, the resolved response model name is
|
|
3649
|
+
* replaced so the resource and its generated test reference the desired wire
|
|
3650
|
+
* type and deserializer. Use this everywhere the Node emitter would otherwise
|
|
3651
|
+
* call `planOperation(op)` directly so resource and test stay in lockstep.
|
|
3652
|
+
*/
|
|
3653
|
+
function planOperationFor(op, ctx) {
|
|
3654
|
+
const plan = planOperation(op);
|
|
3655
|
+
const responseModel = operationOverrideFor$1(ctx, op)?.responseModel;
|
|
3656
|
+
if (responseModel && responseModel !== plan.responseModelName) return {
|
|
3657
|
+
...plan,
|
|
3658
|
+
responseModelName: responseModel,
|
|
3659
|
+
isModelResponse: true
|
|
3660
|
+
};
|
|
3661
|
+
return plan;
|
|
3662
|
+
}
|
|
3640
3663
|
//#endregion
|
|
3641
3664
|
//#region src/node/live-surface.ts
|
|
3642
3665
|
const SRC_DIR = "src";
|
|
@@ -4913,7 +4936,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
|
|
|
4913
4936
|
const irDomainFields = /* @__PURE__ */ new Set();
|
|
4914
4937
|
for (const field of model.fields) {
|
|
4915
4938
|
irWireFields.add(wireFieldName(field.name));
|
|
4916
|
-
irDomainFields.add(fieldName$6(field.name));
|
|
4939
|
+
irDomainFields.add(fieldName$6(field.domainName ?? field.name));
|
|
4917
4940
|
}
|
|
4918
4941
|
for (const [wireField2, fieldDef] of Object.entries(baselineResponse.fields)) {
|
|
4919
4942
|
if (fieldDef.optional) continue;
|
|
@@ -4957,7 +4980,7 @@ function serializerHasBaselineIncompatibility(model, baselineResponse, baselineD
|
|
|
4957
4980
|
return false;
|
|
4958
4981
|
}
|
|
4959
4982
|
function planDeserializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
|
|
4960
|
-
const domain = fieldName$6(field.name);
|
|
4983
|
+
const domain = fieldName$6(field.domainName ?? field.name);
|
|
4961
4984
|
const wireAccess = `response.${wireFieldName(field.name)}`;
|
|
4962
4985
|
const skip = skipFormatFields.has(field.name);
|
|
4963
4986
|
const baselineDomainField = baselineDomain?.fields?.[domain];
|
|
@@ -4993,7 +5016,7 @@ function planDeserializeGuard(field, expr, wireAccess, effectivelyOptional, isNe
|
|
|
4993
5016
|
}
|
|
4994
5017
|
function planSerializeField(field, baselineDomain, baselineResponse, skipFormatFields, ctx) {
|
|
4995
5018
|
const wire = wireFieldName(field.name);
|
|
4996
|
-
const domain = fieldName$6(field.name);
|
|
5019
|
+
const domain = fieldName$6(field.domainName ?? field.name);
|
|
4997
5020
|
const domainAccess = `model.${domain}`;
|
|
4998
5021
|
const skip = skipFormatFields.has(field.name);
|
|
4999
5022
|
const baselineWireField = baselineResponse?.fields?.[wire];
|
|
@@ -5533,9 +5556,6 @@ function existingInterfaceBarrelExports(ctx, serviceDir, stem) {
|
|
|
5533
5556
|
const escapedStem = stem.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5534
5557
|
return new RegExp(`export\\s+(?:type\\s+)?(?:\\*|\\{[^}]+\\})\\s+from\\s+['"]\\./${escapedStem}['"]`).test(content);
|
|
5535
5558
|
}
|
|
5536
|
-
function operationOverrideFor$1(ctx, op) {
|
|
5537
|
-
return nodeOptions(ctx).operationOverrides?.[`${op.httpMethod.toUpperCase()} ${op.path}`];
|
|
5538
|
-
}
|
|
5539
5559
|
function baselineMethodFor$1(service, method, ctx) {
|
|
5540
5560
|
const serviceClass = resolveResourceClassName$3(service, ctx);
|
|
5541
5561
|
return ctx.apiSurface?.classes?.[serviceClass]?.methods?.[method]?.[0];
|
|
@@ -5561,10 +5581,11 @@ function optionsObjectParam$1(method) {
|
|
|
5561
5581
|
const [param] = method.params;
|
|
5562
5582
|
if (param.name !== "options") return void 0;
|
|
5563
5583
|
if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
|
|
5564
|
-
|
|
5584
|
+
const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
|
|
5585
|
+
if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
|
|
5565
5586
|
return {
|
|
5566
5587
|
name: "options",
|
|
5567
|
-
type
|
|
5588
|
+
type,
|
|
5568
5589
|
optional: param.optional === true,
|
|
5569
5590
|
generated: false
|
|
5570
5591
|
};
|
|
@@ -5592,10 +5613,21 @@ function optionsObjectShouldBeOptional(op, plan, resolvedOp) {
|
|
|
5592
5613
|
function operationHasOptionsInput(op, plan, resolvedOp) {
|
|
5593
5614
|
return op.pathParams.length > 0 || plan.hasBody || plan.isPaginated || visibleQueryParamsForOptions(op, plan, resolvedOp).length > 0;
|
|
5594
5615
|
}
|
|
5616
|
+
function isClosedObjectLiteral(type) {
|
|
5617
|
+
const t = type.trim();
|
|
5618
|
+
if (!t.startsWith("{")) return false;
|
|
5619
|
+
let depth = 0;
|
|
5620
|
+
for (let i = 0; i < t.length; i++) {
|
|
5621
|
+
const ch = t[i];
|
|
5622
|
+
if (ch === "{") depth++;
|
|
5623
|
+
else if (ch === "}" && --depth === 0) return i === t.length - 1;
|
|
5624
|
+
}
|
|
5625
|
+
return false;
|
|
5626
|
+
}
|
|
5595
5627
|
function optionsObjectInfo(service, method, op, plan, ctx, baselineMethod, resolvedOp) {
|
|
5596
5628
|
const baseline = optionsObjectParam$1(baselineMethod);
|
|
5597
5629
|
if (baseline) {
|
|
5598
|
-
if (baseline.type
|
|
5630
|
+
if (isClosedObjectLiteral(baseline.type) && isNodeOwnedService(ctx, service.name, resolveResourceClassName$3(service, ctx))) {
|
|
5599
5631
|
const body = extractRequestBodyType(op, ctx);
|
|
5600
5632
|
if (body?.kind === "model") return {
|
|
5601
5633
|
name: "options",
|
|
@@ -5754,14 +5786,14 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
|
|
|
5754
5786
|
const resolvedLookup = buildResolvedLookup(ctx);
|
|
5755
5787
|
const plans = service.operations.map((op) => ({
|
|
5756
5788
|
op,
|
|
5757
|
-
plan:
|
|
5789
|
+
plan: planOperationFor(op, ctx),
|
|
5758
5790
|
method: resolveMethodName$6(op, service, ctx)
|
|
5759
5791
|
}));
|
|
5760
5792
|
for (const { op, plan, method } of plans) {
|
|
5761
5793
|
const resolvedOp = lookupResolved(op, resolvedLookup);
|
|
5762
5794
|
const optionInfo = optionsObjectInfo(service, method, op, plan, ctx, baselineMethodFor$1(service, method, ctx), resolvedOp);
|
|
5763
5795
|
if (!optionInfo?.generated) continue;
|
|
5764
|
-
if (baselineTypeSourceFile(ctx, optionInfo.type)) continue;
|
|
5796
|
+
if (op.pathParams.length === 0 && baselineTypeSourceFile(ctx, optionInfo.type)) continue;
|
|
5765
5797
|
const optionsName = optionInfo.type;
|
|
5766
5798
|
const optionFileStem = `${fileName$3(optionsName)}.interface`;
|
|
5767
5799
|
const filePath = `src/${serviceDir}/interfaces/${optionFileStem}.ts`;
|
|
@@ -5804,7 +5836,8 @@ function generateOptionsInterfaces(service, ctx, specEnumNames) {
|
|
|
5804
5836
|
}
|
|
5805
5837
|
headerParts.push(` ${name}${opt}: ${type};`);
|
|
5806
5838
|
};
|
|
5807
|
-
|
|
5839
|
+
const optionsPathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
5840
|
+
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);
|
|
5808
5841
|
for (const param of visibleQueryParamsForOptions(op, plan, resolvedOp)) pushField(fieldName$6(param.name), param.required, mapParamType(param.type, specEnumNames), param.description, param.deprecated);
|
|
5809
5842
|
if (bodyInfo?.kind === "model") {
|
|
5810
5843
|
const bodyModel = ctx.spec.models.find((m) => m.name === bodyInfo.name);
|
|
@@ -5876,7 +5909,7 @@ function generateResourceClass(service, ctx) {
|
|
|
5876
5909
|
const resourcePath = `src/${serviceDir}/${fileName$3(resolvedName)}.ts`;
|
|
5877
5910
|
let plans = service.operations.map((op) => ({
|
|
5878
5911
|
op,
|
|
5879
|
-
plan:
|
|
5912
|
+
plan: planOperationFor(op, ctx),
|
|
5880
5913
|
method: resolveMethodName$6(op, service, ctx)
|
|
5881
5914
|
}));
|
|
5882
5915
|
deduplicateMethodNames(plans, ctx);
|
|
@@ -5985,6 +6018,7 @@ function generateResourceClass(service, ctx) {
|
|
|
5985
6018
|
const hasCustomEncoding = plans.some((p) => p.op.requestBodyEncoding && p.op.requestBodyEncoding !== "json" && p.plan.hasBody);
|
|
5986
6019
|
if (hasIdempotentPost || hasCustomEncoding) lines.push("import type { PostOptions } from '../common/interfaces/post-options.interface';");
|
|
5987
6020
|
const importedTypeNames = /* @__PURE__ */ new Set();
|
|
6021
|
+
if (needsPaginationOptionsImport) importedTypeNames.add("PaginationOptions");
|
|
5988
6022
|
for (const optionType of optionObjectTypes) {
|
|
5989
6023
|
if (isValidTypeIdentifier(optionType)) {
|
|
5990
6024
|
if (importedTypeNames.has(optionType)) continue;
|
|
@@ -6543,7 +6577,14 @@ function renderOptionsObjectMethod(lines, op, plan, method, service, ctx, modelM
|
|
|
6543
6577
|
lines.push(" }");
|
|
6544
6578
|
return true;
|
|
6545
6579
|
}
|
|
6546
|
-
|
|
6580
|
+
{
|
|
6581
|
+
lines.push(` async ${method}(${renderOptionsParam(optionParam)}): Promise<void> {`);
|
|
6582
|
+
renderOptionsObjectDestructure(lines, pathBindings);
|
|
6583
|
+
const emptyBodyArg = httpMethodNeedsBody(op.httpMethod) ? ", {}" : "";
|
|
6584
|
+
lines.push(` await this.workos.${op.httpMethod}(${pathStr}${emptyBodyArg}${queryOptionsArg});`);
|
|
6585
|
+
lines.push(" }");
|
|
6586
|
+
return true;
|
|
6587
|
+
}
|
|
6547
6588
|
}
|
|
6548
6589
|
function renderOptionsObjectDestructure(lines, pathBindings, restName) {
|
|
6549
6590
|
if (pathBindings.length > 0 && restName) lines.push(` const { ${pathBindings.join(", ")}, ...${restName} } = options;`);
|
|
@@ -6563,7 +6604,8 @@ function bodyArgExprWithParam(bodyExpr, paramName) {
|
|
|
6563
6604
|
return paramName === "payload" ? bodyExpr : bodyExpr.replace(/\bpayload\b/g, paramName);
|
|
6564
6605
|
}
|
|
6565
6606
|
function buildOptionsObjectPathBindings(op, optionType, ctx) {
|
|
6566
|
-
|
|
6607
|
+
const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
6608
|
+
return op.pathParams.map((param) => resolveOptionsObjectField$1(fieldName$6(param.name), optionType, ctx, pathFieldMap));
|
|
6567
6609
|
}
|
|
6568
6610
|
/**
|
|
6569
6611
|
* Map spec path-param names (e.g. `omId`) to the SDK field name exposed on
|
|
@@ -6574,14 +6616,17 @@ function buildOptionsObjectPathBindings(op, optionType, ctx) {
|
|
|
6574
6616
|
*/
|
|
6575
6617
|
function buildOptionsObjectPathParamMap(op, optionType, ctx) {
|
|
6576
6618
|
const map = /* @__PURE__ */ new Map();
|
|
6619
|
+
const pathFieldMap = operationOverrideFor$1(ctx, op)?.pathFieldMap;
|
|
6577
6620
|
for (const param of op.pathParams) {
|
|
6578
6621
|
const localName = fieldName$6(param.name);
|
|
6579
|
-
const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx);
|
|
6622
|
+
const sdkField = resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap);
|
|
6580
6623
|
if (sdkField !== localName) map.set(param.name, sdkField);
|
|
6581
6624
|
}
|
|
6582
6625
|
return map;
|
|
6583
6626
|
}
|
|
6584
|
-
function resolveOptionsObjectField$1(localName, optionType, ctx) {
|
|
6627
|
+
function resolveOptionsObjectField$1(localName, optionType, ctx, pathFieldMap) {
|
|
6628
|
+
const mapped = pathFieldMap?.[localName];
|
|
6629
|
+
if (mapped) return mapped;
|
|
6585
6630
|
const fields = ctx.apiSurface?.interfaces?.[optionType]?.fields;
|
|
6586
6631
|
if (!fields) return localName;
|
|
6587
6632
|
if (fields[localName]) return localName;
|
|
@@ -7537,7 +7582,7 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7537
7582
|
else {
|
|
7538
7583
|
lines.push(`export interface ${domainName}${typeParams} {`);
|
|
7539
7584
|
for (const field of model.fields) {
|
|
7540
|
-
const domainFieldName = fieldName$6(field.name);
|
|
7585
|
+
const domainFieldName = fieldName$6(field.domainName ?? field.name);
|
|
7541
7586
|
if (seenDomainFields.has(domainFieldName)) continue;
|
|
7542
7587
|
seenDomainFields.add(domainFieldName);
|
|
7543
7588
|
if (field.description || field.deprecated || field.readOnly || field.writeOnly || field.default !== void 0) {
|
|
@@ -7554,7 +7599,10 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7554
7599
|
const responseBaselineField = baselineResponse?.fields?.[domainWireField];
|
|
7555
7600
|
const domainResponseOptionalMismatch = baselineField && !baselineField.optional && responseBaselineField && responseBaselineField.optional;
|
|
7556
7601
|
const readonlyPrefix = field.readOnly ? "readonly " : "";
|
|
7557
|
-
if (
|
|
7602
|
+
if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
|
|
7603
|
+
const opt = baselineField.optional ? "?" : "";
|
|
7604
|
+
lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
|
|
7605
|
+
} else if (baselineField && !domainResponseOptionalMismatch && !hasDateTimeConversion(field.type) && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
|
|
7558
7606
|
const opt = baselineField.optional ? "?" : "";
|
|
7559
7607
|
lines.push(` ${readonlyPrefix}${domainFieldName}${opt}: ${baselineField.type};`);
|
|
7560
7608
|
} else {
|
|
@@ -7577,7 +7625,10 @@ function generateModels$7(models, ctx, shared) {
|
|
|
7577
7625
|
if (seenWireFields.has(wireField)) continue;
|
|
7578
7626
|
seenWireFields.add(wireField);
|
|
7579
7627
|
const baselineField = baselineResponse?.fields?.[wireField];
|
|
7580
|
-
if (
|
|
7628
|
+
if (genericDefaults.has(model.name) && baselineField && typeReferencesUnresolvable(baselineField.type, unresolvableNames)) {
|
|
7629
|
+
const opt = baselineField.optional ? "?" : "";
|
|
7630
|
+
lines.push(` ${wireField}${opt}: ${substituteGenericParam(baselineField.type, unresolvableNames)};`);
|
|
7631
|
+
} else if (baselineField && baselineTypeResolvable(baselineField.type, importableNames) && baselineFieldCompatible(baselineField, field)) {
|
|
7581
7632
|
const opt = baselineField.optional ? "?" : "";
|
|
7582
7633
|
lines.push(` ${wireField}${opt}: ${baselineField.type};`);
|
|
7583
7634
|
} else {
|
|
@@ -8020,6 +8071,19 @@ function hasSpecificIRType(ref) {
|
|
|
8020
8071
|
default: return false;
|
|
8021
8072
|
}
|
|
8022
8073
|
}
|
|
8074
|
+
/** True when a baseline field type references one of the model's generic
|
|
8075
|
+
* param identifiers (collected as `unresolvableNames` — bare type names that
|
|
8076
|
+
* resolve to nothing importable, which for a generic model are its params). */
|
|
8077
|
+
function typeReferencesUnresolvable(type, unresolvable) {
|
|
8078
|
+
if (unresolvable.size === 0) return false;
|
|
8079
|
+
const names = type.match(/\b[A-Z][a-zA-Z0-9]*\b/g);
|
|
8080
|
+
return !!names && names.some((n) => unresolvable.has(n));
|
|
8081
|
+
}
|
|
8082
|
+
/** Rewrite a baseline field type so references to the model's (renamed)
|
|
8083
|
+
* generic param resolve to the emitted `GenericType` param. */
|
|
8084
|
+
function substituteGenericParam(type, unresolvable) {
|
|
8085
|
+
return type.replace(/\b[A-Z][a-zA-Z0-9]*\b/g, (name) => unresolvable.has(name) ? "GenericType" : name);
|
|
8086
|
+
}
|
|
8023
8087
|
function renderTypeParams(model, genericDefaults) {
|
|
8024
8088
|
if (!model.typeParams?.length) {
|
|
8025
8089
|
if (genericDefaults?.has(model.name)) return "<GenericType extends Record<string, unknown> = Record<string, unknown>>";
|
|
@@ -8546,10 +8610,11 @@ function optionsObjectParam(method) {
|
|
|
8546
8610
|
const [param] = method.params;
|
|
8547
8611
|
if (param.name !== "options") return void 0;
|
|
8548
8612
|
if (param.passingStyle && param.passingStyle !== "options_object") return void 0;
|
|
8549
|
-
|
|
8613
|
+
const type = param.type?.replace(/(?:\s*\|\s*(?:undefined|null))+\s*$/, "").trim();
|
|
8614
|
+
if (!type || /^(Record|object|any|unknown)\b/.test(type)) return void 0;
|
|
8550
8615
|
return {
|
|
8551
8616
|
name: param.name,
|
|
8552
|
-
type
|
|
8617
|
+
type
|
|
8553
8618
|
};
|
|
8554
8619
|
}
|
|
8555
8620
|
function configuredOptionsMethod(ctx, op) {
|
|
@@ -8614,6 +8679,7 @@ function generateTests$7(spec, ctx) {
|
|
|
8614
8679
|
};
|
|
8615
8680
|
const ops = isNodeOwnedService(ctx, mountName, resolveResourceClassName$3(mergedService, ctx)) ? operations : uncoveredOperations(mergedService, ctx);
|
|
8616
8681
|
if (ops.length === 0) continue;
|
|
8682
|
+
const ignoredMethodNames = ignoredResourceMethodNames(ctx, `src/${resolveResourceDir(mergedService, ctx)}/${fileName$3(resolveResourceClassName$3(mergedService, ctx))}.ts`);
|
|
8617
8683
|
const propName = mountAccessors.get(mountName) ?? servicePropertyName$4(mountName);
|
|
8618
8684
|
if (ctx.apiSurface && baselineWorkOSProps.size > 0 && !baselineWorkOSProps.has(propName)) continue;
|
|
8619
8685
|
if (resolveResourceClassName$3(mergedService, ctx) !== mountName) continue;
|
|
@@ -8621,23 +8687,31 @@ function generateTests$7(spec, ctx) {
|
|
|
8621
8687
|
...mergedService,
|
|
8622
8688
|
operations: ops
|
|
8623
8689
|
} : mergedService;
|
|
8624
|
-
files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors));
|
|
8690
|
+
files.push(generateServiceTest$3(testService, spec, ctx, modelMap, mountAccessors, ignoredMethodNames));
|
|
8625
8691
|
}
|
|
8626
8692
|
const serializerTests = generateSerializerTests(spec, ctx);
|
|
8627
8693
|
for (const f of serializerTests) files.push(f);
|
|
8628
8694
|
return files;
|
|
8629
8695
|
}
|
|
8630
|
-
function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
8696
|
+
function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors, ignoredMethodNames = /* @__PURE__ */ new Set()) {
|
|
8631
8697
|
const resolvedName = resolveResourceClassName$3(service, ctx);
|
|
8632
8698
|
const serviceDir = resolveResourceDir(service, ctx);
|
|
8633
8699
|
const serviceClass = resolvedName;
|
|
8634
8700
|
const serviceProp = mountAccessors?.get(service.name) ?? servicePropertyName$4(resolveServiceName(service, ctx));
|
|
8635
8701
|
const testPath = `src/${serviceDir}/${fileName$3(resolvedName)}.spec.ts`;
|
|
8636
|
-
const
|
|
8702
|
+
const allPlans = service.operations.map((op) => ({
|
|
8637
8703
|
op,
|
|
8638
|
-
plan:
|
|
8704
|
+
plan: planOperationFor(op, ctx),
|
|
8639
8705
|
method: resolveMethodName$6(op, service, ctx)
|
|
8640
8706
|
}));
|
|
8707
|
+
const resolvedLookup = buildResolvedLookup(ctx);
|
|
8708
|
+
const preservedScopes = methodsWithPreservedTestBlocks(ctx, testPath);
|
|
8709
|
+
const plans = allPlans.filter((p) => {
|
|
8710
|
+
if (preservedScopes.has(p.method)) return true;
|
|
8711
|
+
if (ignoredMethodNames.has(p.method)) return false;
|
|
8712
|
+
if (lookupResolved(p.op, resolvedLookup)?.urlBuilder) return false;
|
|
8713
|
+
return true;
|
|
8714
|
+
});
|
|
8641
8715
|
if (ctx.overlayLookup?.methodByOperation) {
|
|
8642
8716
|
const methodOrder = /* @__PURE__ */ new Map();
|
|
8643
8717
|
let pos = 0;
|
|
@@ -8684,7 +8758,7 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
|
8684
8758
|
lines.push("");
|
|
8685
8759
|
lines.push("const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');");
|
|
8686
8760
|
lines.push("");
|
|
8687
|
-
const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(plans, modelMap, ctx);
|
|
8761
|
+
const { lines: helperLines, helpers: entityHelperNames } = generateEntityHelpers(service, allPlans, plans, existingTestIgnoreText(ctx, testPath), modelMap, ctx);
|
|
8688
8762
|
for (const line of helperLines) lines.push(line);
|
|
8689
8763
|
lines.push(`describe('${serviceClass}', () => {`);
|
|
8690
8764
|
lines.push(" beforeEach(() => fetch.resetMocks());");
|
|
@@ -8700,6 +8774,20 @@ function generateServiceTest$3(service, spec, ctx, modelMap, mountAccessors) {
|
|
|
8700
8774
|
lines.push(" });");
|
|
8701
8775
|
}
|
|
8702
8776
|
lines.push("});");
|
|
8777
|
+
const body = lines.join("\n");
|
|
8778
|
+
const enumImportLines = [];
|
|
8779
|
+
for (const [name, info] of Object.entries(ctx.apiSurface?.enums ?? {})) {
|
|
8780
|
+
if (!new RegExp(`\\b${name}\\.[A-Za-z_$]`).test(body)) continue;
|
|
8781
|
+
if (new RegExp(`import\\b[^;]*\\b${name}\\b[^;]*from`).test(body)) continue;
|
|
8782
|
+
const sourceFile = info.sourceFile;
|
|
8783
|
+
const spec = sourceFile ? relativeImport(testPath, sourceFile).replace(/\.ts$/, "") : "./interfaces";
|
|
8784
|
+
enumImportLines.push(`import { ${name} } from '${spec}';`);
|
|
8785
|
+
}
|
|
8786
|
+
if (enumImportLines.length > 0) {
|
|
8787
|
+
const anchor = lines.indexOf("import { WorkOS } from '../workos';");
|
|
8788
|
+
const at = anchor >= 0 ? anchor + 1 : 0;
|
|
8789
|
+
lines.splice(at, 0, ...enumImportLines);
|
|
8790
|
+
}
|
|
8703
8791
|
return {
|
|
8704
8792
|
path: testPath,
|
|
8705
8793
|
content: lines.join("\n"),
|
|
@@ -8718,11 +8806,81 @@ function pathParamTestValue(param, paramName) {
|
|
|
8718
8806
|
if (name) return `test_${fieldName$6(name)}`;
|
|
8719
8807
|
return "test_id";
|
|
8720
8808
|
}
|
|
8721
|
-
|
|
8809
|
+
/** Render an example value as a valid TS literal expression.
|
|
8810
|
+
* Objects and nested arrays go through JSON.stringify so map-typed params
|
|
8811
|
+
* (e.g. providerQueryParams) don't coerce to the literal text `[object Object]`.
|
|
8812
|
+
*/
|
|
8813
|
+
function renderExampleLiteral(value) {
|
|
8814
|
+
if (typeof value === "string") return `'${value}'`;
|
|
8815
|
+
if (Array.isArray(value)) return `[${value.map(renderExampleLiteral).join(", ")}]`;
|
|
8816
|
+
if (value !== null && typeof value === "object") return JSON.stringify(value);
|
|
8817
|
+
return String(value);
|
|
8818
|
+
}
|
|
8819
|
+
/** Resolve the enum members a query-param test value must satisfy. An enum is
|
|
8820
|
+
* expressed several ways: an inline `EnumRef` with `values`, an `enum`/`model`
|
|
8821
|
+
* ref by name, or — when the IR flattened the param to a bare string but the
|
|
8822
|
+
* hand-written options interface still types the field as an enum — the
|
|
8823
|
+
* baseline options field type (e.g. `ConnectionType`). */
|
|
8824
|
+
/** All valid values for an enum named `name`. The extracted SDK surface is
|
|
8825
|
+
* consulted first: when an options field is typed with a hand-written enum
|
|
8826
|
+
* (e.g. `ConnectionType` from `connection-type.enum.ts`), that enum — not a
|
|
8827
|
+
* same-named IR enum that may carry extra members like `Pending` — is what
|
|
8828
|
+
* the generated test must type-check against. Falls back to the IR spec. */
|
|
8829
|
+
function enumValuesByName(name, ctx) {
|
|
8830
|
+
const members = ctx?.apiSurface?.enums?.[name]?.members;
|
|
8831
|
+
if (members && Object.keys(members).length > 0) return Object.values(members);
|
|
8832
|
+
const specValues = ctx?.spec.enums.find((e) => e.name === name)?.values;
|
|
8833
|
+
if (specValues?.length) return specValues.map((v) => v.value);
|
|
8834
|
+
}
|
|
8835
|
+
function resolveParamEnumValues(param, optionFieldType, ctx) {
|
|
8836
|
+
if (optionFieldType) {
|
|
8837
|
+
const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
|
|
8838
|
+
if (/^[A-Za-z_$][\w$]*$/.test(bare)) {
|
|
8839
|
+
const values = enumValuesByName(bare, ctx);
|
|
8840
|
+
if (values) return values;
|
|
8841
|
+
}
|
|
8842
|
+
}
|
|
8843
|
+
if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) {
|
|
8844
|
+
const values = enumValuesByName(param.type.name, ctx);
|
|
8845
|
+
if (values) return values;
|
|
8846
|
+
}
|
|
8847
|
+
if (param.type.kind === "enum" && param.type.values?.length) return param.type.values;
|
|
8848
|
+
}
|
|
8849
|
+
/** When an options field is typed with a real (nominal) TS `enum` from the SDK
|
|
8850
|
+
* surface, a string literal won't type-check — the value must be a member
|
|
8851
|
+
* reference (`ConnectionType.ADFSSAML`). Returns the enum's name + members so
|
|
8852
|
+
* the caller can both render the reference and import the right declaration. */
|
|
8853
|
+
function resolveRealEnum(param, optionFieldType, ctx) {
|
|
8854
|
+
const candidates = [];
|
|
8855
|
+
if (optionFieldType) {
|
|
8856
|
+
const bare = optionFieldType.replace(/\[\]/g, "").replace(/\|\s*(undefined|null)/g, "").trim();
|
|
8857
|
+
if (/^[A-Za-z_$][\w$]*$/.test(bare)) candidates.push(bare);
|
|
8858
|
+
}
|
|
8859
|
+
if ((param.type.kind === "enum" || param.type.kind === "model") && param.type.name) candidates.push(param.type.name);
|
|
8860
|
+
for (const name of candidates) {
|
|
8861
|
+
const members = ctx?.apiSurface?.enums?.[name]?.members;
|
|
8862
|
+
if (members && Object.keys(members).length > 0) return {
|
|
8863
|
+
name,
|
|
8864
|
+
members
|
|
8865
|
+
};
|
|
8866
|
+
}
|
|
8867
|
+
return null;
|
|
8868
|
+
}
|
|
8869
|
+
function queryParamTestValue(param, modelMap, ctx, optionFieldType) {
|
|
8870
|
+
const realEnum = resolveRealEnum(param, optionFieldType, ctx);
|
|
8871
|
+
if (realEnum) {
|
|
8872
|
+
const entries = Object.entries(realEnum.members);
|
|
8873
|
+
const [memberKey] = (typeof param.example === "string" ? entries.find(([, v]) => v === param.example) : void 0) ?? entries[0];
|
|
8874
|
+
return `${realEnum.name}.${memberKey}`;
|
|
8875
|
+
}
|
|
8876
|
+
const enumValues = resolveParamEnumValues(param, optionFieldType, ctx);
|
|
8877
|
+
if (enumValues?.length) {
|
|
8878
|
+
const valid = typeof param.example === "string" && enumValues.includes(param.example) ? param.example : enumValues[0];
|
|
8879
|
+
return typeof valid === "string" ? `'${valid}'` : String(valid);
|
|
8880
|
+
}
|
|
8722
8881
|
if (param.example !== void 0) {
|
|
8723
|
-
if (Array.isArray(param.example)) return `[${param.example.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(", ")}]`;
|
|
8724
8882
|
if (param.type.kind === "primitive" && param.type.format === "date-time" && typeof param.example === "string") return `new Date('${param.example}')`;
|
|
8725
|
-
return
|
|
8883
|
+
return renderExampleLiteral(param.example);
|
|
8726
8884
|
}
|
|
8727
8885
|
return fixtureValueForType(param.type, param.name, "Options", modelMap) ?? "'test'";
|
|
8728
8886
|
}
|
|
@@ -8888,9 +9046,10 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
8888
9046
|
const optionParam = optionsObjectParam(baselineMethod);
|
|
8889
9047
|
if (!optionParam) return null;
|
|
8890
9048
|
const entries = [];
|
|
9049
|
+
const pathFieldMap = ctx ? operationOverrideFor(ctx, op)?.pathFieldMap : void 0;
|
|
8891
9050
|
for (const param of op.pathParams) {
|
|
8892
9051
|
const localName = fieldName$6(param.name);
|
|
8893
|
-
const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx);
|
|
9052
|
+
const optionField = resolveOptionsObjectField(localName, optionParam.type, ctx, pathFieldMap);
|
|
8894
9053
|
entries.push(`${optionField}: ${JSON.stringify(pathParamTestValue(param, localName))}`);
|
|
8895
9054
|
}
|
|
8896
9055
|
if (plan.isPaginated) entries.push("order: 'desc'");
|
|
@@ -8902,7 +9061,8 @@ function buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx) {
|
|
|
8902
9061
|
].includes(param.name)) : op.queryParams).filter((param) => !param.deprecated);
|
|
8903
9062
|
for (const param of queryParams) {
|
|
8904
9063
|
const localName = fieldName$6(param.name);
|
|
8905
|
-
const
|
|
9064
|
+
const optionFieldType = ctx?.apiSurface?.interfaces?.[optionParam.type]?.fields?.[localName]?.type;
|
|
9065
|
+
const value = queryParamTestValue(param, modelMap, ctx, optionFieldType);
|
|
8906
9066
|
entries.push(`${localName}: ${value}`);
|
|
8907
9067
|
}
|
|
8908
9068
|
if (plan.hasBody) {
|
|
@@ -8930,7 +9090,9 @@ function objectLiteralEntries(literal) {
|
|
|
8930
9090
|
const body = trimmed.slice(1, -1).trim();
|
|
8931
9091
|
return body ? body.split(",").map((entry) => entry.trim()) : [];
|
|
8932
9092
|
}
|
|
8933
|
-
function resolveOptionsObjectField(localName, optionType, ctx) {
|
|
9093
|
+
function resolveOptionsObjectField(localName, optionType, ctx, pathFieldMap) {
|
|
9094
|
+
const mapped = pathFieldMap?.[localName];
|
|
9095
|
+
if (mapped) return mapped;
|
|
8934
9096
|
const fields = ctx?.apiSurface?.interfaces?.[optionType]?.fields;
|
|
8935
9097
|
if (!fields) return localName;
|
|
8936
9098
|
if (fields[localName]) return localName;
|
|
@@ -8945,20 +9107,85 @@ function resolveOptionsObjectField(localName, optionType, ctx) {
|
|
|
8945
9107
|
* Generate per-entity assertion helper functions for models used in 2+ tests.
|
|
8946
9108
|
* Returns { lines, helpers } where helpers is a Set of helper function names.
|
|
8947
9109
|
*/
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
9110
|
+
/** Describe-scope names (i.e. method names) that have an `@oagen-ignore` block
|
|
9111
|
+
* nested inside them in the existing test file. Such a `describe` must keep
|
|
9112
|
+
* being emitted even when the method is hand-owned — otherwise the engine has
|
|
9113
|
+
* no `describe` to re-nest the preserved block under and orphans it to the top
|
|
9114
|
+
* of the file (out of `beforeEach` scope), which hangs at runtime. Mirrors the
|
|
9115
|
+
* engine's `findContainingDescribeScope`. */
|
|
9116
|
+
function methodsWithPreservedTestBlocks(ctx, relPath) {
|
|
9117
|
+
const scopes = /* @__PURE__ */ new Set();
|
|
9118
|
+
const root = ctx.outputDir ?? ctx.targetDir;
|
|
9119
|
+
if (!root) return scopes;
|
|
9120
|
+
let content;
|
|
9121
|
+
try {
|
|
9122
|
+
content = fs.readFileSync(path.join(root, relPath), "utf8");
|
|
9123
|
+
} catch {
|
|
9124
|
+
return scopes;
|
|
9125
|
+
}
|
|
9126
|
+
const lines = content.split("\n");
|
|
9127
|
+
const indentOf = (l) => l.length - l.trimStart().length;
|
|
9128
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9129
|
+
if (!lines[i].includes("@oagen-ignore-start")) continue;
|
|
9130
|
+
if (/^\S/.test(lines[i])) continue;
|
|
9131
|
+
const markerIndent = indentOf(lines[i]);
|
|
9132
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
9133
|
+
if (indentOf(lines[j]) >= markerIndent) continue;
|
|
9134
|
+
const m = lines[j].match(/^\s*describe\((['"`])(.+?)\1\s*,/);
|
|
9135
|
+
if (m) {
|
|
9136
|
+
scopes.add(m[2]);
|
|
9137
|
+
break;
|
|
9138
|
+
}
|
|
9139
|
+
if (/^\s*(?:export\s+)?class\s+\w+/.test(lines[j])) break;
|
|
9140
|
+
}
|
|
9141
|
+
}
|
|
9142
|
+
return scopes;
|
|
9143
|
+
}
|
|
9144
|
+
/** Concatenated text of the existing test file's `@oagen-ignore` regions, so
|
|
9145
|
+
* helper generation can see which `expect<Model>()` helpers preserved
|
|
9146
|
+
* hand-written test blocks still reference. */
|
|
9147
|
+
function existingTestIgnoreText(ctx, relPath) {
|
|
9148
|
+
const root = ctx.outputDir ?? ctx.targetDir;
|
|
9149
|
+
if (!root) return "";
|
|
9150
|
+
let content;
|
|
9151
|
+
try {
|
|
9152
|
+
content = fs.readFileSync(path.join(root, relPath), "utf8");
|
|
9153
|
+
} catch {
|
|
9154
|
+
return "";
|
|
9155
|
+
}
|
|
9156
|
+
return [...content.matchAll(/@oagen-ignore-start[\s\S]*?@oagen-ignore-end/g)].map((m) => m[0]).join("\n");
|
|
9157
|
+
}
|
|
9158
|
+
function generateEntityHelpers(service, allPlans, renderedPlans, ignoreRegionText, modelMap, ctx) {
|
|
9159
|
+
const responseModelOf = (entry) => {
|
|
9160
|
+
const { op, plan } = entry;
|
|
8952
9161
|
if (plan.isPaginated && op.pagination?.itemType.kind === "model") {
|
|
8953
|
-
modelName = op.pagination.itemType.name;
|
|
9162
|
+
let modelName = op.pagination.itemType.name;
|
|
8954
9163
|
const rawModel = modelMap.get(modelName);
|
|
8955
9164
|
if (rawModel) {
|
|
8956
9165
|
const unwrapped = unwrapListModel$4(rawModel, modelMap);
|
|
8957
9166
|
if (unwrapped) modelName = unwrapped.name;
|
|
8958
9167
|
}
|
|
8959
|
-
|
|
9168
|
+
return modelName;
|
|
9169
|
+
}
|
|
9170
|
+
return plan.responseModelName ?? null;
|
|
9171
|
+
};
|
|
9172
|
+
const modelUsage = /* @__PURE__ */ new Map();
|
|
9173
|
+
for (const entry of allPlans) {
|
|
9174
|
+
const modelName = responseModelOf(entry);
|
|
8960
9175
|
if (modelName) modelUsage.set(modelName, (modelUsage.get(modelName) ?? 0) + 1);
|
|
8961
9176
|
}
|
|
9177
|
+
const renderedModels = /* @__PURE__ */ new Set();
|
|
9178
|
+
for (const entry of renderedPlans) {
|
|
9179
|
+
const modelName = responseModelOf(entry);
|
|
9180
|
+
if (!modelName) continue;
|
|
9181
|
+
if (entry.plan.isPaginated && entry.op.pagination?.itemType.kind === "model") {
|
|
9182
|
+
const baselineMethod = optionsMethodFor(service, entry.method, entry.op, entry.plan, ctx);
|
|
9183
|
+
const baselineItemType = autoPaginatableItemType(baselineMethod?.returnType);
|
|
9184
|
+
const generatedItemType = resolveInterfaceName(modelName, ctx);
|
|
9185
|
+
if (Boolean(baselineMethod?.returnType && !baselineItemType) || Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType)) continue;
|
|
9186
|
+
}
|
|
9187
|
+
renderedModels.add(modelName);
|
|
9188
|
+
}
|
|
8962
9189
|
const lines = [];
|
|
8963
9190
|
const helpers = /* @__PURE__ */ new Set();
|
|
8964
9191
|
for (const [modelName, count] of modelUsage) {
|
|
@@ -8969,6 +9196,7 @@ function generateEntityHelpers(plans, modelMap, ctx) {
|
|
|
8969
9196
|
if (assertions.length === 0) continue;
|
|
8970
9197
|
const helperName = `expect${resolveInterfaceName(modelName, ctx)}`;
|
|
8971
9198
|
if (helpers.has(helperName)) continue;
|
|
9199
|
+
if (!(renderedModels.has(modelName) || ignoreRegionText.includes(`${helperName}(`))) continue;
|
|
8972
9200
|
helpers.add(helperName);
|
|
8973
9201
|
lines.push(`function ${helperName}(result: any) {`);
|
|
8974
9202
|
for (const assertion of assertions) lines.push(` ${assertion}`);
|
|
@@ -8998,7 +9226,7 @@ function buildFieldAssertions(model, accessor, modelMap) {
|
|
|
8998
9226
|
const assertions = [];
|
|
8999
9227
|
for (const field of model.fields) {
|
|
9000
9228
|
if (!field.required) continue;
|
|
9001
|
-
const domainField = fieldName$6(field.name);
|
|
9229
|
+
const domainField = fieldName$6(field.domainName ?? field.name);
|
|
9002
9230
|
const isDateTime = isDateTimeFieldType(field.type);
|
|
9003
9231
|
const fieldAccessor = isDateTime ? `${accessor}.${domainField}.toISOString()` : `${accessor}.${domainField}`;
|
|
9004
9232
|
if (field.example !== void 0) {
|
|
@@ -10064,6 +10292,21 @@ function withNodeOperationOverrides(ctx) {
|
|
|
10064
10292
|
return next;
|
|
10065
10293
|
}
|
|
10066
10294
|
//#endregion
|
|
10295
|
+
//#region src/shared/file-header.ts
|
|
10296
|
+
/**
|
|
10297
|
+
* Canonical "do not edit" banner text shared by every emitter's
|
|
10298
|
+
* `fileHeader()`. The text is comment-syntax agnostic — each emitter prefixes
|
|
10299
|
+
* it with the appropriate comment marker for its language (`//`, `#`, etc.).
|
|
10300
|
+
*
|
|
10301
|
+
* Keeping this in one place prevents the wording from drifting between
|
|
10302
|
+
* languages (Rust previously emitted a different banner).
|
|
10303
|
+
*
|
|
10304
|
+
* Go intentionally does NOT use this constant: its header must match the
|
|
10305
|
+
* standard `^// Code generated .* DO NOT EDIT\.$` marker that Go tooling
|
|
10306
|
+
* relies on to recognize generated files. See `src/go/index.ts`.
|
|
10307
|
+
*/
|
|
10308
|
+
const AUTOGEN_NOTICE = "This file is auto-generated by oagen. Do not edit.";
|
|
10309
|
+
//#endregion
|
|
10067
10310
|
//#region src/node/index.ts
|
|
10068
10311
|
/**
|
|
10069
10312
|
* Cache live-surface per ctx — every emitter method receives the same ctx in
|
|
@@ -10609,7 +10852,7 @@ const nodeEmitter = {
|
|
|
10609
10852
|
return {};
|
|
10610
10853
|
},
|
|
10611
10854
|
fileHeader() {
|
|
10612
|
-
return
|
|
10855
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
10613
10856
|
},
|
|
10614
10857
|
formatCommand(targetDir) {
|
|
10615
10858
|
const hasPrettier = fs$1.existsSync(path$1.join(targetDir, ".prettierrc"));
|
|
@@ -10677,6 +10920,15 @@ function fieldName$5(name) {
|
|
|
10677
10920
|
return toSnakeCase(name);
|
|
10678
10921
|
}
|
|
10679
10922
|
/**
|
|
10923
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
10924
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
10925
|
+
* a friendlier name. The wire name (still derived from `field.name`) is
|
|
10926
|
+
* unaffected, so the API contract is preserved.
|
|
10927
|
+
*/
|
|
10928
|
+
function domainFieldName$5(field) {
|
|
10929
|
+
return toSnakeCase(field.domainName ?? field.name);
|
|
10930
|
+
}
|
|
10931
|
+
/**
|
|
10680
10932
|
* Python builtins that should not be shadowed by parameter names.
|
|
10681
10933
|
* When a path/query param name collides, suffix with underscore.
|
|
10682
10934
|
*/
|
|
@@ -11574,7 +11826,7 @@ function generateModels$6(models, ctx) {
|
|
|
11574
11826
|
}
|
|
11575
11827
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
11576
11828
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
11577
|
-
const pyName =
|
|
11829
|
+
const pyName = domainFieldName$5(f);
|
|
11578
11830
|
if (seenFieldNames.has(pyName)) return false;
|
|
11579
11831
|
seenFieldNames.add(pyName);
|
|
11580
11832
|
return true;
|
|
@@ -11636,7 +11888,7 @@ function generateModels$6(models, ctx) {
|
|
|
11636
11888
|
return typeStr;
|
|
11637
11889
|
};
|
|
11638
11890
|
for (const field of requiredFields) {
|
|
11639
|
-
const pyFieldName =
|
|
11891
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11640
11892
|
const pyType = rewriteDiscriminatorType(resolveModelFieldType(field.type));
|
|
11641
11893
|
if (field.description || field.deprecated) {
|
|
11642
11894
|
const parts = [];
|
|
@@ -11647,7 +11899,7 @@ function generateModels$6(models, ctx) {
|
|
|
11647
11899
|
} else lines.push(` ${pyFieldName}: ${pyType}`);
|
|
11648
11900
|
}
|
|
11649
11901
|
for (const field of optionalFields) {
|
|
11650
|
-
const pyFieldName =
|
|
11902
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11651
11903
|
const pyType = `Optional[${rewriteDiscriminatorType(field.type.kind === "nullable" ? resolveModelFieldType(field.type.inner) : resolveModelFieldType(field.type))}]`;
|
|
11652
11904
|
if (field.description || field.deprecated) {
|
|
11653
11905
|
const parts = [];
|
|
@@ -11665,7 +11917,7 @@ function generateModels$6(models, ctx) {
|
|
|
11665
11917
|
const preludeLines = [];
|
|
11666
11918
|
const fieldAssignmentLines = [];
|
|
11667
11919
|
for (const field of [...requiredFields, ...optionalFields]) {
|
|
11668
|
-
const pyFieldName =
|
|
11920
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11669
11921
|
const wireKey = field.name;
|
|
11670
11922
|
const isRequired = !isOptionalField(model.name, field, ctx);
|
|
11671
11923
|
const discPrelude = renderDiscriminatedUnionPrelude(field, pyFieldName, wireKey, modelClassName, isRequired);
|
|
@@ -11693,7 +11945,7 @@ function generateModels$6(models, ctx) {
|
|
|
11693
11945
|
lines.push(" \"\"\"Serialize to a dictionary.\"\"\"");
|
|
11694
11946
|
lines.push(" result: Dict[str, Any] = {}");
|
|
11695
11947
|
for (const field of [...requiredFields, ...optionalFields]) {
|
|
11696
|
-
const pyFieldName =
|
|
11948
|
+
const pyFieldName = domainFieldName$5(field);
|
|
11697
11949
|
const wireKey = field.name;
|
|
11698
11950
|
const isRequired = !isOptionalField(model.name, field, ctx);
|
|
11699
11951
|
const isNullable = field.type.kind === "nullable";
|
|
@@ -13569,7 +13821,7 @@ function generateModelFixture$4(model, modelMap, enumMap) {
|
|
|
13569
13821
|
const fixture = {};
|
|
13570
13822
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
13571
13823
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
13572
|
-
const pyName =
|
|
13824
|
+
const pyName = domainFieldName$5(f);
|
|
13573
13825
|
if (seenFieldNames.has(pyName)) return false;
|
|
13574
13826
|
seenFieldNames.add(pyName);
|
|
13575
13827
|
return true;
|
|
@@ -14365,16 +14617,16 @@ function pickAssertableFields(modelName, spec, maxFields = 2) {
|
|
|
14365
14617
|
if (typeof val === "string") {
|
|
14366
14618
|
if (val.includes("\"") || val.includes("'") || val.includes("{") || val.includes("\\") || val.includes("\n")) continue;
|
|
14367
14619
|
results.push({
|
|
14368
|
-
field:
|
|
14620
|
+
field: domainFieldName$5(f),
|
|
14369
14621
|
value: `"${val}"`
|
|
14370
14622
|
});
|
|
14371
14623
|
} else if (typeof val === "boolean") results.push({
|
|
14372
|
-
field:
|
|
14624
|
+
field: domainFieldName$5(f),
|
|
14373
14625
|
value: val ? "True" : "False",
|
|
14374
14626
|
isBool: true
|
|
14375
14627
|
});
|
|
14376
14628
|
else if (typeof val === "number") results.push({
|
|
14377
|
-
field:
|
|
14629
|
+
field: domainFieldName$5(f),
|
|
14378
14630
|
value: String(val)
|
|
14379
14631
|
});
|
|
14380
14632
|
}
|
|
@@ -14434,7 +14686,7 @@ function buildTestArgs$1(op, spec, hiddenParams) {
|
|
|
14434
14686
|
].includes(param.name)) continue;
|
|
14435
14687
|
if (plan.hasBody && op.requestBody?.kind === "model") {
|
|
14436
14688
|
const rbName = op.requestBody.name;
|
|
14437
|
-
if (spec.models.find((m) => m.name === rbName)?.fields.some((f) =>
|
|
14689
|
+
if (spec.models.find((m) => m.name === rbName)?.fields.some((f) => domainFieldName$5(f) === fieldName$5(param.name))) continue;
|
|
14438
14690
|
}
|
|
14439
14691
|
if (param.required && !pathParamNames.has(fieldName$5(param.name))) args.push(`${fieldName$5(param.name)}=${generateTestValue$1(param.type, param.name)}`);
|
|
14440
14692
|
}
|
|
@@ -14693,7 +14945,7 @@ function generateModelRoundTripTests(spec, ctx) {
|
|
|
14693
14945
|
if (model.discriminator) continue;
|
|
14694
14946
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
14695
14947
|
const dedupFields = model.fields.filter((f) => {
|
|
14696
|
-
const pyName =
|
|
14948
|
+
const pyName = domainFieldName$5(f);
|
|
14697
14949
|
if (seenFieldNames.has(pyName)) return false;
|
|
14698
14950
|
seenFieldNames.add(pyName);
|
|
14699
14951
|
return true;
|
|
@@ -14885,7 +15137,7 @@ const pythonEmitter = {
|
|
|
14885
15137
|
return buildOperationsMap$6(spec, ctx);
|
|
14886
15138
|
},
|
|
14887
15139
|
fileHeader() {
|
|
14888
|
-
return
|
|
15140
|
+
return `# ${AUTOGEN_NOTICE}`;
|
|
14889
15141
|
},
|
|
14890
15142
|
formatCommand(_targetDir) {
|
|
14891
15143
|
return {
|
|
@@ -14986,6 +15238,15 @@ function resolveMethodName$4(op, _service, ctx) {
|
|
|
14986
15238
|
function fieldName$4(name) {
|
|
14987
15239
|
return toCamelCase(name);
|
|
14988
15240
|
}
|
|
15241
|
+
/**
|
|
15242
|
+
* camelCase DOMAIN property name for a model field, honoring a `domainName`
|
|
15243
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
15244
|
+
* a friendlier PHP property name. The wire key (see {@link wireName}) still
|
|
15245
|
+
* derives from `field.name`. No-op when `domainName` is unset.
|
|
15246
|
+
*/
|
|
15247
|
+
function domainFieldName$4(field) {
|
|
15248
|
+
return fieldName$4(field.domainName ?? field.name);
|
|
15249
|
+
}
|
|
14989
15250
|
/** snake_case name for fixtures and other snake_case contexts. */
|
|
14990
15251
|
function snakeName(name) {
|
|
14991
15252
|
return toSnakeCase(stripUrnPrefix(name));
|
|
@@ -15120,14 +15381,14 @@ function generateModels$5(models, ctx) {
|
|
|
15120
15381
|
const optionalFields = model.fields.filter((f) => !f.required);
|
|
15121
15382
|
const seenNames = /* @__PURE__ */ new Set();
|
|
15122
15383
|
const allFields = [...requiredFields, ...optionalFields].filter((f) => {
|
|
15123
|
-
const phpName =
|
|
15384
|
+
const phpName = domainFieldName$4(f);
|
|
15124
15385
|
if (seenNames.has(phpName)) return false;
|
|
15125
15386
|
seenNames.add(phpName);
|
|
15126
15387
|
return true;
|
|
15127
15388
|
});
|
|
15128
15389
|
for (let i = 0; i < allFields.length; i++) {
|
|
15129
15390
|
const field = allFields[i];
|
|
15130
|
-
const phpName =
|
|
15391
|
+
const phpName = domainFieldName$4(field);
|
|
15131
15392
|
const phpType = mapTypeRef$5(field.type);
|
|
15132
15393
|
const isOptional = !field.required;
|
|
15133
15394
|
const comma = i < allFields.length - 1 ? "," : ",";
|
|
@@ -15154,7 +15415,7 @@ function generateModels$5(models, ctx) {
|
|
|
15154
15415
|
lines.push(` return new self(`);
|
|
15155
15416
|
for (let i = 0; i < allFields.length; i++) {
|
|
15156
15417
|
const field = allFields[i];
|
|
15157
|
-
const phpName =
|
|
15418
|
+
const phpName = domainFieldName$4(field);
|
|
15158
15419
|
const wireName = field.name;
|
|
15159
15420
|
const comma = i < allFields.length - 1 ? "," : ",";
|
|
15160
15421
|
const accessor = generateFromArrayAccessor(field.type, wireName, field.required);
|
|
@@ -15167,7 +15428,7 @@ function generateModels$5(models, ctx) {
|
|
|
15167
15428
|
lines.push(" {");
|
|
15168
15429
|
lines.push(" return [");
|
|
15169
15430
|
for (const field of allFields) {
|
|
15170
|
-
const phpName =
|
|
15431
|
+
const phpName = domainFieldName$4(field);
|
|
15171
15432
|
const wireName = field.name;
|
|
15172
15433
|
const serialized = generateToArrayValue(field.type, `$this->${phpName}`, !field.required);
|
|
15173
15434
|
lines.push(` '${wireName}' => ${serialized},`);
|
|
@@ -16591,7 +16852,7 @@ function emitFieldHydrationAssertions(lines, modelName, resultVar, fixtureVar, c
|
|
|
16591
16852
|
const assertFields = [candidates.find((f) => f.name === "id"), candidates.filter((f) => f.name !== "id")[0]].filter(Boolean);
|
|
16592
16853
|
for (const f of assertFields) {
|
|
16593
16854
|
if (!f) continue;
|
|
16594
|
-
const phpProp =
|
|
16855
|
+
const phpProp = domainFieldName$4(f);
|
|
16595
16856
|
lines.push(` $this->assertSame(${fixtureVar}['${f.name}'], ${resultVar}->${phpProp});`);
|
|
16596
16857
|
}
|
|
16597
16858
|
}
|
|
@@ -16771,7 +17032,7 @@ const phpEmitter = {
|
|
|
16771
17032
|
return buildOperationsMap$5(spec, ctx);
|
|
16772
17033
|
},
|
|
16773
17034
|
fileHeader() {
|
|
16774
|
-
return
|
|
17035
|
+
return `<?php\n\ndeclare(strict_types=1);\n\n// ${AUTOGEN_NOTICE}`;
|
|
16775
17036
|
},
|
|
16776
17037
|
formatCommand(targetDir) {
|
|
16777
17038
|
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 {
|
|
@@ -16834,6 +17095,15 @@ function methodName$3(name) {
|
|
|
16834
17095
|
function fieldName$3(name) {
|
|
16835
17096
|
return applyAcronyms(toPascalCase(name));
|
|
16836
17097
|
}
|
|
17098
|
+
/**
|
|
17099
|
+
* PascalCase domain field name for a model field, honoring a `domainName`
|
|
17100
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
17101
|
+
* a friendlier name. The wire name (the `json:"..."` struct tag) still derives
|
|
17102
|
+
* from `field.name`.
|
|
17103
|
+
*/
|
|
17104
|
+
function domainFieldName$3(field) {
|
|
17105
|
+
return applyAcronyms(toPascalCase(field.domainName ?? field.name));
|
|
17106
|
+
}
|
|
16837
17107
|
/** Lower-camel identifier with Go acronym conventions preserved. */
|
|
16838
17108
|
function unexportedName(name) {
|
|
16839
17109
|
const exported = className$3(name);
|
|
@@ -17082,7 +17352,7 @@ function generateModels$4(models, ctx) {
|
|
|
17082
17352
|
lines.push(`type ${structName} struct {`);
|
|
17083
17353
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
17084
17354
|
for (const field of model.fields) {
|
|
17085
|
-
const goFieldName =
|
|
17355
|
+
const goFieldName = domainFieldName$3(field);
|
|
17086
17356
|
if (seenFieldNames.has(goFieldName)) continue;
|
|
17087
17357
|
seenFieldNames.add(goFieldName);
|
|
17088
17358
|
const goType = !field.required ? makeOptional$2(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
|
|
@@ -17896,7 +18166,7 @@ function generateParamsStruct(mountName, method, op, plan, ctx, resolvedOp) {
|
|
|
17896
18166
|
if (bodyModel) for (const field of bodyModel.fields) {
|
|
17897
18167
|
if (hidden.has(field.name)) continue;
|
|
17898
18168
|
if (groupedParams.has(field.name)) continue;
|
|
17899
|
-
const goField =
|
|
18169
|
+
const goField = domainFieldName$3(field);
|
|
17900
18170
|
if (emittedFields.has(goField)) continue;
|
|
17901
18171
|
emittedFields.add(goField);
|
|
17902
18172
|
const goType = !field.required ? makeOptional$1(mapTypeRef$4(field.type)) : mapTypeRef$4(field.type);
|
|
@@ -18234,7 +18504,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
|
|
|
18234
18504
|
if (hidden.has(field.name)) continue;
|
|
18235
18505
|
if (groupedParamNames.has(field.name)) continue;
|
|
18236
18506
|
if (!field.required) continue;
|
|
18237
|
-
const goField =
|
|
18507
|
+
const goField = domainFieldName$3(field);
|
|
18238
18508
|
const goType = mapTypeRef$4(field.type);
|
|
18239
18509
|
lines.push(`\t${goField} ${goType} \`json:"${field.name}"\``);
|
|
18240
18510
|
}
|
|
@@ -18246,7 +18516,7 @@ function emitHiddenParamsBodyStruct(lines, method, op, ctx, resolvedOp) {
|
|
|
18246
18516
|
if (hidden.has(field.name)) continue;
|
|
18247
18517
|
if (groupedParamNames.has(field.name)) continue;
|
|
18248
18518
|
if (field.required) continue;
|
|
18249
|
-
const goField =
|
|
18519
|
+
const goField = domainFieldName$3(field);
|
|
18250
18520
|
const goType = makeOptional$1(mapTypeRef$4(field.type));
|
|
18251
18521
|
lines.push(`\t${goField} ${goType} \`json:"${field.name},omitempty"\``);
|
|
18252
18522
|
}
|
|
@@ -18267,7 +18537,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
|
|
|
18267
18537
|
if (paramsType && bodyModel) for (const field of bodyModel.fields) {
|
|
18268
18538
|
if (hidden.has(field.name)) continue;
|
|
18269
18539
|
if (!field.required) continue;
|
|
18270
|
-
const goField =
|
|
18540
|
+
const goField = domainFieldName$3(field);
|
|
18271
18541
|
lines.push(`\t\t${goField}: params.${goField},`);
|
|
18272
18542
|
}
|
|
18273
18543
|
lines.push(" }");
|
|
@@ -18278,7 +18548,7 @@ function emitBodyWithHiddenParams(lines, op, pathExpr, plan, ctx, resolvedOp, pa
|
|
|
18278
18548
|
if (paramsType && bodyModel) for (const field of bodyModel.fields) {
|
|
18279
18549
|
if (hidden.has(field.name)) continue;
|
|
18280
18550
|
if (field.required) continue;
|
|
18281
|
-
const goField =
|
|
18551
|
+
const goField = domainFieldName$3(field);
|
|
18282
18552
|
lines.push(`\tbody.${goField} = params.${goField}`);
|
|
18283
18553
|
}
|
|
18284
18554
|
const queryArg = op.queryParams.filter((qp) => !hidden.has(qp.name)).length > 0 ? "params" : "nil";
|
|
@@ -18705,7 +18975,7 @@ function generateModelFixture$2(model, modelMap, enumMap) {
|
|
|
18705
18975
|
const fixture = {};
|
|
18706
18976
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
18707
18977
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
18708
|
-
const goName =
|
|
18978
|
+
const goName = domainFieldName$3(f);
|
|
18709
18979
|
if (seenFieldNames.has(goName)) return false;
|
|
18710
18980
|
seenFieldNames.add(goName);
|
|
18711
18981
|
return true;
|
|
@@ -19445,6 +19715,15 @@ function methodName$2(name) {
|
|
|
19445
19715
|
function fieldName$2(name) {
|
|
19446
19716
|
return toPascalCase(name);
|
|
19447
19717
|
}
|
|
19718
|
+
/**
|
|
19719
|
+
* PascalCase domain property name for a model field, honoring a `domainName`
|
|
19720
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
19721
|
+
* a friendlier C# property name. The wire/serialization key (the
|
|
19722
|
+
* `[JsonPropertyName("...")]` value) still derives from `field.name`.
|
|
19723
|
+
*/
|
|
19724
|
+
function domainFieldName$2(field) {
|
|
19725
|
+
return toPascalCase(field.domainName ?? field.name);
|
|
19726
|
+
}
|
|
19448
19727
|
function trimAsyncSuffix(name) {
|
|
19449
19728
|
return name.endsWith("Async") ? name.slice(0, -5) : name;
|
|
19450
19729
|
}
|
|
@@ -19811,7 +20090,7 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19811
20090
|
const baseClassName = modelClassName(model.name);
|
|
19812
20091
|
const fieldMap = /* @__PURE__ */ new Map();
|
|
19813
20092
|
for (const field of model.fields) {
|
|
19814
|
-
let csName =
|
|
20093
|
+
let csName = domainFieldName$2(field);
|
|
19815
20094
|
if (csName === baseClassName) csName = `${csName}Value`;
|
|
19816
20095
|
fieldMap.set(csName, mapTypeRef$3(field.type));
|
|
19817
20096
|
}
|
|
@@ -19827,7 +20106,9 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19827
20106
|
const fieldTypes = model.fields.map((f) => mapTypeRef$3(f.type));
|
|
19828
20107
|
const needsCollections = fieldTypes.some((t) => t.startsWith("List<") || t.startsWith("Dictionary<"));
|
|
19829
20108
|
const needsSystem = fieldTypes.some((t) => t.includes("DateTimeOffset"));
|
|
19830
|
-
const
|
|
20109
|
+
const hasClassNameCollision = model.fields.some((f) => domainFieldName$2(f) === csClassName);
|
|
20110
|
+
const hasDomainRename = model.fields.some((f) => domainFieldName$2(f) !== fieldName$2(f.name));
|
|
20111
|
+
const needsJsonAttrs = hasClassNameCollision || hasDomainRename || model.fields.some((f) => f.required && isEnumRef(f.type));
|
|
19831
20112
|
lines.push(`namespace ${ctx.namespacePascal}`);
|
|
19832
20113
|
lines.push("{");
|
|
19833
20114
|
if (needsSystem) lines.push(" using System;");
|
|
@@ -19853,9 +20134,10 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19853
20134
|
const dictObjectFields = [];
|
|
19854
20135
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
19855
20136
|
for (const field of model.fields) {
|
|
19856
|
-
let csFieldName =
|
|
20137
|
+
let csFieldName = domainFieldName$2(field);
|
|
19857
20138
|
const collidesWithClassName = csFieldName === csClassName;
|
|
19858
20139
|
if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
|
|
20140
|
+
const hasDomainOverride = domainFieldName$2(field) !== fieldName$2(field.name);
|
|
19859
20141
|
if (seenFieldNames.has(csFieldName)) continue;
|
|
19860
20142
|
seenFieldNames.add(csFieldName);
|
|
19861
20143
|
let useNewModifier = false;
|
|
@@ -19902,7 +20184,7 @@ function generateModels$3(models, ctx, discCtx) {
|
|
|
19902
20184
|
const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
|
|
19903
20185
|
lines.push(...emitJsonPropertyAttributes(field.name, {
|
|
19904
20186
|
isRequiredEnum,
|
|
19905
|
-
explicitWireName: collidesWithClassName
|
|
20187
|
+
explicitWireName: collidesWithClassName || hasDomainOverride
|
|
19906
20188
|
}));
|
|
19907
20189
|
const discriminatedUnionConverter = discriminatedUnionConverterName(field.type);
|
|
19908
20190
|
if (discriminatedUnionConverter) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${discriminatedUnionConverter}))]`);
|
|
@@ -21136,9 +21418,10 @@ function generateFixtures$1(spec) {
|
|
|
21136
21418
|
const modelMap = new Map(spec.models.map((m) => [m.name, m]));
|
|
21137
21419
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
21138
21420
|
const files = [];
|
|
21421
|
+
const nonPaginatedWrapperRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
21139
21422
|
for (const model of spec.models) {
|
|
21140
21423
|
if (isListMetadataModel(model)) continue;
|
|
21141
|
-
if (isListWrapperModel(model)) continue;
|
|
21424
|
+
if (isListWrapperModel(model) && !nonPaginatedWrapperRefs.has(model.name)) continue;
|
|
21142
21425
|
const fixture = model.fields.length === 0 ? {} : generateModelFixture$1(model, modelMap, enumMap);
|
|
21143
21426
|
files.push({
|
|
21144
21427
|
path: `testdata/${fixtureFileName(model.name)}.json`,
|
|
@@ -21207,7 +21490,7 @@ function generateModelFixture$1(model, modelMap, enumMap) {
|
|
|
21207
21490
|
const fixture = {};
|
|
21208
21491
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
21209
21492
|
const deduplicatedFields = model.fields.filter((f) => {
|
|
21210
|
-
const csName =
|
|
21493
|
+
const csName = domainFieldName$2(f);
|
|
21211
21494
|
if (seenFieldNames.has(csName)) return false;
|
|
21212
21495
|
seenFieldNames.add(csName);
|
|
21213
21496
|
return true;
|
|
@@ -21728,7 +22011,7 @@ function buildFixtureAssertions(model, spec) {
|
|
|
21728
22011
|
if (field.type.kind !== "primitive" || field.type.type !== "string") continue;
|
|
21729
22012
|
if (field.type.format === "date-time" || field.type.format === "date") continue;
|
|
21730
22013
|
if (field.type.format === "binary") continue;
|
|
21731
|
-
const csField =
|
|
22014
|
+
const csField = domainFieldName$2(field);
|
|
21732
22015
|
const val = fixture[field.name];
|
|
21733
22016
|
if (typeof val === "string" && val.length > 0) assertions.push(`Assert.Equal(${csStringLiteral(val)}, result.${csField});`);
|
|
21734
22017
|
else assertions.push(`Assert.NotEmpty(result.${csField});`);
|
|
@@ -21987,7 +22270,7 @@ const dotnetEmitter = {
|
|
|
21987
22270
|
return buildOperationsMap$3(spec, fixNamespace(ctx));
|
|
21988
22271
|
},
|
|
21989
22272
|
fileHeader() {
|
|
21990
|
-
return
|
|
22273
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
21991
22274
|
},
|
|
21992
22275
|
formatCommand(targetDir) {
|
|
21993
22276
|
const workspace = findDotnetWorkspace(targetDir);
|
|
@@ -22050,6 +22333,16 @@ function propertyName(name) {
|
|
|
22050
22333
|
if (camel === "object") return "objectType";
|
|
22051
22334
|
return escapeReserved(camel);
|
|
22052
22335
|
}
|
|
22336
|
+
/**
|
|
22337
|
+
* camelCase domain property name for a MODEL field, honoring a `domainName`
|
|
22338
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
22339
|
+
* a friendlier name. The wire key (the `@JsonProperty("...")` argument) still
|
|
22340
|
+
* derives from `field.name`. No-op when `domainName` is unset, so it is also
|
|
22341
|
+
* safe on params. Only apply to model fields.
|
|
22342
|
+
*/
|
|
22343
|
+
function domainPropertyName(field) {
|
|
22344
|
+
return propertyName(field.domainName ?? field.name);
|
|
22345
|
+
}
|
|
22053
22346
|
/** Lower-case Kotlin package segment for a service / mount group. */
|
|
22054
22347
|
function packageSegment(name) {
|
|
22055
22348
|
return toPascalCase(name).toLowerCase();
|
|
@@ -22752,7 +23045,7 @@ function renderFields(fields, overrideFields = /* @__PURE__ */ new Set()) {
|
|
|
22752
23045
|
const lines = [];
|
|
22753
23046
|
for (const rawField of fields) {
|
|
22754
23047
|
const field = promoteFieldType$1(rawField);
|
|
22755
|
-
const kotlinName =
|
|
23048
|
+
const kotlinName = domainPropertyName(field);
|
|
22756
23049
|
if (seen.has(kotlinName)) continue;
|
|
22757
23050
|
seen.add(kotlinName);
|
|
22758
23051
|
const baseType = mapTypeRef$2(field.type);
|
|
@@ -24606,7 +24899,7 @@ function buildResponseAssertions(responseModelName, ctx) {
|
|
|
24606
24899
|
for (const field of model.fields) {
|
|
24607
24900
|
if (!field.required) continue;
|
|
24608
24901
|
if (assertions.length >= MAX_RESPONSE_ASSERTIONS) break;
|
|
24609
|
-
const ktProp =
|
|
24902
|
+
const ktProp = domainPropertyName(field);
|
|
24610
24903
|
const type = field.type;
|
|
24611
24904
|
if (type.kind === "primitive") {
|
|
24612
24905
|
if (type.format === "date-time") continue;
|
|
@@ -25050,7 +25343,7 @@ const kotlinEmitter = {
|
|
|
25050
25343
|
return buildOperationsMap$2(spec, ctx);
|
|
25051
25344
|
},
|
|
25052
25345
|
fileHeader() {
|
|
25053
|
-
return
|
|
25346
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
25054
25347
|
},
|
|
25055
25348
|
formatCommand(targetDir) {
|
|
25056
25349
|
if (!fs.existsSync(path.join(targetDir, "gradlew"))) return null;
|
|
@@ -25102,6 +25395,15 @@ function fieldName$1(name) {
|
|
|
25102
25395
|
return toSnakeCase(name);
|
|
25103
25396
|
}
|
|
25104
25397
|
/**
|
|
25398
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
25399
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
25400
|
+
* a friendlier name. The wire key (still derived from `field.name`) is what
|
|
25401
|
+
* gets sent/received over the wire — only the domain attr/accessor name changes.
|
|
25402
|
+
*/
|
|
25403
|
+
function domainFieldName$1(field) {
|
|
25404
|
+
return toSnakeCase(field.domainName ?? field.name);
|
|
25405
|
+
}
|
|
25406
|
+
/**
|
|
25105
25407
|
* Ruby reserved words that cannot be used as parameter names.
|
|
25106
25408
|
* When a path/query param name collides, suffix with underscore.
|
|
25107
25409
|
*/
|
|
@@ -25332,7 +25634,7 @@ function generateModels$1(models, ctx) {
|
|
|
25332
25634
|
}
|
|
25333
25635
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
25334
25636
|
const fields = model.fields.filter((f) => {
|
|
25335
|
-
const n =
|
|
25637
|
+
const n = domainFieldName$1(f);
|
|
25336
25638
|
if (seenFieldNames.has(n)) return false;
|
|
25337
25639
|
seenFieldNames.add(n);
|
|
25338
25640
|
return true;
|
|
@@ -25346,7 +25648,7 @@ function generateModels$1(models, ctx) {
|
|
|
25346
25648
|
lines.push(" HASH_ATTRS = {");
|
|
25347
25649
|
for (let i = 0; i < fields.length; i++) {
|
|
25348
25650
|
const field = fields[i];
|
|
25349
|
-
const fname =
|
|
25651
|
+
const fname = domainFieldName$1(field);
|
|
25350
25652
|
const sep = i === fields.length - 1 ? "" : ",";
|
|
25351
25653
|
lines.push(` ${rubyHashLiteralKey(field.name)} :${fname}${sep}`);
|
|
25352
25654
|
}
|
|
@@ -25355,13 +25657,13 @@ function generateModels$1(models, ctx) {
|
|
|
25355
25657
|
if (deprecatedFields.length > 0) {
|
|
25356
25658
|
for (const f of deprecatedFields) {
|
|
25357
25659
|
const desc = f.description ? ` ${f.description.split("\n")[0].trim()}` : "";
|
|
25358
|
-
lines.push(` # @!attribute ${
|
|
25660
|
+
lines.push(` # @!attribute ${domainFieldName$1(f)}`);
|
|
25359
25661
|
lines.push(` # @deprecated${desc}`);
|
|
25360
25662
|
}
|
|
25361
25663
|
lines.push("");
|
|
25362
25664
|
}
|
|
25363
25665
|
if (accessorFields.length > 0) {
|
|
25364
|
-
const attrs = accessorFields.map((f) => `:${
|
|
25666
|
+
const attrs = accessorFields.map((f) => `:${domainFieldName$1(f)}`);
|
|
25365
25667
|
if (attrs.length === 1) lines.push(` attr_accessor ${attrs[0]}`);
|
|
25366
25668
|
else {
|
|
25367
25669
|
lines.push(` attr_accessor \\`);
|
|
@@ -25373,7 +25675,7 @@ function generateModels$1(models, ctx) {
|
|
|
25373
25675
|
lines.push("");
|
|
25374
25676
|
}
|
|
25375
25677
|
for (const f of deprecatedFields) {
|
|
25376
|
-
const fname =
|
|
25678
|
+
const fname = domainFieldName$1(f);
|
|
25377
25679
|
lines.push(` def ${fname}`);
|
|
25378
25680
|
lines.push(` warn "[DEPRECATION] \\\`${fname}\\\` is deprecated and will be removed in a future version.", uplevel: 1`);
|
|
25379
25681
|
lines.push(` @${fname}`);
|
|
@@ -25387,7 +25689,7 @@ function generateModels$1(models, ctx) {
|
|
|
25387
25689
|
lines.push(" def initialize(json)");
|
|
25388
25690
|
lines.push(" hash = self.class.normalize(json)");
|
|
25389
25691
|
for (const field of fields) {
|
|
25390
|
-
const fname =
|
|
25692
|
+
const fname = domainFieldName$1(field);
|
|
25391
25693
|
const rawKey = field.name;
|
|
25392
25694
|
lines.push(` ${deserializeAssignment(fname, rawKey, field.type, field.required, enumNames, modelNames)}`);
|
|
25393
25695
|
}
|
|
@@ -26883,7 +27185,7 @@ function generateModelRoundTripTest(spec) {
|
|
|
26883
27185
|
const dedupFields = /* @__PURE__ */ new Set();
|
|
26884
27186
|
for (const f of model.fields) {
|
|
26885
27187
|
const wireName = f.name;
|
|
26886
|
-
const rubyFieldName =
|
|
27188
|
+
const rubyFieldName = domainFieldName$1(f);
|
|
26887
27189
|
if (dedupFields.has(rubyFieldName)) continue;
|
|
26888
27190
|
dedupFields.add(rubyFieldName);
|
|
26889
27191
|
const stub = roundTripStub(f.type, enumNames);
|
|
@@ -27212,7 +27514,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
27212
27514
|
lines.push("");
|
|
27213
27515
|
const seenFieldNames = /* @__PURE__ */ new Set();
|
|
27214
27516
|
for (const f of model.fields) {
|
|
27215
|
-
const fname =
|
|
27517
|
+
const fname = domainFieldName$1(f);
|
|
27216
27518
|
if (seenFieldNames.has(fname)) continue;
|
|
27217
27519
|
seenFieldNames.add(fname);
|
|
27218
27520
|
const sorbetType = f.required ? mapSorbetType(f.type) : wrapNilable(mapSorbetType(f.type));
|
|
@@ -27489,7 +27791,7 @@ const rubyEmitter = {
|
|
|
27489
27791
|
return buildOperationsMap$1(spec, ctx);
|
|
27490
27792
|
},
|
|
27491
27793
|
fileHeader() {
|
|
27492
|
-
return `# frozen_string_literal: true\n\n#
|
|
27794
|
+
return `# frozen_string_literal: true\n\n# ${AUTOGEN_NOTICE}`;
|
|
27493
27795
|
},
|
|
27494
27796
|
formatCommand(targetDir) {
|
|
27495
27797
|
return {
|
|
@@ -27575,6 +27877,15 @@ function methodName(name) {
|
|
|
27575
27877
|
function fieldName(name) {
|
|
27576
27878
|
return escapeKeyword(toSnakeCase(name));
|
|
27577
27879
|
}
|
|
27880
|
+
/**
|
|
27881
|
+
* snake_case domain field name for a model field, honoring a `domainName`
|
|
27882
|
+
* override (set via the `fieldHints` config) so a wire field can surface under
|
|
27883
|
+
* a friendlier identifier. The wire name (and thus the `#[serde(rename = ...)]`
|
|
27884
|
+
* key) still derives from `field.name`.
|
|
27885
|
+
*/
|
|
27886
|
+
function domainFieldName(field) {
|
|
27887
|
+
return escapeKeyword(toSnakeCase(field.domainName ?? field.name));
|
|
27888
|
+
}
|
|
27578
27889
|
/** PascalCase enum variant. */
|
|
27579
27890
|
function variantName(value) {
|
|
27580
27891
|
const pascal = toPascalCase(String(value));
|
|
@@ -27846,6 +28157,7 @@ function generateModels(models, ctx, registry) {
|
|
|
27846
28157
|
const files = [];
|
|
27847
28158
|
const moduleNames = [];
|
|
27848
28159
|
const seen = /* @__PURE__ */ new Set();
|
|
28160
|
+
const taggedVariantFields = collectTaggedVariantFields(models);
|
|
27849
28161
|
for (const model of models) {
|
|
27850
28162
|
const mod = moduleName(model.name);
|
|
27851
28163
|
if (seen.has(mod)) continue;
|
|
@@ -27854,7 +28166,7 @@ function generateModels(models, ctx, registry) {
|
|
|
27854
28166
|
const path = ctx.overlayLookup?.fileBySymbol?.get(model.name) ?? `src/models/${mod}.rs`;
|
|
27855
28167
|
files.push({
|
|
27856
28168
|
path,
|
|
27857
|
-
content: renderModel(model, registry),
|
|
28169
|
+
content: renderModel(model, registry, taggedVariantFields.get(model.name)),
|
|
27858
28170
|
overwriteExisting: true
|
|
27859
28171
|
});
|
|
27860
28172
|
}
|
|
@@ -27866,7 +28178,43 @@ function generateModels(models, ctx, registry) {
|
|
|
27866
28178
|
});
|
|
27867
28179
|
return files;
|
|
27868
28180
|
}
|
|
27869
|
-
|
|
28181
|
+
/**
|
|
28182
|
+
* Walk every model field and record which models are arms of an
|
|
28183
|
+
* internally-tagged union, mapped to that union's discriminator property.
|
|
28184
|
+
*/
|
|
28185
|
+
function collectTaggedVariantFields(models) {
|
|
28186
|
+
const out = /* @__PURE__ */ new Map();
|
|
28187
|
+
const visit = (ref) => {
|
|
28188
|
+
switch (ref.kind) {
|
|
28189
|
+
case "union":
|
|
28190
|
+
if (ref.discriminator?.property) for (const variant of ref.variants) {
|
|
28191
|
+
const name = variantModelName(variant);
|
|
28192
|
+
if (name) out.set(name, ref.discriminator.property);
|
|
28193
|
+
}
|
|
28194
|
+
ref.variants.forEach(visit);
|
|
28195
|
+
break;
|
|
28196
|
+
case "array":
|
|
28197
|
+
visit(ref.items);
|
|
28198
|
+
break;
|
|
28199
|
+
case "nullable":
|
|
28200
|
+
visit(ref.inner);
|
|
28201
|
+
break;
|
|
28202
|
+
case "map":
|
|
28203
|
+
visit(ref.valueType);
|
|
28204
|
+
break;
|
|
28205
|
+
default: break;
|
|
28206
|
+
}
|
|
28207
|
+
};
|
|
28208
|
+
for (const model of models) for (const field of model.fields) visit(field.type);
|
|
28209
|
+
return out;
|
|
28210
|
+
}
|
|
28211
|
+
/** Resolve the underlying model name of a union arm, unwrapping a nullable. */
|
|
28212
|
+
function variantModelName(ref) {
|
|
28213
|
+
if (ref.kind === "model") return ref.name;
|
|
28214
|
+
if (ref.kind === "nullable") return variantModelName(ref.inner);
|
|
28215
|
+
return null;
|
|
28216
|
+
}
|
|
28217
|
+
function renderModel(model, registry, tagField) {
|
|
27870
28218
|
const lines = [];
|
|
27871
28219
|
lines.push(HEADER_PLACEHOLDER);
|
|
27872
28220
|
lines.push("#[allow(unused_imports)]");
|
|
@@ -27878,7 +28226,7 @@ function renderModel(model, registry) {
|
|
|
27878
28226
|
if (model.description) lines.push(...docComment$1(model.description));
|
|
27879
28227
|
lines.push("#[derive(Debug, Clone, Serialize, Deserialize)]");
|
|
27880
28228
|
const resolvedNames = resolveFieldNames(model.fields);
|
|
27881
|
-
const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry));
|
|
28229
|
+
const fieldLines = model.fields.map((f, i) => renderField(f, resolvedNames[i], model.name, registry, tagField));
|
|
27882
28230
|
if (fieldLines.length === 0) lines.push(`pub struct ${typeName(model.name)} {}`);
|
|
27883
28231
|
else {
|
|
27884
28232
|
lines.push(`pub struct ${typeName(model.name)} {`);
|
|
@@ -27888,17 +28236,20 @@ function renderModel(model, registry) {
|
|
|
27888
28236
|
return lines.filter((l) => l !== HEADER_PLACEHOLDER).join("\n") + "\n";
|
|
27889
28237
|
}
|
|
27890
28238
|
/**
|
|
27891
|
-
* Resolve unique Rust identifiers for struct fields.
|
|
27892
|
-
*
|
|
27893
|
-
* `
|
|
27894
|
-
*
|
|
27895
|
-
*
|
|
28239
|
+
* Resolve unique Rust identifiers for struct fields. The domain identifier
|
|
28240
|
+
* honors a `fieldHints` override (`domainName`, e.g. wire `connection_type` →
|
|
28241
|
+
* domain `type`); the wire name (and the `#[serde(rename = ...)]` key emitted
|
|
28242
|
+
* in `renderField`) still derives from `f.name`. Multiple names can collide
|
|
28243
|
+
* after snake-casing (e.g. `integration_type` and `integrationType` both
|
|
28244
|
+
* become `integration_type`). Subsequent collisions get a numeric suffix so
|
|
28245
|
+
* the struct compiles; serde `rename` preserves the original wire name in every
|
|
28246
|
+
* case.
|
|
27896
28247
|
*/
|
|
27897
28248
|
function resolveFieldNames(fields) {
|
|
27898
28249
|
const used = /* @__PURE__ */ new Set();
|
|
27899
28250
|
const out = [];
|
|
27900
28251
|
for (const f of fields) {
|
|
27901
|
-
const base =
|
|
28252
|
+
const base = domainFieldName(f);
|
|
27902
28253
|
let candidate = base;
|
|
27903
28254
|
let suffix = 2;
|
|
27904
28255
|
while (used.has(candidate)) {
|
|
@@ -27910,7 +28261,7 @@ function resolveFieldNames(fields) {
|
|
|
27910
28261
|
}
|
|
27911
28262
|
return out;
|
|
27912
28263
|
}
|
|
27913
|
-
function renderField(field, rustField, modelName, registry) {
|
|
28264
|
+
function renderField(field, rustField, modelName, registry, tagField) {
|
|
27914
28265
|
const lines = [];
|
|
27915
28266
|
const hasDescription = !!field.description;
|
|
27916
28267
|
if (hasDescription) for (const c of docComment$1(field.description)) lines.push(` ${c}`);
|
|
@@ -27925,8 +28276,13 @@ function renderField(field, rustField, modelName, registry) {
|
|
|
27925
28276
|
});
|
|
27926
28277
|
if ((!field.required || field.type.kind === "nullable") && !baseType.startsWith("Option<")) baseType = makeOptional(baseType);
|
|
27927
28278
|
baseType = applySecretRedaction(baseType, field.name);
|
|
27928
|
-
if (
|
|
27929
|
-
|
|
28279
|
+
if (tagField === field.name) {
|
|
28280
|
+
const args = rename ? `rename = "${rename}", default, skip_serializing` : "default, skip_serializing";
|
|
28281
|
+
lines.push(` #[serde(${args})]`);
|
|
28282
|
+
} else {
|
|
28283
|
+
if (rename) lines.push(` #[serde(rename = "${rename}")]`);
|
|
28284
|
+
if (baseType.startsWith("Option<")) lines.push(" #[serde(skip_serializing_if = \"Option::is_none\", default)]");
|
|
28285
|
+
}
|
|
27930
28286
|
if (field.deprecated) lines.push(" #[deprecated]");
|
|
27931
28287
|
lines.push(` pub ${rustField}: ${baseType},`);
|
|
27932
28288
|
return lines.join("\n");
|
|
@@ -28508,7 +28864,7 @@ function registerSyntheticBody(op, paramsName, bodyGroupParamNames, bodyGroupFie
|
|
|
28508
28864
|
if (!f.required && !rust.startsWith("Option<")) rust = makeOptional(rust);
|
|
28509
28865
|
rust = applySecretRedaction(rust, f.name);
|
|
28510
28866
|
return {
|
|
28511
|
-
rustName:
|
|
28867
|
+
rustName: domainFieldName(f),
|
|
28512
28868
|
wireName: f.name,
|
|
28513
28869
|
rustType: rust,
|
|
28514
28870
|
required: !!f.required && !rust.startsWith("Option<"),
|
|
@@ -29863,7 +30219,7 @@ const rustEmitter = {
|
|
|
29863
30219
|
return buildOperationsMap(spec, ctx);
|
|
29864
30220
|
},
|
|
29865
30221
|
fileHeader() {
|
|
29866
|
-
return
|
|
30222
|
+
return `// ${AUTOGEN_NOTICE}`;
|
|
29867
30223
|
},
|
|
29868
30224
|
formatCommand(_targetDir) {
|
|
29869
30225
|
return {
|
|
@@ -29918,4 +30274,4 @@ const workosEmittersPlugin = {
|
|
|
29918
30274
|
//#endregion
|
|
29919
30275
|
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 };
|
|
29920
30276
|
|
|
29921
|
-
//# sourceMappingURL=plugin-
|
|
30277
|
+
//# sourceMappingURL=plugin-Cciic50q.mjs.map
|