@workos/oagen-emitters 0.6.6 → 0.6.8
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 +15 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BgVrq-hM.mjs → plugin-Cmg_LFtm.mjs} +433 -53
- package/dist/plugin-Cmg_LFtm.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/index.ts +10 -3
- package/src/dotnet/models.ts +24 -0
- package/src/go/models.ts +18 -1
- package/src/go/tests.ts +8 -2
- package/src/ruby/index.ts +4 -2
- package/src/ruby/naming.ts +23 -0
- package/src/ruby/parameter-groups.ts +221 -0
- package/src/ruby/rbi.ts +52 -3
- package/src/ruby/resources.ts +118 -19
- package/src/ruby/tests.ts +161 -18
- package/test/dotnet/models.test.ts +77 -0
- package/dist/plugin-BgVrq-hM.mjs.map +0 -1
|
@@ -7782,7 +7782,7 @@ const buildHiddenParams = buildHiddenParams$1;
|
|
|
7782
7782
|
* PascalCase variant class name for a parameter group variant.
|
|
7783
7783
|
* E.g., group "parent_resource", variant "by_id" -> "ParentResourceById".
|
|
7784
7784
|
*/
|
|
7785
|
-
function groupVariantClassName$
|
|
7785
|
+
function groupVariantClassName$3(groupName, variantName) {
|
|
7786
7786
|
return className$5(`${groupName}_${variantName}`);
|
|
7787
7787
|
}
|
|
7788
7788
|
/**
|
|
@@ -7796,7 +7796,7 @@ function generateParameterGroupDataclasses(operations, specEnumNames, models) {
|
|
|
7796
7796
|
for (const op of operations) {
|
|
7797
7797
|
const bodyFieldTypes = collectBodyFieldTypes(op, models);
|
|
7798
7798
|
for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
|
|
7799
|
-
const variantClass = groupVariantClassName$
|
|
7799
|
+
const variantClass = groupVariantClassName$3(group.name, variant.name);
|
|
7800
7800
|
if (emitted.has(variantClass)) continue;
|
|
7801
7801
|
emitted.add(variantClass);
|
|
7802
7802
|
lines.push("");
|
|
@@ -7829,7 +7829,7 @@ function collectParameterGroupClassNames(operations) {
|
|
|
7829
7829
|
const names = [];
|
|
7830
7830
|
const seen = /* @__PURE__ */ new Set();
|
|
7831
7831
|
for (const op of operations) for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
|
|
7832
|
-
const cls = groupVariantClassName$
|
|
7832
|
+
const cls = groupVariantClassName$3(group.name, variant.name);
|
|
7833
7833
|
if (!seen.has(cls)) {
|
|
7834
7834
|
seen.add(cls);
|
|
7835
7835
|
names.push(cls);
|
|
@@ -7924,7 +7924,7 @@ function emitMethodSignature(lines, op, plan, method, isAsync, specEnumNames, mo
|
|
|
7924
7924
|
}
|
|
7925
7925
|
}
|
|
7926
7926
|
for (const group of op.parameterGroups ?? []) {
|
|
7927
|
-
const unionType = `Union[${group.variants.map((v) => groupVariantClassName$
|
|
7927
|
+
const unionType = `Union[${group.variants.map((v) => groupVariantClassName$3(group.name, v.name)).join(", ")}]`;
|
|
7928
7928
|
const paramName = fieldName$4(group.name);
|
|
7929
7929
|
if (group.optional) lines.push(` ${paramName}: Optional[${unionType}] = None,`);
|
|
7930
7930
|
else lines.push(` ${paramName}: ${unionType},`);
|
|
@@ -8050,7 +8050,7 @@ function emitMethodDocstring(lines, op, plan, method, meta, specEnumNames, ctx,
|
|
|
8050
8050
|
}
|
|
8051
8051
|
}
|
|
8052
8052
|
for (const group of op.parameterGroups ?? []) {
|
|
8053
|
-
const variantClasses = group.variants.map((v) => groupVariantClassName$
|
|
8053
|
+
const variantClasses = group.variants.map((v) => groupVariantClassName$3(group.name, v.name));
|
|
8054
8054
|
const desc = `Identifies the ${group.name.replace(/_/g, " ")}. One of: ${variantClasses.join(", ")}.`;
|
|
8055
8055
|
allParams.push({
|
|
8056
8056
|
name: fieldName$4(group.name),
|
|
@@ -8357,7 +8357,7 @@ function emitGroupDispatch(lines, op, target = "params", bodyFieldTypes) {
|
|
|
8357
8357
|
const indent = group.optional ? " " : "";
|
|
8358
8358
|
let first = true;
|
|
8359
8359
|
for (const variant of group.variants) {
|
|
8360
|
-
const variantClass = groupVariantClassName$
|
|
8360
|
+
const variantClass = groupVariantClassName$3(group.name, variant.name);
|
|
8361
8361
|
const keyword = first ? "if" : "elif";
|
|
8362
8362
|
first = false;
|
|
8363
8363
|
lines.push(` ${indent}${keyword} isinstance(${groupParam}, ${variantClass}):`);
|
|
@@ -10928,7 +10928,7 @@ function isRedirectEndpoint(op, resolvedOp) {
|
|
|
10928
10928
|
return false;
|
|
10929
10929
|
}
|
|
10930
10930
|
/** PHP class name for a parameter group variant (e.g. ParentResourceById). */
|
|
10931
|
-
function groupVariantClassName$
|
|
10931
|
+
function groupVariantClassName$2(groupName, variantName) {
|
|
10932
10932
|
return `${className$4(groupName)}${className$4(variantName)}`;
|
|
10933
10933
|
}
|
|
10934
10934
|
/**
|
|
@@ -10948,7 +10948,7 @@ function generateParameterGroupFiles(op, ctx, modelMap) {
|
|
|
10948
10948
|
const files = [];
|
|
10949
10949
|
const bodyFieldTypes = collectBodyFieldTypes(op, [...modelMap.values()]);
|
|
10950
10950
|
for (const group of op.parameterGroups ?? []) for (const variant of group.variants) {
|
|
10951
|
-
const variantClass = groupVariantClassName$
|
|
10951
|
+
const variantClass = groupVariantClassName$2(group.name, variant.name);
|
|
10952
10952
|
const lines = [];
|
|
10953
10953
|
lines.push(`namespace ${ctx.namespacePascal}\\Service;`);
|
|
10954
10954
|
lines.push("");
|
|
@@ -10982,7 +10982,7 @@ function generateGroupDispatch(op, indent, target = "$query") {
|
|
|
10982
10982
|
const phpParamName = fieldName$3(group.name);
|
|
10983
10983
|
for (let vi = 0; vi < group.variants.length; vi++) {
|
|
10984
10984
|
const variant = group.variants[vi];
|
|
10985
|
-
const variantClass = groupVariantClassName$
|
|
10985
|
+
const variantClass = groupVariantClassName$2(group.name, variant.name);
|
|
10986
10986
|
const keyword = vi === 0 ? "if" : "elseif";
|
|
10987
10987
|
lines.push(`${indent}${keyword} ($${phpParamName} instanceof ${variantClass}) {`);
|
|
10988
10988
|
for (const param of variant.parameters) {
|
|
@@ -11036,7 +11036,7 @@ function generateMethod$2(lines, op, service, ctx, modelMap, resolvedOp) {
|
|
|
11036
11036
|
const phpName = fieldName$3(group.name);
|
|
11037
11037
|
if (seenDocParams.has(phpName)) continue;
|
|
11038
11038
|
seenDocParams.add(phpName);
|
|
11039
|
-
const unionDocType = group.variants.map((v) => groupVariantClassName$
|
|
11039
|
+
const unionDocType = group.variants.map((v) => groupVariantClassName$2(group.name, v.name)).join("|");
|
|
11040
11040
|
const nullPrefix = group.optional ? "null|" : "";
|
|
11041
11041
|
docParts.push(`@param ${nullPrefix}${unionDocType} $${phpName}`);
|
|
11042
11042
|
}
|
|
@@ -11258,7 +11258,7 @@ function buildMethodParams(op, plan, modelMap, ctx, hiddenParams) {
|
|
|
11258
11258
|
const phpName = fieldName$3(group.name);
|
|
11259
11259
|
if (usedNames.has(phpName)) continue;
|
|
11260
11260
|
usedNames.add(phpName);
|
|
11261
|
-
const unionType = group.variants.map((v) => groupVariantClassName$
|
|
11261
|
+
const unionType = group.variants.map((v) => groupVariantClassName$2(group.name, v.name)).join("|");
|
|
11262
11262
|
if (group.optional) optional.push(`null|${unionType} $${phpName} = null`);
|
|
11263
11263
|
else required.push(`${unionType} $${phpName}`);
|
|
11264
11264
|
}
|
|
@@ -12395,7 +12395,21 @@ function makeOptional$1(goType) {
|
|
|
12395
12395
|
return `*${goType}`;
|
|
12396
12396
|
}
|
|
12397
12397
|
function structuralHash$2(model) {
|
|
12398
|
-
|
|
12398
|
+
const fieldHash = model.fields.map((f) => `${f.name}:${JSON.stringify(f.type)}:${f.required}`).sort().join("|");
|
|
12399
|
+
const domain = crudEntityDomain(model.name);
|
|
12400
|
+
return domain ? `${domain}::${fieldHash}` : fieldHash;
|
|
12401
|
+
}
|
|
12402
|
+
const CRUD_PREFIXES = [
|
|
12403
|
+
"Create",
|
|
12404
|
+
"Update",
|
|
12405
|
+
"Delete",
|
|
12406
|
+
"Get",
|
|
12407
|
+
"List"
|
|
12408
|
+
];
|
|
12409
|
+
/** Strip CRUD verb prefix to get the entity name, or null if no prefix matches. */
|
|
12410
|
+
function crudEntityDomain(name) {
|
|
12411
|
+
for (const prefix of CRUD_PREFIXES) if (name.startsWith(prefix) && name.length > prefix.length) return name.slice(prefix.length);
|
|
12412
|
+
return null;
|
|
12399
12413
|
}
|
|
12400
12414
|
/** Known acronyms to preserve as single tokens during humanization. */
|
|
12401
12415
|
const HUMANIZE_ACRONYMS$1 = [
|
|
@@ -14150,7 +14164,8 @@ function generateServiceTest$1(service, spec, ctx, _accessPaths, fixtureRewrites
|
|
|
14150
14164
|
} else if (plan.responseModelName) {
|
|
14151
14165
|
const respModel = plan.responseModelName;
|
|
14152
14166
|
const isArrayResponse = !isPaginated && op.response?.kind === "array";
|
|
14153
|
-
|
|
14167
|
+
let fixturePath = `testdata/${fileName$1(respModel)}.json`;
|
|
14168
|
+
if (fixtureRewrites.has(fixturePath)) fixturePath = fixtureRewrites.get(fixturePath);
|
|
14154
14169
|
const expectedPath = buildExpectedPath$1(op);
|
|
14155
14170
|
const httpMethodUpper = op.httpMethod.toUpperCase();
|
|
14156
14171
|
const isBodyMethod = httpMethodUpper === "POST" || httpMethodUpper === "PUT" || httpMethodUpper === "PATCH";
|
|
@@ -14231,7 +14246,8 @@ function generateServiceTest$1(service, spec, ctx, _accessPaths, fixtureRewrites
|
|
|
14231
14246
|
const wrapperParamsStruct = paramsStructName(resolvedName, wrapperMethod);
|
|
14232
14247
|
const responseType = wrapper.responseModelName;
|
|
14233
14248
|
const testName = `Test${accessorName}_${wrapperMethod}`;
|
|
14234
|
-
|
|
14249
|
+
let fixturePath = responseType ? `testdata/${fileName$1(responseType)}.json` : null;
|
|
14250
|
+
if (fixturePath && fixtureRewrites.has(fixturePath)) fixturePath = fixtureRewrites.get(fixturePath);
|
|
14235
14251
|
const wrapperCallArgs = ["context.Background()"];
|
|
14236
14252
|
for (const p of sortPathParamsByTemplateOrder$2(op)) wrapperCallArgs.push(`"test_${p.name}"`);
|
|
14237
14253
|
wrapperCallArgs.push(`&${ctx.namespace}.${wrapperParamsStruct}{}`);
|
|
@@ -14975,7 +14991,8 @@ function generateModels$2(models, ctx, discCtx) {
|
|
|
14975
14991
|
const human = humanize$1(model.name);
|
|
14976
14992
|
lines.push(` /// <summary>Represents ${articleFor(human)} ${human}.</summary>`);
|
|
14977
14993
|
}
|
|
14978
|
-
|
|
14994
|
+
const isDiscBase = discCtx?.discriminatorBases.has(model.name) ?? false;
|
|
14995
|
+
if (isDiscBase) lines.push(` [Newtonsoft.Json.JsonConverter(typeof(${csClassName}DiscriminatorConverter))]`);
|
|
14979
14996
|
const baseName = discCtx?.variantToBase.get(model.name);
|
|
14980
14997
|
const baseClassName = baseName ? modelClassName(baseName) : null;
|
|
14981
14998
|
const baseFields = baseName ? baseFieldLookup.get(baseName) : void 0;
|
|
@@ -15003,10 +15020,16 @@ function generateModels$2(models, ctx, discCtx) {
|
|
|
15003
15020
|
let csType;
|
|
15004
15021
|
let initializer = "";
|
|
15005
15022
|
let setterModifier = "";
|
|
15023
|
+
const discProp = isDiscBase ? discCtx?.discriminatorProperties?.get(model.name) : void 0;
|
|
15024
|
+
const isDiscriminatorField = discProp !== void 0 && field.name === discProp;
|
|
15006
15025
|
if (constInit !== null && !isOptional) {
|
|
15007
15026
|
csType = baseType;
|
|
15008
15027
|
initializer = ` = ${constInit};`;
|
|
15009
15028
|
setterModifier = "internal ";
|
|
15029
|
+
} else if (isDiscriminatorField) {
|
|
15030
|
+
csType = baseType;
|
|
15031
|
+
if (!isAlreadyNullable && !isValueTypeRef(field.type)) initializer = " = default!;";
|
|
15032
|
+
setterModifier = "internal ";
|
|
15010
15033
|
} else if (isOptional) if (isAlreadyNullable) csType = baseType;
|
|
15011
15034
|
else if (isValueTypeRef(field.type)) csType = `${baseType}?`;
|
|
15012
15035
|
else csType = `${baseType}?`;
|
|
@@ -15039,6 +15062,14 @@ function generateModels$2(models, ctx, discCtx) {
|
|
|
15039
15062
|
lines.push(` /// <paramref name="key"/> coerced to <typeparamref name="T"/>, or the default`);
|
|
15040
15063
|
lines.push(` /// value when the key is missing or the value is not convertible.`);
|
|
15041
15064
|
lines.push(` /// </summary>`);
|
|
15065
|
+
if (isDiscBase) {
|
|
15066
|
+
lines.push(` /// <remarks>`);
|
|
15067
|
+
lines.push(` /// Variant subclasses provide strongly-typed <c>${dict.csName}</c> properties that`);
|
|
15068
|
+
lines.push(` /// shadow this dictionary. This accessor is intended for forward-compatible handling`);
|
|
15069
|
+
lines.push(` /// of types not yet known to this SDK version. For recognized types, cast to the`);
|
|
15070
|
+
lines.push(` /// specific subclass and access its typed <c>${dict.csName}</c> property directly.`);
|
|
15071
|
+
lines.push(` /// </remarks>`);
|
|
15072
|
+
}
|
|
15042
15073
|
lines.push(` /// <typeparam name="T">Expected value type.</typeparam>`);
|
|
15043
15074
|
lines.push(` /// <param name="key">The key to look up.</param>`);
|
|
15044
15075
|
lines.push(` public T? Get${dict.csName}Attribute<T>(string key)`);
|
|
@@ -15534,7 +15565,7 @@ function groupBaseClassName(mountName, groupName) {
|
|
|
15534
15565
|
return `${className$2(mountName)}${className$2(groupName)}`;
|
|
15535
15566
|
}
|
|
15536
15567
|
/** Concrete variant class name (e.g. UserManagementRoleSingle). */
|
|
15537
|
-
function groupVariantClassName(mountName, groupName, variantName) {
|
|
15568
|
+
function groupVariantClassName$1(mountName, groupName, variantName) {
|
|
15538
15569
|
return `${className$2(mountName)}${className$2(groupName)}${className$2(variantName)}`;
|
|
15539
15570
|
}
|
|
15540
15571
|
/**
|
|
@@ -15552,7 +15583,7 @@ function generateParameterGroupTypes(mountName, op, models, emitted) {
|
|
|
15552
15583
|
lines.push("");
|
|
15553
15584
|
lines.push(` public abstract class ${baseName} { }`);
|
|
15554
15585
|
for (const variant of group.variants) {
|
|
15555
|
-
const variantName = groupVariantClassName(mountName, group.name, variant.name);
|
|
15586
|
+
const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
|
|
15556
15587
|
lines.push("");
|
|
15557
15588
|
lines.push(` public class ${variantName} : ${baseName}`);
|
|
15558
15589
|
lines.push(" {");
|
|
@@ -15580,7 +15611,7 @@ function emitGroupSerialization(mountName, op, indent, models, target) {
|
|
|
15580
15611
|
const groupField = fieldName$1(group.name);
|
|
15581
15612
|
let first = true;
|
|
15582
15613
|
for (const variant of group.variants) {
|
|
15583
|
-
const variantName = groupVariantClassName(mountName, group.name, variant.name);
|
|
15614
|
+
const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
|
|
15584
15615
|
const localVar = localName(variant.name);
|
|
15585
15616
|
const keyword = first ? "if" : "else if";
|
|
15586
15617
|
first = false;
|
|
@@ -16861,7 +16892,7 @@ const dotnetEmitter = {
|
|
|
16861
16892
|
const discriminatorBases = /* @__PURE__ */ new Set();
|
|
16862
16893
|
const variantToBase = /* @__PURE__ */ new Map();
|
|
16863
16894
|
const modelDiscriminators = /* @__PURE__ */ new Map();
|
|
16864
|
-
const
|
|
16895
|
+
const dotnetModels = enriched.map((m) => {
|
|
16865
16896
|
const disc = m.discriminator;
|
|
16866
16897
|
if (disc && m.fields.length === 0) {
|
|
16867
16898
|
const original = originalByName.get(m.name);
|
|
@@ -16876,9 +16907,13 @@ const dotnetEmitter = {
|
|
|
16876
16907
|
}
|
|
16877
16908
|
}
|
|
16878
16909
|
return m;
|
|
16879
|
-
})
|
|
16910
|
+
});
|
|
16911
|
+
const discriminatorProperties = /* @__PURE__ */ new Map();
|
|
16912
|
+
for (const [baseName, disc] of modelDiscriminators) discriminatorProperties.set(baseName, disc.property);
|
|
16913
|
+
const files = generateModels$2(dotnetModels, c, {
|
|
16880
16914
|
discriminatorBases,
|
|
16881
|
-
variantToBase
|
|
16915
|
+
variantToBase,
|
|
16916
|
+
discriminatorProperties
|
|
16882
16917
|
});
|
|
16883
16918
|
if (discriminatedUnions$1.size > 0) for (const [baseName, disc] of discriminatedUnions$1) {
|
|
16884
16919
|
const converterName = `${baseName}DiscriminatorConverter`;
|
|
@@ -16939,6 +16974,8 @@ const dotnetEmitter = {
|
|
|
16939
16974
|
lines.push(` /// </summary>`);
|
|
16940
16975
|
lines.push(` public class ${converterName} : Newtonsoft.Json.JsonConverter`);
|
|
16941
16976
|
lines.push(" {");
|
|
16977
|
+
lines.push(" public override bool CanWrite => false;");
|
|
16978
|
+
lines.push("");
|
|
16942
16979
|
lines.push(` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`);
|
|
16943
16980
|
lines.push("");
|
|
16944
16981
|
lines.push(" public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)");
|
|
@@ -16960,8 +16997,6 @@ const dotnetEmitter = {
|
|
|
16960
16997
|
lines.push(" return target;");
|
|
16961
16998
|
lines.push(" }");
|
|
16962
16999
|
lines.push("");
|
|
16963
|
-
lines.push(" public override bool CanWrite => false;");
|
|
16964
|
-
lines.push("");
|
|
16965
17000
|
lines.push(" public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer)");
|
|
16966
17001
|
lines.push(" {");
|
|
16967
17002
|
lines.push(" throw new NotImplementedException(\"Serialization is handled by the default serializer.\");");
|
|
@@ -19642,6 +19677,22 @@ function safeParamName(name) {
|
|
|
19642
19677
|
function moduleName(name) {
|
|
19643
19678
|
return toSnakeCase(name);
|
|
19644
19679
|
}
|
|
19680
|
+
/**
|
|
19681
|
+
* PascalCase class name for a parameter-group variant. Mirrors the Python
|
|
19682
|
+
* convention: group "password" + variant "plaintext" → `PasswordPlaintext`.
|
|
19683
|
+
* Used as the Ruby constant under the WorkOS module.
|
|
19684
|
+
*/
|
|
19685
|
+
function groupVariantClassName(groupName, variantName) {
|
|
19686
|
+
return className(`${groupName}_${variantName}`);
|
|
19687
|
+
}
|
|
19688
|
+
/**
|
|
19689
|
+
* Fully-qualified Ruby constant for a parameter-group variant scoped under
|
|
19690
|
+
* its owning resource module — e.g. "WorkOS::UserManagement::PasswordPlaintext".
|
|
19691
|
+
* Mirrors Python's `workos.user_management.PasswordPlaintext` namespacing.
|
|
19692
|
+
*/
|
|
19693
|
+
function scopedGroupVariantClassName(mountTarget, groupName, variantName) {
|
|
19694
|
+
return `WorkOS::${className(mountTarget)}::${groupVariantClassName(groupName, variantName)}`;
|
|
19695
|
+
}
|
|
19645
19696
|
/** snake_case property name for service accessors on the client. */
|
|
19646
19697
|
function servicePropertyName(name) {
|
|
19647
19698
|
return toSnakeCase(name);
|
|
@@ -20198,6 +20249,185 @@ function rubyStringLit$1(s) {
|
|
|
20198
20249
|
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
20199
20250
|
}
|
|
20200
20251
|
//#endregion
|
|
20252
|
+
//#region src/ruby/parameter-groups.ts
|
|
20253
|
+
/**
|
|
20254
|
+
* Sorbet type string for a TypeRef. Mirrors `mapSorbetType` in rbi.ts but
|
|
20255
|
+
* lives here so the parameter-groups module is self-contained.
|
|
20256
|
+
*/
|
|
20257
|
+
function mapSorbetType$1(ref) {
|
|
20258
|
+
switch (ref.kind) {
|
|
20259
|
+
case "primitive":
|
|
20260
|
+
switch (ref.type) {
|
|
20261
|
+
case "string": return "String";
|
|
20262
|
+
case "integer": return "Integer";
|
|
20263
|
+
case "number": return "Float";
|
|
20264
|
+
case "boolean": return "T::Boolean";
|
|
20265
|
+
case "unknown": return "T.untyped";
|
|
20266
|
+
}
|
|
20267
|
+
break;
|
|
20268
|
+
case "array": return `T::Array[${mapSorbetType$1(ref.items)}]`;
|
|
20269
|
+
case "model": return `WorkOS::${className(ref.name)}`;
|
|
20270
|
+
case "enum": return "String";
|
|
20271
|
+
case "nullable": return `T.nilable(${mapSorbetType$1(ref.inner)})`;
|
|
20272
|
+
case "literal":
|
|
20273
|
+
if (typeof ref.value === "string") return "String";
|
|
20274
|
+
if (ref.value === null) return "NilClass";
|
|
20275
|
+
if (typeof ref.value === "number") return Number.isInteger(ref.value) ? "Integer" : "Float";
|
|
20276
|
+
return "T::Boolean";
|
|
20277
|
+
case "union": {
|
|
20278
|
+
const variants = ref.variants.map((v) => mapSorbetType$1(v));
|
|
20279
|
+
const unique = [...new Set(variants)];
|
|
20280
|
+
if (unique.length === 1) return unique[0];
|
|
20281
|
+
return `T.any(${unique.join(", ")})`;
|
|
20282
|
+
}
|
|
20283
|
+
case "map": return `T::Hash[String, ${mapSorbetType$1(ref.valueType)}]`;
|
|
20284
|
+
}
|
|
20285
|
+
return "T.untyped";
|
|
20286
|
+
}
|
|
20287
|
+
/**
|
|
20288
|
+
* Build a stable groupName -> mountTarget map. Each parameter group is owned
|
|
20289
|
+
* by exactly one resource module — Ruby variant classes are inlined into
|
|
20290
|
+
* `WorkOS::<MountTarget>::<Variant>` (matching Python's per-resource layout),
|
|
20291
|
+
* so a dispatcher in another resource that references the same group still
|
|
20292
|
+
* resolves to a single canonical class.
|
|
20293
|
+
*
|
|
20294
|
+
* Mount targets are visited in alphabetical order so first-wins is
|
|
20295
|
+
* deterministic across runs. In the current spec no group is shared across
|
|
20296
|
+
* mount targets; if one ever is, the alphabetically-first owner gets the
|
|
20297
|
+
* class and other dispatchers reference it by full path.
|
|
20298
|
+
*/
|
|
20299
|
+
function buildGroupOwnerMap(ctx) {
|
|
20300
|
+
const owner = /* @__PURE__ */ new Map();
|
|
20301
|
+
const groups = groupByMount(ctx);
|
|
20302
|
+
const sortedTargets = [...groups.keys()].sort();
|
|
20303
|
+
for (const target of sortedTargets) {
|
|
20304
|
+
const g = groups.get(target);
|
|
20305
|
+
if (!g) continue;
|
|
20306
|
+
for (const op of g.operations) for (const grp of op.parameterGroups ?? []) if (!owner.has(grp.name)) owner.set(grp.name, target);
|
|
20307
|
+
}
|
|
20308
|
+
return owner;
|
|
20309
|
+
}
|
|
20310
|
+
/**
|
|
20311
|
+
* Collect all variant classes a given mount target owns. Variants are
|
|
20312
|
+
* inlined into the resource file (and its RBI counterpart) — Zeitwerk's
|
|
20313
|
+
* collapse convention means subdirectories under `lib/workos/<service>/`
|
|
20314
|
+
* don't add a namespace level, so files there can't define
|
|
20315
|
+
* `WorkOS::<Service>::<Variant>`. Inline definitions sidestep that.
|
|
20316
|
+
*
|
|
20317
|
+
* Variant parameter types are taken from the IR's leaf type. When the IR's
|
|
20318
|
+
* leaf is a bare primitive but the request body model has a richer type
|
|
20319
|
+
* (array/enum/model/map), we fall back to the body type to recover fidelity
|
|
20320
|
+
* the IR drops. Body nullability is stripped — when a parameter group is
|
|
20321
|
+
* optional, the body field for the group becomes nullable, but within a
|
|
20322
|
+
* variant the leaf is always required (selecting the variant means passing it).
|
|
20323
|
+
*/
|
|
20324
|
+
function collectVariantsForMountTarget(ctx, models, mountTarget) {
|
|
20325
|
+
const owner = buildGroupOwnerMap(ctx);
|
|
20326
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20327
|
+
const out = [];
|
|
20328
|
+
const g = groupByMount(ctx).get(mountTarget);
|
|
20329
|
+
if (!g) return out;
|
|
20330
|
+
for (const op of g.operations) {
|
|
20331
|
+
const bodyFieldTypes = collectBodyFieldTypes(op, models);
|
|
20332
|
+
for (const group of op.parameterGroups ?? []) {
|
|
20333
|
+
if (owner.get(group.name) !== mountTarget) continue;
|
|
20334
|
+
for (const variant of group.variants) {
|
|
20335
|
+
const cls = groupVariantClassName(group.name, variant.name);
|
|
20336
|
+
if (seen.has(cls)) continue;
|
|
20337
|
+
seen.add(cls);
|
|
20338
|
+
out.push({
|
|
20339
|
+
className: cls,
|
|
20340
|
+
groupName: group.name,
|
|
20341
|
+
variantName: variant.name,
|
|
20342
|
+
mountTarget,
|
|
20343
|
+
parameters: variant.parameters.map((p) => ({
|
|
20344
|
+
name: p.name,
|
|
20345
|
+
type: pickVariantParamType(p.type, bodyFieldTypes.get(p.name))
|
|
20346
|
+
}))
|
|
20347
|
+
});
|
|
20348
|
+
}
|
|
20349
|
+
}
|
|
20350
|
+
}
|
|
20351
|
+
return out;
|
|
20352
|
+
}
|
|
20353
|
+
/**
|
|
20354
|
+
* Pick the type for a variant leaf parameter.
|
|
20355
|
+
*
|
|
20356
|
+
* Prefer the IR's leaf type. Use the body model's type only when the IR is a
|
|
20357
|
+
* bare primitive but the body has a structured type — that's the original
|
|
20358
|
+
* fidelity-recovery case the body fallback was added for. Strip any outer
|
|
20359
|
+
* nullable from the body type, since body nullability reflects the parent
|
|
20360
|
+
* group's optionality, not the leaf's required-ness within the variant.
|
|
20361
|
+
*
|
|
20362
|
+
* Exported so the test emitter can recover the same type the variant class
|
|
20363
|
+
* declares — IR primitives for fields like `role_slugs` would otherwise stub
|
|
20364
|
+
* as `"stub"` strings instead of the `["stub"]` arrays the class accepts.
|
|
20365
|
+
*/
|
|
20366
|
+
function pickVariantParamType(irType, bodyType) {
|
|
20367
|
+
if (!bodyType) return irType;
|
|
20368
|
+
const unwrappedBody = bodyType.kind === "nullable" ? bodyType.inner : bodyType;
|
|
20369
|
+
const bodyIsStructured = unwrappedBody.kind === "array" || unwrappedBody.kind === "enum" || unwrappedBody.kind === "model" || unwrappedBody.kind === "map";
|
|
20370
|
+
if (irType.kind === "primitive" && bodyIsStructured) return unwrappedBody;
|
|
20371
|
+
return irType;
|
|
20372
|
+
}
|
|
20373
|
+
function readableName(name) {
|
|
20374
|
+
return name.replace(/_/g, " ");
|
|
20375
|
+
}
|
|
20376
|
+
/**
|
|
20377
|
+
* Render the inline `Data.define` block for a single variant, indented for
|
|
20378
|
+
* inclusion inside a `class <Service>` body. Returns an array of lines with
|
|
20379
|
+
* 4-space indent (the resource file's class members are 4-space indented).
|
|
20380
|
+
*/
|
|
20381
|
+
function emitInlineVariantClass(v) {
|
|
20382
|
+
const lines = [];
|
|
20383
|
+
lines.push(` # Identifies the ${readableName(v.groupName)} (${readableName(v.variantName)} variant).`);
|
|
20384
|
+
lines.push(" #");
|
|
20385
|
+
for (const p of v.parameters) {
|
|
20386
|
+
const yardType = mapTypeRefForYard(p.type);
|
|
20387
|
+
lines.push(` # @!attribute [r] ${fieldName(p.name)}`);
|
|
20388
|
+
lines.push(` # @return [${yardType}]`);
|
|
20389
|
+
}
|
|
20390
|
+
if (v.parameters.length === 0) lines.push(` ${v.className} = Data.define`);
|
|
20391
|
+
else {
|
|
20392
|
+
const fields = v.parameters.map((p) => `:${fieldName(p.name)}`).join(", ");
|
|
20393
|
+
lines.push(` ${v.className} = Data.define(${fields})`);
|
|
20394
|
+
}
|
|
20395
|
+
return lines;
|
|
20396
|
+
}
|
|
20397
|
+
/**
|
|
20398
|
+
* Render the inline RBI `class` block for a single variant, indented for
|
|
20399
|
+
* inclusion inside a `class <Service>` body in a service .rbi file. Returns
|
|
20400
|
+
* lines with 4-space indent.
|
|
20401
|
+
*/
|
|
20402
|
+
function emitInlineVariantRbi(v) {
|
|
20403
|
+
const lines = [];
|
|
20404
|
+
const fqcn = `WorkOS::${v.mountTarget}::${v.className}`;
|
|
20405
|
+
lines.push(` class ${v.className}`);
|
|
20406
|
+
for (const p of v.parameters) {
|
|
20407
|
+
lines.push(` sig { returns(${mapSorbetType$1(p.type)}) }`);
|
|
20408
|
+
lines.push(` def ${fieldName(p.name)}; end`);
|
|
20409
|
+
lines.push("");
|
|
20410
|
+
}
|
|
20411
|
+
if (v.parameters.length === 0) {
|
|
20412
|
+
lines.push(` sig { returns(${fqcn}) }`);
|
|
20413
|
+
lines.push(` def self.new; end`);
|
|
20414
|
+
} else {
|
|
20415
|
+
lines.push(" sig do");
|
|
20416
|
+
lines.push(" params(");
|
|
20417
|
+
for (let i = 0; i < v.parameters.length; i++) {
|
|
20418
|
+
const p = v.parameters[i];
|
|
20419
|
+
const sep = i === v.parameters.length - 1 ? "" : ",";
|
|
20420
|
+
lines.push(` ${fieldName(p.name)}: ${mapSorbetType$1(p.type)}${sep}`);
|
|
20421
|
+
}
|
|
20422
|
+
lines.push(` ).returns(${fqcn})`);
|
|
20423
|
+
lines.push(" end");
|
|
20424
|
+
const kwargs = v.parameters.map((p) => `${fieldName(p.name)}:`).join(", ");
|
|
20425
|
+
lines.push(` def self.new(${kwargs}); end`);
|
|
20426
|
+
}
|
|
20427
|
+
lines.push(" end");
|
|
20428
|
+
return lines;
|
|
20429
|
+
}
|
|
20430
|
+
//#endregion
|
|
20201
20431
|
//#region src/ruby/resources.ts
|
|
20202
20432
|
/**
|
|
20203
20433
|
* Generate Ruby resource (service) classes from IR services.
|
|
@@ -20214,6 +20444,7 @@ function generateResources(services, ctx) {
|
|
|
20214
20444
|
for (const m of ctx.spec.models) modelByName.set(m.name, m);
|
|
20215
20445
|
const listWrapperModels = /* @__PURE__ */ new Map();
|
|
20216
20446
|
for (const m of ctx.spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
|
|
20447
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
20217
20448
|
for (const [mountTarget, group] of groups) {
|
|
20218
20449
|
const cls = className(mountTarget);
|
|
20219
20450
|
const file = fileName(mountTarget);
|
|
@@ -20243,7 +20474,8 @@ function generateResources(services, ctx) {
|
|
|
20243
20474
|
modelNames,
|
|
20244
20475
|
modelByName,
|
|
20245
20476
|
listWrapperModels,
|
|
20246
|
-
requires
|
|
20477
|
+
requires,
|
|
20478
|
+
groupOwners
|
|
20247
20479
|
});
|
|
20248
20480
|
methodBodies.push(body);
|
|
20249
20481
|
if (resolved?.wrappers && resolved.wrappers.length > 0) {
|
|
@@ -20264,6 +20496,11 @@ function generateResources(services, ctx) {
|
|
|
20264
20496
|
}
|
|
20265
20497
|
lines.push("module WorkOS");
|
|
20266
20498
|
lines.push(` class ${cls}`);
|
|
20499
|
+
const variants = collectVariantsForMountTarget(ctx, ctx.spec.models, mountTarget);
|
|
20500
|
+
for (const v of variants) {
|
|
20501
|
+
for (const line of emitInlineVariantClass(v)) lines.push(line);
|
|
20502
|
+
lines.push("");
|
|
20503
|
+
}
|
|
20267
20504
|
lines.push(" def initialize(client)");
|
|
20268
20505
|
lines.push(" @client = client");
|
|
20269
20506
|
lines.push(" end");
|
|
@@ -20284,13 +20521,19 @@ function generateResources(services, ctx) {
|
|
|
20284
20521
|
}
|
|
20285
20522
|
/** Build a single Ruby method from an Operation. */
|
|
20286
20523
|
function emitMethod(args) {
|
|
20287
|
-
const { op, method, defaults, inferFromClient, hiddenParams, enumNames, modelNames, modelByName, listWrapperModels, requires } = args;
|
|
20524
|
+
const { op, method, defaults, inferFromClient, hiddenParams, enumNames, modelNames, modelByName, listWrapperModels, requires, groupOwners } = args;
|
|
20525
|
+
/** Fully-qualified Ruby constant for a variant (e.g. WorkOS::UserManagement::PasswordPlaintext). */
|
|
20526
|
+
const variantClassRef = (group, variantName) => {
|
|
20527
|
+
const owner = groupOwners.get(group.name);
|
|
20528
|
+
if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
|
|
20529
|
+
return scopedGroupVariantClassName(owner, group.name, variantName);
|
|
20530
|
+
};
|
|
20288
20531
|
planOperation(op);
|
|
20289
20532
|
const lines = [];
|
|
20290
20533
|
const pathParams = op.pathParams ?? [];
|
|
20291
20534
|
const groupedParamNames = collectGroupedParamNames(op);
|
|
20292
20535
|
const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
|
|
20293
|
-
const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName);
|
|
20536
|
+
const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName).filter((f) => !groupedParamNames.has(f.name));
|
|
20294
20537
|
const pathParamNames = new Set(pathParams.map((p) => safeParamName(p.name)));
|
|
20295
20538
|
const bodyFieldRenames = /* @__PURE__ */ new Map();
|
|
20296
20539
|
for (const f of bodyFields) {
|
|
@@ -20358,7 +20601,7 @@ function emitMethod(args) {
|
|
|
20358
20601
|
sigParts.push(`${n}: nil`);
|
|
20359
20602
|
}
|
|
20360
20603
|
sigParts.push("request_options: {}");
|
|
20361
|
-
const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels);
|
|
20604
|
+
const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef);
|
|
20362
20605
|
for (const line of doc) lines.push(` ${line}`);
|
|
20363
20606
|
if (sigParts.length === 0) lines.push(` def ${method}`);
|
|
20364
20607
|
else if (sigParts.length === 1 && sigParts[0].length < 60) lines.push(` def ${method}(${sigParts[0]})`);
|
|
@@ -20384,28 +20627,32 @@ function emitMethod(args) {
|
|
|
20384
20627
|
const groupsGoToQuery = hasGroups && !hasBodyMethod;
|
|
20385
20628
|
const hasQuery = qEntries.length > 0 || groupsGoToQuery;
|
|
20386
20629
|
if (hasQuery) {
|
|
20630
|
+
const queryCompact = qEntries.some((q) => !q.required) ? ".compact" : "";
|
|
20387
20631
|
lines.push(" params = {");
|
|
20388
20632
|
for (let i = 0; i < qEntries.length; i++) {
|
|
20389
20633
|
const q = qEntries[i];
|
|
20390
20634
|
const sep = i === qEntries.length - 1 && !groupsGoToQuery ? "" : ",";
|
|
20391
20635
|
lines.push(` ${rubyStringLit(q.name)} => ${safeParamName(q.name)}${sep}`);
|
|
20392
20636
|
}
|
|
20393
|
-
lines.push(
|
|
20637
|
+
lines.push(` }${queryCompact}`);
|
|
20394
20638
|
if (groupsGoToQuery) for (const group of op.parameterGroups ?? []) {
|
|
20395
20639
|
const prop = fieldName(group.name);
|
|
20396
20640
|
if (group.optional) {
|
|
20397
20641
|
lines.push(` if ${prop}`);
|
|
20398
|
-
lines.push(` case ${prop}
|
|
20399
|
-
} else lines.push(` case ${prop}
|
|
20642
|
+
lines.push(` case ${prop}`);
|
|
20643
|
+
} else lines.push(` case ${prop}`);
|
|
20400
20644
|
for (const variant of group.variants) {
|
|
20401
|
-
|
|
20402
|
-
|
|
20645
|
+
const variantClass = variantClassRef(group, variant.name);
|
|
20646
|
+
lines.push(` when ${variantClass}`);
|
|
20647
|
+
for (const p of variant.parameters) lines.push(` params[${rubyStringLit(p.name)}] = ${prop}.${fieldName(p.name)}`);
|
|
20403
20648
|
}
|
|
20649
|
+
lines.push(` else`);
|
|
20650
|
+
lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
|
|
20404
20651
|
lines.push(" end");
|
|
20405
20652
|
if (group.optional) lines.push(" end");
|
|
20406
20653
|
}
|
|
20407
20654
|
}
|
|
20408
|
-
const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http);
|
|
20655
|
+
const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http) || hasGroups && hasBodyMethod;
|
|
20409
20656
|
if (hasBody) {
|
|
20410
20657
|
const bodyEntries = [];
|
|
20411
20658
|
for (const [k, v] of Object.entries(defaults)) {
|
|
@@ -20417,26 +20664,32 @@ function emitMethod(args) {
|
|
|
20417
20664
|
const optKey = fc === "client_secret" ? "api_key" : fc;
|
|
20418
20665
|
bodyEntries.push(`${rubyStringLit(fc)} => (request_options[:${optKey}] || @client.${clientProp})`);
|
|
20419
20666
|
}
|
|
20667
|
+
let bodyHasNilable = false;
|
|
20420
20668
|
for (const f of bodyFields) {
|
|
20421
20669
|
if (hiddenParams.has(f.name)) continue;
|
|
20422
20670
|
bodyEntries.push(`${rubyStringLit(f.name)} => ${bodyKwargName(f.name)}`);
|
|
20671
|
+
if (!f.required) bodyHasNilable = true;
|
|
20423
20672
|
}
|
|
20673
|
+
const bodyCompact = bodyHasNilable ? ".compact" : "";
|
|
20424
20674
|
lines.push(" body = {");
|
|
20425
20675
|
for (let i = 0; i < bodyEntries.length; i++) {
|
|
20426
20676
|
const sep = i === bodyEntries.length - 1 ? "" : ",";
|
|
20427
20677
|
lines.push(` ${bodyEntries[i]}${sep}`);
|
|
20428
20678
|
}
|
|
20429
|
-
lines.push(
|
|
20679
|
+
lines.push(` }${bodyCompact}`);
|
|
20430
20680
|
if (hasGroups && hasBodyMethod) for (const group of op.parameterGroups ?? []) {
|
|
20431
20681
|
const prop = fieldName(group.name);
|
|
20432
20682
|
if (group.optional) {
|
|
20433
20683
|
lines.push(` if ${prop}`);
|
|
20434
|
-
lines.push(` case ${prop}
|
|
20435
|
-
} else lines.push(` case ${prop}
|
|
20684
|
+
lines.push(` case ${prop}`);
|
|
20685
|
+
} else lines.push(` case ${prop}`);
|
|
20436
20686
|
for (const variant of group.variants) {
|
|
20437
|
-
|
|
20438
|
-
|
|
20687
|
+
const variantClass = variantClassRef(group, variant.name);
|
|
20688
|
+
lines.push(` when ${variantClass}`);
|
|
20689
|
+
for (const p of variant.parameters) lines.push(` body[${rubyStringLit(p.name)}] = ${prop}.${fieldName(p.name)}`);
|
|
20439
20690
|
}
|
|
20691
|
+
lines.push(` else`);
|
|
20692
|
+
lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
|
|
20440
20693
|
lines.push(" end");
|
|
20441
20694
|
if (group.optional) lines.push(" end");
|
|
20442
20695
|
}
|
|
@@ -20651,7 +20904,7 @@ function oneLine(desc) {
|
|
|
20651
20904
|
if (!desc) return "";
|
|
20652
20905
|
return desc.replace(/\r/g, " ").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
|
|
20653
20906
|
}
|
|
20654
|
-
function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels) {
|
|
20907
|
+
function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef) {
|
|
20655
20908
|
const lines = [];
|
|
20656
20909
|
const firstLine = (op.description ?? `${op.httpMethod.toUpperCase()} ${op.path}`).split("\n")[0] ?? "";
|
|
20657
20910
|
lines.push(`# ${firstLine}`);
|
|
@@ -20687,6 +20940,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
|
|
|
20687
20940
|
const deprecatedPrefix = q.deprecated ? "(deprecated) " : "";
|
|
20688
20941
|
lines.push(`# @param ${n} [${type}${suffix}] ${deprecatedPrefix}${oneLine(q.description)}`.trim());
|
|
20689
20942
|
}
|
|
20943
|
+
for (const group of op.parameterGroups ?? []) {
|
|
20944
|
+
const n = fieldName(group.name);
|
|
20945
|
+
if (emittedParamNames.has(n)) continue;
|
|
20946
|
+
emittedParamNames.add(n);
|
|
20947
|
+
const variantTypes = group.variants.map((v) => variantClassRef(group, v.name)).join(", ");
|
|
20948
|
+
const suffix = group.optional ? ", nil" : "";
|
|
20949
|
+
lines.push(`# @param ${n} [${variantTypes}${suffix}] Identifies the ${group.name.replace(/_/g, " ")}.`);
|
|
20950
|
+
}
|
|
20690
20951
|
lines.push(`# @param request_options [Hash] (see WorkOS::Types::RequestOptions)`);
|
|
20691
20952
|
const ref = op.response;
|
|
20692
20953
|
if (ref.kind === "primitive" && ref.type === "unknown") lines.push(`# @return [void]`);
|
|
@@ -20709,6 +20970,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
|
|
|
20709
20970
|
function rubyStringLit(s) {
|
|
20710
20971
|
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
20711
20972
|
}
|
|
20973
|
+
/**
|
|
20974
|
+
* Build a Ruby double-quoted string expression for the `else raise ArgumentError`
|
|
20975
|
+
* arm of a parameter-group dispatcher. Lists the expected variant classes and
|
|
20976
|
+
* interpolates the actual class of the value the caller passed.
|
|
20977
|
+
*/
|
|
20978
|
+
function dispatchErrorLiteral(group, prop, variantClassRef) {
|
|
20979
|
+
return `"expected ${prop} to be one of: ${group.variants.map((v) => variantClassRef(group, v.name)).join(", ")}, got #{${prop}.class}"`;
|
|
20980
|
+
}
|
|
20712
20981
|
//#endregion
|
|
20713
20982
|
//#region src/ruby/client.ts
|
|
20714
20983
|
/**
|
|
@@ -20938,9 +21207,11 @@ function generateClientClass(spec, ctx) {
|
|
|
20938
21207
|
function generateTests(spec, ctx) {
|
|
20939
21208
|
const files = [];
|
|
20940
21209
|
const groups = groupByMount(ctx);
|
|
21210
|
+
const models = spec.models;
|
|
20941
21211
|
const modelByName = /* @__PURE__ */ new Map();
|
|
20942
|
-
for (const m of
|
|
21212
|
+
for (const m of models) modelByName.set(m.name, m);
|
|
20943
21213
|
const lookup = buildResolvedLookup(ctx);
|
|
21214
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
20944
21215
|
for (const [mountTarget, group] of groups) {
|
|
20945
21216
|
const cls = className(mountTarget);
|
|
20946
21217
|
const prop = servicePropertyName(mountTarget);
|
|
@@ -20969,7 +21240,9 @@ function generateTests(spec, ctx) {
|
|
|
20969
21240
|
op.httpMethod.toLowerCase();
|
|
20970
21241
|
const httpMethodSym = `:${op.httpMethod.toLowerCase()}`;
|
|
20971
21242
|
const resolved = lookupResolved(op, lookup);
|
|
20972
|
-
const
|
|
21243
|
+
const hiddenParams = buildHiddenParams$1(resolved);
|
|
21244
|
+
const callArgs = buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models);
|
|
21245
|
+
const bodyMatcher = buildBodyMatcher(op, modelByName, hiddenParams, models);
|
|
20973
21246
|
authMethodManifest.push({
|
|
20974
21247
|
method,
|
|
20975
21248
|
httpMethodSym,
|
|
@@ -20978,23 +21251,47 @@ function generateTests(spec, ctx) {
|
|
|
20978
21251
|
});
|
|
20979
21252
|
const stubRegex = stubUrlRegex(stubUrl);
|
|
20980
21253
|
lines.push(` def test_${method}_returns_expected_result`);
|
|
21254
|
+
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21255
|
+
if (bodyMatcher) lines.push(` .with(body: ${bodyMatcher})`);
|
|
20981
21256
|
if (isList) {
|
|
20982
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
20983
21257
|
lines.push(` .to_return(body: '{"data": [], "list_metadata": {}}', status: 200)`);
|
|
20984
21258
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
20985
21259
|
lines.push(" assert_kind_of WorkOS::Types::ListStruct, result");
|
|
20986
21260
|
} else if (op.response.kind === "primitive") {
|
|
20987
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
20988
21261
|
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
20989
21262
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
20990
21263
|
lines.push(" assert_nil result");
|
|
20991
21264
|
} else {
|
|
20992
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
20993
21265
|
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
20994
21266
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
20995
21267
|
lines.push(" refute_nil result");
|
|
20996
21268
|
}
|
|
20997
21269
|
lines.push(" end");
|
|
21270
|
+
for (const group of op.parameterGroups ?? []) for (let vi = 1; vi < group.variants.length; vi++) {
|
|
21271
|
+
const variant = group.variants[vi];
|
|
21272
|
+
const overrides = new Map([[group.name, vi]]);
|
|
21273
|
+
const variantCallArgs = buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models, overrides);
|
|
21274
|
+
const variantBodyMatcher = buildBodyMatcher(op, modelByName, hiddenParams, models, overrides);
|
|
21275
|
+
const suffix = `with_${fieldName(group.name)}_${fieldName(variant.name)}`;
|
|
21276
|
+
lines.push("");
|
|
21277
|
+
lines.push(` def test_${method}_${suffix}_returns_expected_result`);
|
|
21278
|
+
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21279
|
+
if (variantBodyMatcher) lines.push(` .with(body: ${variantBodyMatcher})`);
|
|
21280
|
+
if (isList) {
|
|
21281
|
+
lines.push(` .to_return(body: '{"data": [], "list_metadata": {}}', status: 200)`);
|
|
21282
|
+
lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
|
|
21283
|
+
lines.push(" assert_kind_of WorkOS::Types::ListStruct, result");
|
|
21284
|
+
} else if (op.response.kind === "primitive") {
|
|
21285
|
+
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
21286
|
+
lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
|
|
21287
|
+
lines.push(" assert_nil result");
|
|
21288
|
+
} else {
|
|
21289
|
+
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
21290
|
+
lines.push(` result = @client.${prop}.${method}(${variantCallArgs})`);
|
|
21291
|
+
lines.push(" refute_nil result");
|
|
21292
|
+
}
|
|
21293
|
+
lines.push(" end");
|
|
21294
|
+
}
|
|
20998
21295
|
if (resolved?.wrappers && resolved.wrappers.length > 0) for (const wrapper of resolved.wrappers) emitWrapperTests({
|
|
20999
21296
|
lines,
|
|
21000
21297
|
wrapper,
|
|
@@ -21125,8 +21422,12 @@ function roundTripStub(ref, enumNames) {
|
|
|
21125
21422
|
default: return `nil`;
|
|
21126
21423
|
}
|
|
21127
21424
|
}
|
|
21128
|
-
/** Build minimal placeholder arguments for calling the SDK method from a test.
|
|
21129
|
-
|
|
21425
|
+
/** Build minimal placeholder arguments for calling the SDK method from a test.
|
|
21426
|
+
* `variantOverrides` selects a non-zero variant index per group; absent groups
|
|
21427
|
+
* default to variant 0. Used to emit per-variant test cases that exercise the
|
|
21428
|
+
* second/third arm of each parameter-group dispatcher.
|
|
21429
|
+
*/
|
|
21430
|
+
function buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models, variantOverrides = /* @__PURE__ */ new Map()) {
|
|
21130
21431
|
const parts = [];
|
|
21131
21432
|
const seen = /* @__PURE__ */ new Set();
|
|
21132
21433
|
const pathParamNames = /* @__PURE__ */ new Set();
|
|
@@ -21158,17 +21459,68 @@ function buildCallArgsStub(op, modelByName, hiddenParams) {
|
|
|
21158
21459
|
seen.add(name);
|
|
21159
21460
|
parts.push(`${name}: ${stubValueFor(q.type)}`);
|
|
21160
21461
|
}
|
|
21462
|
+
const bodyFieldTypes = collectBodyFieldTypes(op, models);
|
|
21161
21463
|
for (const group of op.parameterGroups ?? []) {
|
|
21162
|
-
if (group.optional) continue;
|
|
21163
21464
|
const name = fieldName(group.name);
|
|
21164
21465
|
if (seen.has(name)) continue;
|
|
21165
21466
|
seen.add(name);
|
|
21166
|
-
const
|
|
21167
|
-
|
|
21168
|
-
|
|
21467
|
+
const idx = variantOverrides.get(group.name) ?? 0;
|
|
21468
|
+
const variant = group.variants[idx];
|
|
21469
|
+
if (variant) {
|
|
21470
|
+
const owner = groupOwners.get(group.name);
|
|
21471
|
+
if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
|
|
21472
|
+
const variantClass = scopedGroupVariantClassName(owner, group.name, variant.name);
|
|
21473
|
+
const fieldStubs = variant.parameters.map((p) => `${fieldName(p.name)}: ${stubValueFor(pickVariantParamType(p.type, bodyFieldTypes.get(p.name)))}`).join(", ");
|
|
21474
|
+
parts.push(`${name}: ${variantClass}.new(${fieldStubs})`);
|
|
21475
|
+
}
|
|
21169
21476
|
}
|
|
21170
21477
|
return parts.join(", ");
|
|
21171
21478
|
}
|
|
21479
|
+
/**
|
|
21480
|
+
* Build a Ruby `hash_including(...)` matcher describing the wire body the
|
|
21481
|
+
* SDK should send for an operation whose body is constructed (in part) by a
|
|
21482
|
+
* parameter-group dispatcher. Returns `null` for operations without body
|
|
21483
|
+
* groups — those are still stubbed without a body matcher.
|
|
21484
|
+
*
|
|
21485
|
+
* The matcher includes every required non-group body field plus the first
|
|
21486
|
+
* variant's wire-name leaves for each group dispatched into the body. This
|
|
21487
|
+
* catches regressions where the dispatcher silently drops a passed group
|
|
21488
|
+
* (the original `update_organization_membership` regression).
|
|
21489
|
+
*/
|
|
21490
|
+
function buildBodyMatcher(op, modelByName, hiddenParams, models, variantOverrides = /* @__PURE__ */ new Map()) {
|
|
21491
|
+
const httpMethod = op.httpMethod.toLowerCase();
|
|
21492
|
+
const hasBodyMethod = ![
|
|
21493
|
+
"get",
|
|
21494
|
+
"head",
|
|
21495
|
+
"delete"
|
|
21496
|
+
].includes(httpMethod);
|
|
21497
|
+
const hasGroups = (op.parameterGroups?.length ?? 0) > 0;
|
|
21498
|
+
if (!hasBodyMethod || !hasGroups) return null;
|
|
21499
|
+
const groupedParamNames = /* @__PURE__ */ new Set();
|
|
21500
|
+
for (const group of op.parameterGroups ?? []) for (const variant of group.variants) for (const p of variant.parameters) groupedParamNames.add(p.name);
|
|
21501
|
+
const entries = [];
|
|
21502
|
+
if (op.requestBody) {
|
|
21503
|
+
const bodyModel = resolveBodyModel(op.requestBody, modelByName);
|
|
21504
|
+
if (bodyModel) for (const f of bodyModel.fields) {
|
|
21505
|
+
if (!f.required) continue;
|
|
21506
|
+
if (hiddenParams.has(f.name)) continue;
|
|
21507
|
+
if (groupedParamNames.has(f.name)) continue;
|
|
21508
|
+
entries.push(`"${f.name}" => ${stubValueFor(f.type)}`);
|
|
21509
|
+
}
|
|
21510
|
+
}
|
|
21511
|
+
const bodyFieldTypes = collectBodyFieldTypes(op, models);
|
|
21512
|
+
for (const group of op.parameterGroups ?? []) {
|
|
21513
|
+
const idx = variantOverrides.get(group.name) ?? 0;
|
|
21514
|
+
const variant = group.variants[idx];
|
|
21515
|
+
if (!variant) continue;
|
|
21516
|
+
for (const p of variant.parameters) {
|
|
21517
|
+
const recovered = pickVariantParamType(p.type, bodyFieldTypes.get(p.name));
|
|
21518
|
+
entries.push(`"${p.name}" => ${stubValueFor(recovered)}`);
|
|
21519
|
+
}
|
|
21520
|
+
}
|
|
21521
|
+
if (entries.length === 0) return null;
|
|
21522
|
+
return `hash_including(${entries.join(", ")})`;
|
|
21523
|
+
}
|
|
21172
21524
|
function resolveBodyModel(ref, modelByName) {
|
|
21173
21525
|
if (ref.kind === "model") return modelByName.get(ref.name) ?? null;
|
|
21174
21526
|
if (ref.kind === "nullable") return resolveBodyModel(ref.inner, modelByName);
|
|
@@ -21222,7 +21574,7 @@ function stubValueFor(ref) {
|
|
|
21222
21574
|
case "boolean": return `true`;
|
|
21223
21575
|
default: return `nil`;
|
|
21224
21576
|
}
|
|
21225
|
-
case "array": return `[]`;
|
|
21577
|
+
case "array": return `[${stubValueFor(ref.items)}]`;
|
|
21226
21578
|
case "map": return `{}`;
|
|
21227
21579
|
case "enum": return `"stub"`;
|
|
21228
21580
|
case "literal":
|
|
@@ -21357,6 +21709,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21357
21709
|
for (const m of spec.models) modelByName.set(m.name, m);
|
|
21358
21710
|
const listWrapperModels = /* @__PURE__ */ new Map();
|
|
21359
21711
|
for (const m of spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
|
|
21712
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
21360
21713
|
for (const [mountTarget, group] of groups) {
|
|
21361
21714
|
const cls = className(mountTarget);
|
|
21362
21715
|
const lines = [];
|
|
@@ -21364,6 +21717,11 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21364
21717
|
lines.push("");
|
|
21365
21718
|
lines.push("module WorkOS");
|
|
21366
21719
|
lines.push(` class ${cls}`);
|
|
21720
|
+
const variants = collectVariantsForMountTarget(ctx, spec.models, mountTarget);
|
|
21721
|
+
for (const v of variants) {
|
|
21722
|
+
for (const line of emitInlineVariantRbi(v)) lines.push(line);
|
|
21723
|
+
lines.push("");
|
|
21724
|
+
}
|
|
21367
21725
|
lines.push(" sig { params(client: WorkOS::BaseClient).void }");
|
|
21368
21726
|
lines.push(" def initialize(client); end");
|
|
21369
21727
|
lines.push("");
|
|
@@ -21380,7 +21738,15 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21380
21738
|
const hiddenParams = buildHiddenParams$1(resolved);
|
|
21381
21739
|
const groupedParamNames = collectGroupedParamNames(op);
|
|
21382
21740
|
const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
|
|
21383
|
-
const bodyFields = getRequestBodyFieldsFlat(op, hiddenParams, modelByName);
|
|
21741
|
+
const bodyFields = getRequestBodyFieldsFlat(op, hiddenParams, modelByName).filter((f) => !groupedParamNames.has(f.name));
|
|
21742
|
+
const parameterGroups = op.parameterGroups ?? [];
|
|
21743
|
+
const groupSorbetType = (group) => {
|
|
21744
|
+
const owner = groupOwners.get(group.name);
|
|
21745
|
+
if (!owner) throw new Error(`No owner mount target found for parameter group '${group.name}'`);
|
|
21746
|
+
const variants = group.variants.map((v) => scopedGroupVariantClassName(owner, group.name, v.name));
|
|
21747
|
+
if (variants.length === 1) return variants[0];
|
|
21748
|
+
return `T.any(${variants.join(", ")})`;
|
|
21749
|
+
};
|
|
21384
21750
|
const sigParams = [];
|
|
21385
21751
|
const seen = /* @__PURE__ */ new Set();
|
|
21386
21752
|
for (const p of op.pathParams ?? []) {
|
|
@@ -21405,6 +21771,13 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21405
21771
|
seen.add(n);
|
|
21406
21772
|
sigParams.push(`${n}: ${mapSorbetType(q.type)}`);
|
|
21407
21773
|
}
|
|
21774
|
+
for (const group of parameterGroups) {
|
|
21775
|
+
if (group.optional) continue;
|
|
21776
|
+
const n = fieldName(group.name);
|
|
21777
|
+
if (seen.has(n)) continue;
|
|
21778
|
+
seen.add(n);
|
|
21779
|
+
sigParams.push(`${n}: ${groupSorbetType(group)}`);
|
|
21780
|
+
}
|
|
21408
21781
|
for (const f of bodyFields) {
|
|
21409
21782
|
if (hiddenParams.has(f.name)) continue;
|
|
21410
21783
|
if (f.required) continue;
|
|
@@ -21421,6 +21794,13 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21421
21794
|
seen.add(n);
|
|
21422
21795
|
sigParams.push(`${n}: T.nilable(${unwrapNilable(mapSorbetType(q.type))})`);
|
|
21423
21796
|
}
|
|
21797
|
+
for (const group of parameterGroups) {
|
|
21798
|
+
if (!group.optional) continue;
|
|
21799
|
+
const n = fieldName(group.name);
|
|
21800
|
+
if (seen.has(n)) continue;
|
|
21801
|
+
seen.add(n);
|
|
21802
|
+
sigParams.push(`${n}: T.nilable(${groupSorbetType(group)})`);
|
|
21803
|
+
}
|
|
21424
21804
|
sigParams.push("request_options: T::Hash[Symbol, T.untyped]");
|
|
21425
21805
|
const retType = mapSorbetReturnType(op.response, listWrapperModels, modelNames);
|
|
21426
21806
|
lines.push(" sig do");
|
|
@@ -21595,4 +21975,4 @@ const workosEmittersPlugin = {
|
|
|
21595
21975
|
//#endregion
|
|
21596
21976
|
export { nodeEmitter as _, rustExtractor as a, pythonExtractor as c, rubyEmitter as d, kotlinEmitter as f, pythonEmitter as g, phpEmitter as h, kotlinExtractor as i, rubyExtractor as l, goEmitter as m, elixirExtractor as n, goExtractor as o, dotnetEmitter as p, dotnetExtractor as r, phpExtractor as s, workosEmittersPlugin as t, nodeExtractor as u };
|
|
21597
21977
|
|
|
21598
|
-
//# sourceMappingURL=plugin-
|
|
21978
|
+
//# sourceMappingURL=plugin-Cmg_LFtm.mjs.map
|