@workos/oagen-emitters 0.6.6 → 0.6.7
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 +8 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-BgVrq-hM.mjs → plugin-Bk0xWTQC.mjs} +45 -10
- package/dist/plugin-Bk0xWTQC.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/test/dotnet/models.test.ts +77 -0
- package/dist/plugin-BgVrq-hM.mjs.map +0 -1
package/dist/plugin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as workosEmittersPlugin } from "./plugin-
|
|
1
|
+
import { t as workosEmittersPlugin } from "./plugin-Bk0xWTQC.mjs";
|
|
2
2
|
export { workosEmittersPlugin };
|
package/package.json
CHANGED
package/src/dotnet/index.ts
CHANGED
|
@@ -103,7 +103,14 @@ export const dotnetEmitter: Emitter = {
|
|
|
103
103
|
return m;
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
// Build a map of base model name → discriminator wire-property name so the
|
|
107
|
+
// model emitter can mark the discriminator field as internal-set.
|
|
108
|
+
const discriminatorProperties = new Map<string, string>();
|
|
109
|
+
for (const [baseName, disc] of modelDiscriminators) {
|
|
110
|
+
discriminatorProperties.set(baseName, disc.property);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const discCtx: DiscriminatorContext = { discriminatorBases, variantToBase, discriminatorProperties };
|
|
107
114
|
const files = generateModels(dotnetModels, c, discCtx);
|
|
108
115
|
|
|
109
116
|
// Generate discriminator converters for oneOf unions with discriminator
|
|
@@ -181,6 +188,8 @@ export const dotnetEmitter: Emitter = {
|
|
|
181
188
|
lines.push(` /// </summary>`);
|
|
182
189
|
lines.push(` public class ${converterName} : Newtonsoft.Json.JsonConverter`);
|
|
183
190
|
lines.push(' {');
|
|
191
|
+
lines.push(' public override bool CanWrite => false;');
|
|
192
|
+
lines.push('');
|
|
184
193
|
lines.push(
|
|
185
194
|
` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`,
|
|
186
195
|
);
|
|
@@ -206,8 +215,6 @@ export const dotnetEmitter: Emitter = {
|
|
|
206
215
|
lines.push(' return target;');
|
|
207
216
|
lines.push(' }');
|
|
208
217
|
lines.push('');
|
|
209
|
-
lines.push(' public override bool CanWrite => false;');
|
|
210
|
-
lines.push('');
|
|
211
218
|
lines.push(
|
|
212
219
|
' public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer)',
|
|
213
220
|
);
|
package/src/dotnet/models.ts
CHANGED
|
@@ -31,6 +31,8 @@ export interface DiscriminatorContext {
|
|
|
31
31
|
discriminatorBases: Set<string>;
|
|
32
32
|
/** Maps variant model name → base model name. */
|
|
33
33
|
variantToBase: Map<string, string>;
|
|
34
|
+
/** Maps base model name → wire name of the discriminator property. */
|
|
35
|
+
discriminatorProperties?: Map<string, string>;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -161,6 +163,12 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
161
163
|
let initializer = '';
|
|
162
164
|
let setterModifier = '';
|
|
163
165
|
|
|
166
|
+
// On a discriminated union base, the discriminator property (e.g. "event")
|
|
167
|
+
// should be non-public-settable even though it lacks a single const value
|
|
168
|
+
// (each variant has a different value). Consumers must never mutate it.
|
|
169
|
+
const discProp = isDiscBase ? discCtx?.discriminatorProperties?.get(model.name) : undefined;
|
|
170
|
+
const isDiscriminatorField = discProp !== undefined && field.name === discProp;
|
|
171
|
+
|
|
164
172
|
if (constInit !== null && !isOptional) {
|
|
165
173
|
// Discriminator-style single-value enum/literal: emit with a const
|
|
166
174
|
// initializer and a non-public setter so callers can't drift the
|
|
@@ -170,6 +178,14 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
170
178
|
csType = baseType;
|
|
171
179
|
initializer = ` = ${constInit};`;
|
|
172
180
|
setterModifier = 'internal ';
|
|
181
|
+
} else if (isDiscriminatorField) {
|
|
182
|
+
// Discriminator property on the base class: varies per variant but
|
|
183
|
+
// should still be non-public-settable so consumers can't change it.
|
|
184
|
+
csType = baseType;
|
|
185
|
+
if (!isAlreadyNullable && !isValueTypeRef(field.type)) {
|
|
186
|
+
initializer = ' = default!;';
|
|
187
|
+
}
|
|
188
|
+
setterModifier = 'internal ';
|
|
173
189
|
} else if (isOptional) {
|
|
174
190
|
if (isAlreadyNullable) {
|
|
175
191
|
csType = baseType;
|
|
@@ -219,6 +235,14 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
219
235
|
lines.push(` /// <paramref name="key"/> coerced to <typeparamref name="T"/>, or the default`);
|
|
220
236
|
lines.push(` /// value when the key is missing or the value is not convertible.`);
|
|
221
237
|
lines.push(` /// </summary>`);
|
|
238
|
+
if (isDiscBase) {
|
|
239
|
+
lines.push(` /// <remarks>`);
|
|
240
|
+
lines.push(` /// Variant subclasses provide strongly-typed <c>${dict.csName}</c> properties that`);
|
|
241
|
+
lines.push(` /// shadow this dictionary. This accessor is intended for forward-compatible handling`);
|
|
242
|
+
lines.push(` /// of types not yet known to this SDK version. For recognized types, cast to the`);
|
|
243
|
+
lines.push(` /// specific subclass and access its typed <c>${dict.csName}</c> property directly.`);
|
|
244
|
+
lines.push(` /// </remarks>`);
|
|
245
|
+
}
|
|
222
246
|
lines.push(` /// <typeparam name="T">Expected value type.</typeparam>`);
|
|
223
247
|
lines.push(` /// <param name="key">The key to look up.</param>`);
|
|
224
248
|
lines.push(` public T? Get${dict.csName}Attribute<T>(string key)`);
|
package/src/go/models.ts
CHANGED
|
@@ -166,10 +166,27 @@ function makeOptional(goType: string): string {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
function structuralHash(model: Model): string {
|
|
169
|
-
|
|
169
|
+
const fieldHash = model.fields
|
|
170
170
|
.map((f) => `${f.name}:${JSON.stringify(f.type)}:${f.required}`)
|
|
171
171
|
.sort()
|
|
172
172
|
.join('|');
|
|
173
|
+
// Include entity domain for CRUD-prefixed models to prevent cross-domain
|
|
174
|
+
// aliasing (e.g. UpdateGroup vs UpdateAuthorizationPermission have identical
|
|
175
|
+
// fields but belong to different API domains and should stay separate types).
|
|
176
|
+
const domain = crudEntityDomain(model.name);
|
|
177
|
+
return domain ? `${domain}::${fieldHash}` : fieldHash;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const CRUD_PREFIXES = ['Create', 'Update', 'Delete', 'Get', 'List'];
|
|
181
|
+
|
|
182
|
+
/** Strip CRUD verb prefix to get the entity name, or null if no prefix matches. */
|
|
183
|
+
function crudEntityDomain(name: string): string | null {
|
|
184
|
+
for (const prefix of CRUD_PREFIXES) {
|
|
185
|
+
if (name.startsWith(prefix) && name.length > prefix.length) {
|
|
186
|
+
return name.slice(prefix.length);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
173
190
|
}
|
|
174
191
|
|
|
175
192
|
/** Known acronyms to preserve as single tokens during humanization. */
|
package/src/go/tests.ts
CHANGED
|
@@ -307,7 +307,10 @@ function generateServiceTest(
|
|
|
307
307
|
// Success test
|
|
308
308
|
const respModel = plan.responseModelName;
|
|
309
309
|
const isArrayResponse = !isPaginated && op.response?.kind === 'array';
|
|
310
|
-
|
|
310
|
+
let fixturePath = `testdata/${fileName(respModel)}.json`;
|
|
311
|
+
if (fixtureRewrites.has(fixturePath)) {
|
|
312
|
+
fixturePath = fixtureRewrites.get(fixturePath)!;
|
|
313
|
+
}
|
|
311
314
|
const expectedPath = buildExpectedPath(op);
|
|
312
315
|
|
|
313
316
|
const httpMethodUpper = op.httpMethod.toUpperCase();
|
|
@@ -411,7 +414,10 @@ function generateServiceTest(
|
|
|
411
414
|
const wrapperParamsStruct = paramsStructName(resolvedName, wrapperMethod);
|
|
412
415
|
const responseType = wrapper.responseModelName;
|
|
413
416
|
const testName = `Test${accessorName}_${wrapperMethod}`;
|
|
414
|
-
|
|
417
|
+
let fixturePath = responseType ? `testdata/${fileName(responseType)}.json` : null;
|
|
418
|
+
if (fixturePath && fixtureRewrites.has(fixturePath)) {
|
|
419
|
+
fixturePath = fixtureRewrites.get(fixturePath)!;
|
|
420
|
+
}
|
|
415
421
|
|
|
416
422
|
const wrapperCallArgs: string[] = ['context.Background()'];
|
|
417
423
|
for (const p of sortPathParamsByTemplateOrder(op)) {
|
|
@@ -255,4 +255,81 @@ describe('dotnet/models', () => {
|
|
|
255
255
|
expect(orgFile).toBeDefined();
|
|
256
256
|
expect(orgFile.content).toContain('List<OrganizationDomain>');
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
it('emits internal set on discriminator property of base class', () => {
|
|
260
|
+
const models: Model[] = [
|
|
261
|
+
{
|
|
262
|
+
name: 'EventSchema',
|
|
263
|
+
fields: [
|
|
264
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
265
|
+
{ name: 'event', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
266
|
+
{
|
|
267
|
+
name: 'data',
|
|
268
|
+
type: { kind: 'map', valueType: { kind: 'primitive', type: 'unknown' } },
|
|
269
|
+
required: true,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'UserCreated',
|
|
275
|
+
fields: [
|
|
276
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
277
|
+
{ name: 'event', type: { kind: 'literal', value: 'user.created' }, required: true },
|
|
278
|
+
{ name: 'data', type: { kind: 'model', name: 'UserCreatedData' }, required: true },
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
name: 'UserCreatedData',
|
|
283
|
+
fields: [{ name: 'user_id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
primeEnumAliases([]);
|
|
288
|
+
const discCtx = {
|
|
289
|
+
discriminatorBases: new Set(['EventSchema']),
|
|
290
|
+
variantToBase: new Map([['UserCreated', 'EventSchema']]),
|
|
291
|
+
discriminatorProperties: new Map([['EventSchema', 'event']]),
|
|
292
|
+
};
|
|
293
|
+
const files = generateModels(models, { ...ctx, spec: { ...emptySpec, models } }, discCtx);
|
|
294
|
+
|
|
295
|
+
const baseFile = files.find((f) => f.path.includes('EventSchema.cs'))!;
|
|
296
|
+
expect(baseFile).toBeDefined();
|
|
297
|
+
|
|
298
|
+
// The discriminator property "event" should have internal set
|
|
299
|
+
expect(baseFile.content).toContain('Event { get; internal set; }');
|
|
300
|
+
// Non-discriminator required fields should NOT have internal set
|
|
301
|
+
expect(baseFile.content).toContain('Id { get; set; }');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('adds remarks to dictionary accessors on discriminator base class', () => {
|
|
305
|
+
const models: Model[] = [
|
|
306
|
+
{
|
|
307
|
+
name: 'EventSchema',
|
|
308
|
+
fields: [
|
|
309
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
310
|
+
{ name: 'event', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
311
|
+
{
|
|
312
|
+
name: 'data',
|
|
313
|
+
type: { kind: 'map', valueType: { kind: 'primitive', type: 'unknown' } },
|
|
314
|
+
required: true,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
primeEnumAliases([]);
|
|
321
|
+
const discCtx = {
|
|
322
|
+
discriminatorBases: new Set(['EventSchema']),
|
|
323
|
+
variantToBase: new Map<string, string>(),
|
|
324
|
+
discriminatorProperties: new Map([['EventSchema', 'event']]),
|
|
325
|
+
};
|
|
326
|
+
const files = generateModels(models, { ...ctx, spec: { ...emptySpec, models } }, discCtx);
|
|
327
|
+
|
|
328
|
+
const baseFile = files.find((f) => f.path.includes('EventSchema.cs'))!;
|
|
329
|
+
expect(baseFile).toBeDefined();
|
|
330
|
+
|
|
331
|
+
// Dictionary accessors on discriminator bases should have a remarks note
|
|
332
|
+
expect(baseFile.content).toContain('/// <remarks>');
|
|
333
|
+
expect(baseFile.content).toContain('forward-compatible');
|
|
334
|
+
});
|
|
258
335
|
});
|