@workos/oagen-emitters 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +1 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-Bk0xWTQC.mjs → plugin-Bp46oZIh.mjs} +397 -52
- package/dist/plugin-Bp46oZIh.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +2 -2
- package/src/node/tests.ts +1 -1
- package/src/node/utils.ts +1 -1
- package/src/python/models.ts +1 -1
- package/src/python/resources.ts +1 -1
- package/src/python/tests.ts +2 -2
- package/src/ruby/client.ts +1 -1
- package/src/ruby/index.ts +4 -2
- package/src/ruby/models.ts +1 -1
- 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/dist/plugin-Bk0xWTQC.mjs.map +0 -1
|
@@ -3016,7 +3016,7 @@ function buildKnownTypeNames(models, ctx) {
|
|
|
3016
3016
|
* the output directory for a given IR service name.
|
|
3017
3017
|
*/
|
|
3018
3018
|
function createServiceDirResolver(models, services, ctx) {
|
|
3019
|
-
const modelToService = assignModelsToServices(models, services);
|
|
3019
|
+
const modelToService = assignModelsToServices(models, services, ctx.modelHints);
|
|
3020
3020
|
const serviceNameMap = buildServiceNameMap(services, ctx);
|
|
3021
3021
|
const resolveDir = (irService) => irService ? resolveServiceDir$1(serviceNameMap.get(irService) ?? irService) : "common";
|
|
3022
3022
|
return {
|
|
@@ -6532,7 +6532,7 @@ function modelNeedsRoundTripTest(model) {
|
|
|
6532
6532
|
*/
|
|
6533
6533
|
function generateSerializerTests(spec, ctx) {
|
|
6534
6534
|
const files = [];
|
|
6535
|
-
const modelToService = assignModelsToServices$1(spec.models, spec.services);
|
|
6535
|
+
const modelToService = assignModelsToServices$1(spec.models, spec.services, ctx.modelHints);
|
|
6536
6536
|
const serviceNameMap = /* @__PURE__ */ new Map();
|
|
6537
6537
|
for (const service of spec.services) serviceNameMap.set(service.name, resolveResourceClassName$3(service, ctx));
|
|
6538
6538
|
const resolveDir = (irService) => irService ? resolveServiceDir$1(serviceNameMap.get(irService) ?? irService) : "common";
|
|
@@ -7100,7 +7100,7 @@ function assignEnumsToServices(enums, services) {
|
|
|
7100
7100
|
*/
|
|
7101
7101
|
function generateModels$5(models, ctx) {
|
|
7102
7102
|
if (models.length === 0) return [];
|
|
7103
|
-
const modelToService = assignModelsToServices(models, ctx.spec.services);
|
|
7103
|
+
const modelToService = assignModelsToServices(models, ctx.spec.services, ctx.modelHints);
|
|
7104
7104
|
const enumToService = assignEnumsToServices(ctx.spec.enums, ctx.spec.services);
|
|
7105
7105
|
const mountDirMap = buildMountDirMap$1(ctx);
|
|
7106
7106
|
const resolveDir = (irService) => irService ? mountDirMap.get(irService) ?? "common" : "common";
|
|
@@ -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}):`);
|
|
@@ -8453,7 +8453,7 @@ function generateResources$5(services, ctx) {
|
|
|
8453
8453
|
if (enumImports.size > 0) lines.push(`from ${importPrefix}_types import RequestOptions, enum_value`);
|
|
8454
8454
|
else lines.push(`from ${importPrefix}_types import RequestOptions`);
|
|
8455
8455
|
const actualModelImports = [...modelImports];
|
|
8456
|
-
const modelToServiceMap = assignModelsToServices(ctx.spec.models, ctx.spec.services);
|
|
8456
|
+
const modelToServiceMap = assignModelsToServices(ctx.spec.models, ctx.spec.services, ctx.modelHints);
|
|
8457
8457
|
for (const model of ctx.spec.models) if (model.discriminator) {
|
|
8458
8458
|
const svc = modelToServiceMap.get(model.name);
|
|
8459
8459
|
if (svc) modelToServiceMap.set(model.name + "Variant", svc);
|
|
@@ -9270,7 +9270,7 @@ function generateServiceTest$2(service, spec, ctx, accessPaths, resolvedOps) {
|
|
|
9270
9270
|
if (!model) return true;
|
|
9271
9271
|
return !isListWrapperModel(model);
|
|
9272
9272
|
});
|
|
9273
|
-
const modelToServiceMap = assignModelsToServices(spec.models, spec.services);
|
|
9273
|
+
const modelToServiceMap = assignModelsToServices(spec.models, spec.services, ctx.modelHints);
|
|
9274
9274
|
const mountDirMap = buildMountDirMap$1(ctx);
|
|
9275
9275
|
const resolveModelDir = (modelName) => {
|
|
9276
9276
|
const svc = modelToServiceMap.get(modelName);
|
|
@@ -10120,7 +10120,7 @@ function generateModelRoundTripTests(spec, ctx) {
|
|
|
10120
10120
|
for (const name of responseModelNames) requestOnlyModelNames.delete(name);
|
|
10121
10121
|
const models = spec.models.filter((m) => !isListWrapperModel(m) && !isListMetadataModel(m) && !requestOnlyModelNames.has(m.name));
|
|
10122
10122
|
if (models.length === 0) return null;
|
|
10123
|
-
const modelToService = assignModelsToServices(spec.models, spec.services);
|
|
10123
|
+
const modelToService = assignModelsToServices(spec.models, spec.services, ctx.modelHints);
|
|
10124
10124
|
const roundTripDirMap = buildMountDirMap$1(ctx);
|
|
10125
10125
|
const resolveDir = (irService) => irService ? roundTripDirMap.get(irService) ?? "common" : "common";
|
|
10126
10126
|
const lines = [];
|
|
@@ -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
|
}
|
|
@@ -15565,7 +15565,7 @@ function groupBaseClassName(mountName, groupName) {
|
|
|
15565
15565
|
return `${className$2(mountName)}${className$2(groupName)}`;
|
|
15566
15566
|
}
|
|
15567
15567
|
/** Concrete variant class name (e.g. UserManagementRoleSingle). */
|
|
15568
|
-
function groupVariantClassName(mountName, groupName, variantName) {
|
|
15568
|
+
function groupVariantClassName$1(mountName, groupName, variantName) {
|
|
15569
15569
|
return `${className$2(mountName)}${className$2(groupName)}${className$2(variantName)}`;
|
|
15570
15570
|
}
|
|
15571
15571
|
/**
|
|
@@ -15583,7 +15583,7 @@ function generateParameterGroupTypes(mountName, op, models, emitted) {
|
|
|
15583
15583
|
lines.push("");
|
|
15584
15584
|
lines.push(` public abstract class ${baseName} { }`);
|
|
15585
15585
|
for (const variant of group.variants) {
|
|
15586
|
-
const variantName = groupVariantClassName(mountName, group.name, variant.name);
|
|
15586
|
+
const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
|
|
15587
15587
|
lines.push("");
|
|
15588
15588
|
lines.push(` public class ${variantName} : ${baseName}`);
|
|
15589
15589
|
lines.push(" {");
|
|
@@ -15611,7 +15611,7 @@ function emitGroupSerialization(mountName, op, indent, models, target) {
|
|
|
15611
15611
|
const groupField = fieldName$1(group.name);
|
|
15612
15612
|
let first = true;
|
|
15613
15613
|
for (const variant of group.variants) {
|
|
15614
|
-
const variantName = groupVariantClassName(mountName, group.name, variant.name);
|
|
15614
|
+
const variantName = groupVariantClassName$1(mountName, group.name, variant.name);
|
|
15615
15615
|
const localVar = localName(variant.name);
|
|
15616
15616
|
const keyword = first ? "if" : "else if";
|
|
15617
15617
|
first = false;
|
|
@@ -19677,6 +19677,22 @@ function safeParamName(name) {
|
|
|
19677
19677
|
function moduleName(name) {
|
|
19678
19678
|
return toSnakeCase(name);
|
|
19679
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
|
+
}
|
|
19680
19696
|
/** snake_case property name for service accessors on the client. */
|
|
19681
19697
|
function servicePropertyName(name) {
|
|
19682
19698
|
return toSnakeCase(name);
|
|
@@ -19762,7 +19778,7 @@ function generateModels(models, ctx) {
|
|
|
19762
19778
|
if (models.length === 0) return [];
|
|
19763
19779
|
const enumNames = new Set(ctx.spec.enums.map((e) => e.name));
|
|
19764
19780
|
const modelNames = new Set(models.map((m) => m.name));
|
|
19765
|
-
const modelToService = assignModelsToServices(models, ctx.spec.services);
|
|
19781
|
+
const modelToService = assignModelsToServices(models, ctx.spec.services, ctx.modelHints);
|
|
19766
19782
|
const mountDirMap = buildMountDirMap(ctx);
|
|
19767
19783
|
const dirFor = (modelName) => {
|
|
19768
19784
|
const service = modelToService.get(modelName);
|
|
@@ -20233,6 +20249,185 @@ function rubyStringLit$1(s) {
|
|
|
20233
20249
|
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
20234
20250
|
}
|
|
20235
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
|
|
20236
20431
|
//#region src/ruby/resources.ts
|
|
20237
20432
|
/**
|
|
20238
20433
|
* Generate Ruby resource (service) classes from IR services.
|
|
@@ -20249,6 +20444,7 @@ function generateResources(services, ctx) {
|
|
|
20249
20444
|
for (const m of ctx.spec.models) modelByName.set(m.name, m);
|
|
20250
20445
|
const listWrapperModels = /* @__PURE__ */ new Map();
|
|
20251
20446
|
for (const m of ctx.spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
|
|
20447
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
20252
20448
|
for (const [mountTarget, group] of groups) {
|
|
20253
20449
|
const cls = className(mountTarget);
|
|
20254
20450
|
const file = fileName(mountTarget);
|
|
@@ -20278,7 +20474,8 @@ function generateResources(services, ctx) {
|
|
|
20278
20474
|
modelNames,
|
|
20279
20475
|
modelByName,
|
|
20280
20476
|
listWrapperModels,
|
|
20281
|
-
requires
|
|
20477
|
+
requires,
|
|
20478
|
+
groupOwners
|
|
20282
20479
|
});
|
|
20283
20480
|
methodBodies.push(body);
|
|
20284
20481
|
if (resolved?.wrappers && resolved.wrappers.length > 0) {
|
|
@@ -20299,6 +20496,11 @@ function generateResources(services, ctx) {
|
|
|
20299
20496
|
}
|
|
20300
20497
|
lines.push("module WorkOS");
|
|
20301
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
|
+
}
|
|
20302
20504
|
lines.push(" def initialize(client)");
|
|
20303
20505
|
lines.push(" @client = client");
|
|
20304
20506
|
lines.push(" end");
|
|
@@ -20319,13 +20521,19 @@ function generateResources(services, ctx) {
|
|
|
20319
20521
|
}
|
|
20320
20522
|
/** Build a single Ruby method from an Operation. */
|
|
20321
20523
|
function emitMethod(args) {
|
|
20322
|
-
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
|
+
};
|
|
20323
20531
|
planOperation(op);
|
|
20324
20532
|
const lines = [];
|
|
20325
20533
|
const pathParams = op.pathParams ?? [];
|
|
20326
20534
|
const groupedParamNames = collectGroupedParamNames(op);
|
|
20327
20535
|
const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
|
|
20328
|
-
const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName);
|
|
20536
|
+
const bodyFields = getRequestBodyFields(op, hiddenParams, modelByName).filter((f) => !groupedParamNames.has(f.name));
|
|
20329
20537
|
const pathParamNames = new Set(pathParams.map((p) => safeParamName(p.name)));
|
|
20330
20538
|
const bodyFieldRenames = /* @__PURE__ */ new Map();
|
|
20331
20539
|
for (const f of bodyFields) {
|
|
@@ -20393,7 +20601,7 @@ function emitMethod(args) {
|
|
|
20393
20601
|
sigParts.push(`${n}: nil`);
|
|
20394
20602
|
}
|
|
20395
20603
|
sigParts.push("request_options: {}");
|
|
20396
|
-
const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels);
|
|
20604
|
+
const doc = buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef);
|
|
20397
20605
|
for (const line of doc) lines.push(` ${line}`);
|
|
20398
20606
|
if (sigParts.length === 0) lines.push(` def ${method}`);
|
|
20399
20607
|
else if (sigParts.length === 1 && sigParts[0].length < 60) lines.push(` def ${method}(${sigParts[0]})`);
|
|
@@ -20419,28 +20627,32 @@ function emitMethod(args) {
|
|
|
20419
20627
|
const groupsGoToQuery = hasGroups && !hasBodyMethod;
|
|
20420
20628
|
const hasQuery = qEntries.length > 0 || groupsGoToQuery;
|
|
20421
20629
|
if (hasQuery) {
|
|
20630
|
+
const queryCompact = qEntries.some((q) => !q.required) ? ".compact" : "";
|
|
20422
20631
|
lines.push(" params = {");
|
|
20423
20632
|
for (let i = 0; i < qEntries.length; i++) {
|
|
20424
20633
|
const q = qEntries[i];
|
|
20425
20634
|
const sep = i === qEntries.length - 1 && !groupsGoToQuery ? "" : ",";
|
|
20426
20635
|
lines.push(` ${rubyStringLit(q.name)} => ${safeParamName(q.name)}${sep}`);
|
|
20427
20636
|
}
|
|
20428
|
-
lines.push(
|
|
20637
|
+
lines.push(` }${queryCompact}`);
|
|
20429
20638
|
if (groupsGoToQuery) for (const group of op.parameterGroups ?? []) {
|
|
20430
20639
|
const prop = fieldName(group.name);
|
|
20431
20640
|
if (group.optional) {
|
|
20432
20641
|
lines.push(` if ${prop}`);
|
|
20433
|
-
lines.push(` case ${prop}
|
|
20434
|
-
} else lines.push(` case ${prop}
|
|
20642
|
+
lines.push(` case ${prop}`);
|
|
20643
|
+
} else lines.push(` case ${prop}`);
|
|
20435
20644
|
for (const variant of group.variants) {
|
|
20436
|
-
|
|
20437
|
-
|
|
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)}`);
|
|
20438
20648
|
}
|
|
20649
|
+
lines.push(` else`);
|
|
20650
|
+
lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
|
|
20439
20651
|
lines.push(" end");
|
|
20440
20652
|
if (group.optional) lines.push(" end");
|
|
20441
20653
|
}
|
|
20442
20654
|
}
|
|
20443
|
-
const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http);
|
|
20655
|
+
const hasBody = bodyFields.length > 0 && !["get", "head"].includes(method_http) || hasGroups && hasBodyMethod;
|
|
20444
20656
|
if (hasBody) {
|
|
20445
20657
|
const bodyEntries = [];
|
|
20446
20658
|
for (const [k, v] of Object.entries(defaults)) {
|
|
@@ -20452,26 +20664,32 @@ function emitMethod(args) {
|
|
|
20452
20664
|
const optKey = fc === "client_secret" ? "api_key" : fc;
|
|
20453
20665
|
bodyEntries.push(`${rubyStringLit(fc)} => (request_options[:${optKey}] || @client.${clientProp})`);
|
|
20454
20666
|
}
|
|
20667
|
+
let bodyHasNilable = false;
|
|
20455
20668
|
for (const f of bodyFields) {
|
|
20456
20669
|
if (hiddenParams.has(f.name)) continue;
|
|
20457
20670
|
bodyEntries.push(`${rubyStringLit(f.name)} => ${bodyKwargName(f.name)}`);
|
|
20671
|
+
if (!f.required) bodyHasNilable = true;
|
|
20458
20672
|
}
|
|
20673
|
+
const bodyCompact = bodyHasNilable ? ".compact" : "";
|
|
20459
20674
|
lines.push(" body = {");
|
|
20460
20675
|
for (let i = 0; i < bodyEntries.length; i++) {
|
|
20461
20676
|
const sep = i === bodyEntries.length - 1 ? "" : ",";
|
|
20462
20677
|
lines.push(` ${bodyEntries[i]}${sep}`);
|
|
20463
20678
|
}
|
|
20464
|
-
lines.push(
|
|
20679
|
+
lines.push(` }${bodyCompact}`);
|
|
20465
20680
|
if (hasGroups && hasBodyMethod) for (const group of op.parameterGroups ?? []) {
|
|
20466
20681
|
const prop = fieldName(group.name);
|
|
20467
20682
|
if (group.optional) {
|
|
20468
20683
|
lines.push(` if ${prop}`);
|
|
20469
|
-
lines.push(` case ${prop}
|
|
20470
|
-
} else lines.push(` case ${prop}
|
|
20684
|
+
lines.push(` case ${prop}`);
|
|
20685
|
+
} else lines.push(` case ${prop}`);
|
|
20471
20686
|
for (const variant of group.variants) {
|
|
20472
|
-
|
|
20473
|
-
|
|
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)}`);
|
|
20474
20690
|
}
|
|
20691
|
+
lines.push(` else`);
|
|
20692
|
+
lines.push(` raise ArgumentError, ${dispatchErrorLiteral(group, prop, variantClassRef)}`);
|
|
20475
20693
|
lines.push(" end");
|
|
20476
20694
|
if (group.optional) lines.push(" end");
|
|
20477
20695
|
}
|
|
@@ -20686,7 +20904,7 @@ function oneLine(desc) {
|
|
|
20686
20904
|
if (!desc) return "";
|
|
20687
20905
|
return desc.replace(/\r/g, " ").replace(/\n+/g, " ").replace(/\s+/g, " ").trim();
|
|
20688
20906
|
}
|
|
20689
|
-
function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels) {
|
|
20907
|
+
function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bodyFieldRenames, listWrapperModels, variantClassRef) {
|
|
20690
20908
|
const lines = [];
|
|
20691
20909
|
const firstLine = (op.description ?? `${op.httpMethod.toUpperCase()} ${op.path}`).split("\n")[0] ?? "";
|
|
20692
20910
|
lines.push(`# ${firstLine}`);
|
|
@@ -20722,6 +20940,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
|
|
|
20722
20940
|
const deprecatedPrefix = q.deprecated ? "(deprecated) " : "";
|
|
20723
20941
|
lines.push(`# @param ${n} [${type}${suffix}] ${deprecatedPrefix}${oneLine(q.description)}`.trim());
|
|
20724
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
|
+
}
|
|
20725
20951
|
lines.push(`# @param request_options [Hash] (see WorkOS::Types::RequestOptions)`);
|
|
20726
20952
|
const ref = op.response;
|
|
20727
20953
|
if (ref.kind === "primitive" && ref.type === "unknown") lines.push(`# @return [void]`);
|
|
@@ -20744,6 +20970,14 @@ function buildYardDoc(op, pathParams, queryParams, bodyFields, hiddenParams, bod
|
|
|
20744
20970
|
function rubyStringLit(s) {
|
|
20745
20971
|
return `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
20746
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
|
+
}
|
|
20747
20981
|
//#endregion
|
|
20748
20982
|
//#region src/ruby/client.ts
|
|
20749
20983
|
/**
|
|
@@ -20912,7 +21146,7 @@ function generateMainEntryFile(spec, ctx) {
|
|
|
20912
21146
|
* keep the generated namespace flat while the filesystem is grouped.
|
|
20913
21147
|
*/
|
|
20914
21148
|
function collectModelSubdirs(spec, ctx) {
|
|
20915
|
-
const modelToService = assignModelsToServices(spec.models, spec.services);
|
|
21149
|
+
const modelToService = assignModelsToServices(spec.models, spec.services, ctx.modelHints);
|
|
20916
21150
|
const mountDirMap = buildMountDirMap(ctx);
|
|
20917
21151
|
const subdirs = /* @__PURE__ */ new Set();
|
|
20918
21152
|
for (const model of spec.models) {
|
|
@@ -20973,9 +21207,11 @@ function generateClientClass(spec, ctx) {
|
|
|
20973
21207
|
function generateTests(spec, ctx) {
|
|
20974
21208
|
const files = [];
|
|
20975
21209
|
const groups = groupByMount(ctx);
|
|
21210
|
+
const models = spec.models;
|
|
20976
21211
|
const modelByName = /* @__PURE__ */ new Map();
|
|
20977
|
-
for (const m of
|
|
21212
|
+
for (const m of models) modelByName.set(m.name, m);
|
|
20978
21213
|
const lookup = buildResolvedLookup(ctx);
|
|
21214
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
20979
21215
|
for (const [mountTarget, group] of groups) {
|
|
20980
21216
|
const cls = className(mountTarget);
|
|
20981
21217
|
const prop = servicePropertyName(mountTarget);
|
|
@@ -21004,7 +21240,9 @@ function generateTests(spec, ctx) {
|
|
|
21004
21240
|
op.httpMethod.toLowerCase();
|
|
21005
21241
|
const httpMethodSym = `:${op.httpMethod.toLowerCase()}`;
|
|
21006
21242
|
const resolved = lookupResolved(op, lookup);
|
|
21007
|
-
const
|
|
21243
|
+
const hiddenParams = buildHiddenParams$1(resolved);
|
|
21244
|
+
const callArgs = buildCallArgsStub(op, modelByName, hiddenParams, groupOwners, models);
|
|
21245
|
+
const bodyMatcher = buildBodyMatcher(op, modelByName, hiddenParams, models);
|
|
21008
21246
|
authMethodManifest.push({
|
|
21009
21247
|
method,
|
|
21010
21248
|
httpMethodSym,
|
|
@@ -21013,23 +21251,47 @@ function generateTests(spec, ctx) {
|
|
|
21013
21251
|
});
|
|
21014
21252
|
const stubRegex = stubUrlRegex(stubUrl);
|
|
21015
21253
|
lines.push(` def test_${method}_returns_expected_result`);
|
|
21254
|
+
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21255
|
+
if (bodyMatcher) lines.push(` .with(body: ${bodyMatcher})`);
|
|
21016
21256
|
if (isList) {
|
|
21017
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21018
21257
|
lines.push(` .to_return(body: '{"data": [], "list_metadata": {}}', status: 200)`);
|
|
21019
21258
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
21020
21259
|
lines.push(" assert_kind_of WorkOS::Types::ListStruct, result");
|
|
21021
21260
|
} else if (op.response.kind === "primitive") {
|
|
21022
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21023
21261
|
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
21024
21262
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
21025
21263
|
lines.push(" assert_nil result");
|
|
21026
21264
|
} else {
|
|
21027
|
-
lines.push(` stub_request(${httpMethodSym}, ${stubRegex})`);
|
|
21028
21265
|
lines.push(` .to_return(body: "{}", status: 200)`);
|
|
21029
21266
|
lines.push(` result = @client.${prop}.${method}(${callArgs})`);
|
|
21030
21267
|
lines.push(" refute_nil result");
|
|
21031
21268
|
}
|
|
21032
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
|
+
}
|
|
21033
21295
|
if (resolved?.wrappers && resolved.wrappers.length > 0) for (const wrapper of resolved.wrappers) emitWrapperTests({
|
|
21034
21296
|
lines,
|
|
21035
21297
|
wrapper,
|
|
@@ -21160,8 +21422,12 @@ function roundTripStub(ref, enumNames) {
|
|
|
21160
21422
|
default: return `nil`;
|
|
21161
21423
|
}
|
|
21162
21424
|
}
|
|
21163
|
-
/** Build minimal placeholder arguments for calling the SDK method from a test.
|
|
21164
|
-
|
|
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()) {
|
|
21165
21431
|
const parts = [];
|
|
21166
21432
|
const seen = /* @__PURE__ */ new Set();
|
|
21167
21433
|
const pathParamNames = /* @__PURE__ */ new Set();
|
|
@@ -21193,17 +21459,68 @@ function buildCallArgsStub(op, modelByName, hiddenParams) {
|
|
|
21193
21459
|
seen.add(name);
|
|
21194
21460
|
parts.push(`${name}: ${stubValueFor(q.type)}`);
|
|
21195
21461
|
}
|
|
21462
|
+
const bodyFieldTypes = collectBodyFieldTypes(op, models);
|
|
21196
21463
|
for (const group of op.parameterGroups ?? []) {
|
|
21197
|
-
if (group.optional) continue;
|
|
21198
21464
|
const name = fieldName(group.name);
|
|
21199
21465
|
if (seen.has(name)) continue;
|
|
21200
21466
|
seen.add(name);
|
|
21201
|
-
const
|
|
21202
|
-
|
|
21203
|
-
|
|
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
|
+
}
|
|
21204
21476
|
}
|
|
21205
21477
|
return parts.join(", ");
|
|
21206
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
|
+
}
|
|
21207
21524
|
function resolveBodyModel(ref, modelByName) {
|
|
21208
21525
|
if (ref.kind === "model") return modelByName.get(ref.name) ?? null;
|
|
21209
21526
|
if (ref.kind === "nullable") return resolveBodyModel(ref.inner, modelByName);
|
|
@@ -21257,7 +21574,7 @@ function stubValueFor(ref) {
|
|
|
21257
21574
|
case "boolean": return `true`;
|
|
21258
21575
|
default: return `nil`;
|
|
21259
21576
|
}
|
|
21260
|
-
case "array": return `[]`;
|
|
21577
|
+
case "array": return `[${stubValueFor(ref.items)}]`;
|
|
21261
21578
|
case "map": return `{}`;
|
|
21262
21579
|
case "enum": return `"stub"`;
|
|
21263
21580
|
case "literal":
|
|
@@ -21392,6 +21709,7 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21392
21709
|
for (const m of spec.models) modelByName.set(m.name, m);
|
|
21393
21710
|
const listWrapperModels = /* @__PURE__ */ new Map();
|
|
21394
21711
|
for (const m of spec.models) if (isListWrapperModel(m)) listWrapperModels.set(m.name, m);
|
|
21712
|
+
const groupOwners = buildGroupOwnerMap(ctx);
|
|
21395
21713
|
for (const [mountTarget, group] of groups) {
|
|
21396
21714
|
const cls = className(mountTarget);
|
|
21397
21715
|
const lines = [];
|
|
@@ -21399,6 +21717,11 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21399
21717
|
lines.push("");
|
|
21400
21718
|
lines.push("module WorkOS");
|
|
21401
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
|
+
}
|
|
21402
21725
|
lines.push(" sig { params(client: WorkOS::BaseClient).void }");
|
|
21403
21726
|
lines.push(" def initialize(client); end");
|
|
21404
21727
|
lines.push("");
|
|
@@ -21415,7 +21738,15 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21415
21738
|
const hiddenParams = buildHiddenParams$1(resolved);
|
|
21416
21739
|
const groupedParamNames = collectGroupedParamNames(op);
|
|
21417
21740
|
const queryParams = (op.queryParams ?? []).filter((q) => !groupedParamNames.has(q.name));
|
|
21418
|
-
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
|
+
};
|
|
21419
21750
|
const sigParams = [];
|
|
21420
21751
|
const seen = /* @__PURE__ */ new Set();
|
|
21421
21752
|
for (const p of op.pathParams ?? []) {
|
|
@@ -21440,6 +21771,13 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21440
21771
|
seen.add(n);
|
|
21441
21772
|
sigParams.push(`${n}: ${mapSorbetType(q.type)}`);
|
|
21442
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
|
+
}
|
|
21443
21781
|
for (const f of bodyFields) {
|
|
21444
21782
|
if (hiddenParams.has(f.name)) continue;
|
|
21445
21783
|
if (f.required) continue;
|
|
@@ -21456,6 +21794,13 @@ function generateRbiFiles(spec, ctx) {
|
|
|
21456
21794
|
seen.add(n);
|
|
21457
21795
|
sigParams.push(`${n}: T.nilable(${unwrapNilable(mapSorbetType(q.type))})`);
|
|
21458
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
|
+
}
|
|
21459
21804
|
sigParams.push("request_options: T::Hash[Symbol, T.untyped]");
|
|
21460
21805
|
const retType = mapSorbetReturnType(op.response, listWrapperModels, modelNames);
|
|
21461
21806
|
lines.push(" sig do");
|
|
@@ -21630,4 +21975,4 @@ const workosEmittersPlugin = {
|
|
|
21630
21975
|
//#endregion
|
|
21631
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 };
|
|
21632
21977
|
|
|
21633
|
-
//# sourceMappingURL=plugin-
|
|
21978
|
+
//# sourceMappingURL=plugin-Bp46oZIh.mjs.map
|