@workos/oagen-emitters 0.4.0 → 0.5.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/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lint.yml +1 -1
- package/.github/workflows/release-please.yml +2 -2
- package/.github/workflows/release.yml +1 -1
- package/.husky/pre-push +11 -0
- package/.node-version +1 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +35 -224
- package/dist/index.d.mts +9 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -15234
- package/dist/plugin-BSop9f9z.mjs +21471 -0
- package/dist/plugin-BSop9f9z.mjs.map +1 -0
- package/dist/plugin.d.mts +7 -0
- package/dist/plugin.d.mts.map +1 -0
- package/dist/plugin.mjs +2 -0
- package/docs/sdk-architecture/dotnet.md +5 -5
- package/oagen.config.ts +5 -373
- package/package.json +10 -34
- package/src/dotnet/index.ts +6 -4
- package/src/dotnet/models.ts +58 -82
- package/src/dotnet/naming.ts +44 -6
- package/src/dotnet/resources.ts +350 -29
- package/src/dotnet/tests.ts +44 -24
- package/src/dotnet/type-map.ts +44 -17
- package/src/dotnet/wrappers.ts +21 -10
- package/src/go/client.ts +35 -3
- package/src/go/enums.ts +4 -0
- package/src/go/index.ts +10 -5
- package/src/go/models.ts +6 -1
- package/src/go/resources.ts +534 -73
- package/src/go/tests.ts +39 -3
- package/src/go/type-map.ts +8 -3
- package/src/go/wrappers.ts +79 -21
- package/src/index.ts +14 -0
- package/src/kotlin/client.ts +7 -2
- package/src/kotlin/enums.ts +30 -3
- package/src/kotlin/models.ts +97 -6
- package/src/kotlin/naming.ts +7 -1
- package/src/kotlin/resources.ts +370 -39
- package/src/kotlin/tests.ts +120 -6
- package/src/node/client.ts +38 -11
- package/src/node/field-plan.ts +12 -14
- package/src/node/fixtures.ts +39 -3
- package/src/node/models.ts +281 -37
- package/src/node/resources.ts +156 -52
- package/src/node/tests.ts +76 -27
- package/src/node/type-map.ts +1 -31
- package/src/node/utils.ts +96 -6
- package/src/node/wrappers.ts +31 -1
- package/src/php/models.ts +0 -33
- package/src/php/resources.ts +199 -18
- package/src/php/tests.ts +26 -2
- package/src/php/type-map.ts +16 -2
- package/src/php/wrappers.ts +6 -2
- package/src/plugin.ts +50 -0
- package/src/python/client.ts +13 -3
- package/src/python/enums.ts +28 -3
- package/src/python/index.ts +35 -27
- package/src/python/models.ts +138 -1
- package/src/python/resources.ts +234 -17
- package/src/python/tests.ts +260 -16
- package/src/python/type-map.ts +16 -2
- package/src/ruby/client.ts +238 -0
- package/src/ruby/enums.ts +149 -0
- package/src/ruby/index.ts +93 -0
- package/src/ruby/manifest.ts +35 -0
- package/src/ruby/models.ts +360 -0
- package/src/ruby/naming.ts +187 -0
- package/src/ruby/rbi.ts +313 -0
- package/src/ruby/resources.ts +799 -0
- package/src/ruby/tests.ts +459 -0
- package/src/ruby/type-map.ts +97 -0
- package/src/ruby/wrappers.ts +161 -0
- package/src/shared/model-utils.ts +131 -7
- package/src/shared/naming-utils.ts +36 -0
- package/src/shared/non-spec-services.ts +13 -0
- package/src/shared/resolved-ops.ts +75 -1
- package/test/dotnet/client.test.ts +2 -2
- package/test/dotnet/models.test.ts +7 -9
- package/test/dotnet/resources.test.ts +135 -3
- package/test/dotnet/tests.test.ts +5 -5
- package/test/entrypoint.test.ts +89 -0
- package/test/go/client.test.ts +6 -6
- package/test/go/resources.test.ts +156 -7
- package/test/kotlin/models.test.ts +1 -1
- package/test/kotlin/resources.test.ts +210 -0
- package/test/node/models.test.ts +134 -1
- package/test/node/resources.test.ts +134 -26
- package/test/node/utils.test.ts +140 -0
- package/test/php/models.test.ts +5 -4
- package/test/php/resources.test.ts +66 -1
- package/test/plugin.test.ts +50 -0
- package/test/python/client.test.ts +56 -0
- package/test/python/models.test.ts +99 -0
- package/test/python/resources.test.ts +294 -0
- package/test/python/tests.test.ts +91 -0
- package/test/ruby/client.test.ts +81 -0
- package/test/ruby/resources.test.ts +386 -0
- package/test/shared/resolved-ops.test.ts +122 -0
- package/tsdown.config.ts +1 -1
- package/dist/index.mjs.map +0 -1
- package/scripts/generate-php.js +0 -13
- package/scripts/git-push-with-published-oagen.sh +0 -21
|
@@ -139,8 +139,9 @@ describe('dotnet/resources', () => {
|
|
|
139
139
|
|
|
140
140
|
const content = optionsFile.content;
|
|
141
141
|
expect(content).toContain('Options');
|
|
142
|
-
expect(content).toContain('[JsonProperty("name")]');
|
|
143
142
|
expect(content).toContain('public string Name');
|
|
143
|
+
// Convention-based naming — no per-property JSON attributes
|
|
144
|
+
expect(content).not.toContain('[JsonProperty("name")]');
|
|
144
145
|
});
|
|
145
146
|
|
|
146
147
|
it('generates paginated list method with auto-pagination', () => {
|
|
@@ -204,9 +205,9 @@ describe('dotnet/resources', () => {
|
|
|
204
205
|
const serviceFile = files.find((f) => f.path.includes('OrganizationsService.cs'))!;
|
|
205
206
|
const content = serviceFile.content;
|
|
206
207
|
|
|
207
|
-
// List method (
|
|
208
|
+
// List method (return type is async Task)
|
|
208
209
|
expect(content).toContain('async Task<WorkOSList<Organization>>');
|
|
209
|
-
expect(content).toContain('
|
|
210
|
+
expect(content).toContain('ListAsync(');
|
|
210
211
|
|
|
211
212
|
// Auto-pagination method
|
|
212
213
|
expect(content).toContain('ListAutoPagingAsync');
|
|
@@ -252,4 +253,135 @@ describe('dotnet/resources', () => {
|
|
|
252
253
|
|
|
253
254
|
expect(serviceFile.content).toContain('[System.Obsolete');
|
|
254
255
|
});
|
|
256
|
+
|
|
257
|
+
it('generates parameter group abstract base + variant classes and query serialization', () => {
|
|
258
|
+
const models: Model[] = [
|
|
259
|
+
{
|
|
260
|
+
name: 'Authorization',
|
|
261
|
+
fields: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'AuthorizationList',
|
|
265
|
+
fields: [
|
|
266
|
+
{
|
|
267
|
+
name: 'data',
|
|
268
|
+
type: { kind: 'array', items: { kind: 'model', name: 'Authorization' } },
|
|
269
|
+
required: true,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'list_metadata',
|
|
273
|
+
type: { kind: 'model', name: 'ListMetadata' },
|
|
274
|
+
required: true,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const services: Service[] = [
|
|
281
|
+
{
|
|
282
|
+
name: 'Fga',
|
|
283
|
+
operations: [
|
|
284
|
+
{
|
|
285
|
+
name: 'listAuthorizations',
|
|
286
|
+
httpMethod: 'get',
|
|
287
|
+
path: '/fga/authorizations',
|
|
288
|
+
pathParams: [],
|
|
289
|
+
queryParams: [
|
|
290
|
+
{ name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
|
|
291
|
+
{ name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
292
|
+
{ name: 'parent_resource_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
293
|
+
{ name: 'parent_resource_type_slug', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
294
|
+
{ name: 'parent_resource_external_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
295
|
+
],
|
|
296
|
+
headerParams: [],
|
|
297
|
+
response: { kind: 'model', name: 'AuthorizationList' },
|
|
298
|
+
errors: [],
|
|
299
|
+
injectIdempotencyKey: false,
|
|
300
|
+
pagination: {
|
|
301
|
+
strategy: 'cursor',
|
|
302
|
+
param: 'after',
|
|
303
|
+
dataPath: 'data',
|
|
304
|
+
itemType: { kind: 'model', name: 'Authorization' },
|
|
305
|
+
},
|
|
306
|
+
parameterGroups: [
|
|
307
|
+
{
|
|
308
|
+
name: 'parent_resource',
|
|
309
|
+
optional: false,
|
|
310
|
+
variants: [
|
|
311
|
+
{
|
|
312
|
+
name: 'by_id',
|
|
313
|
+
parameters: [
|
|
314
|
+
{ name: 'parent_resource_id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'by_external_id',
|
|
319
|
+
parameters: [
|
|
320
|
+
{
|
|
321
|
+
name: 'parent_resource_type_slug',
|
|
322
|
+
type: { kind: 'primitive', type: 'string' },
|
|
323
|
+
required: true,
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: 'parent_resource_external_id',
|
|
327
|
+
type: { kind: 'primitive', type: 'string' },
|
|
328
|
+
required: true,
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
},
|
|
338
|
+
];
|
|
339
|
+
|
|
340
|
+
primeEnumAliases([]);
|
|
341
|
+
const ctxWithServices: EmitterContext = {
|
|
342
|
+
...ctx,
|
|
343
|
+
spec: { ...emptySpec, services, models },
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const files = generateResources(services, ctxWithServices);
|
|
347
|
+
|
|
348
|
+
// Options file should exist and contain group types
|
|
349
|
+
const optionsFile = files.find((f) => f.path.includes('Options.cs'))!;
|
|
350
|
+
expect(optionsFile).toBeDefined();
|
|
351
|
+
const optContent = optionsFile.content;
|
|
352
|
+
|
|
353
|
+
// Abstract base class (prefixed with service name)
|
|
354
|
+
expect(optContent).toContain('public abstract class FGAParentResource { }');
|
|
355
|
+
|
|
356
|
+
// Concrete variant: ById
|
|
357
|
+
expect(optContent).toContain('public class FGAParentResourceById : FGAParentResource');
|
|
358
|
+
expect(optContent).toContain('public string ParentResourceId { get; set; } = default!;');
|
|
359
|
+
|
|
360
|
+
// Concrete variant: ByExternalId
|
|
361
|
+
expect(optContent).toContain('public class FGAParentResourceByExternalId : FGAParentResource');
|
|
362
|
+
expect(optContent).toContain('public string ParentResourceTypeSlug { get; set; } = default!;');
|
|
363
|
+
expect(optContent).toContain('public string ParentResourceExternalId { get; set; } = default!;');
|
|
364
|
+
|
|
365
|
+
// Group property on options class with JsonIgnore
|
|
366
|
+
expect(optContent).toContain('[JsonIgnore]');
|
|
367
|
+
expect(optContent).toContain('[STJS.JsonIgnore]');
|
|
368
|
+
expect(optContent).toContain('public FGAParentResource ParentResource { get; set; } = default!;');
|
|
369
|
+
|
|
370
|
+
// Grouped params should NOT appear as individual properties
|
|
371
|
+
expect(optContent).not.toMatch(/\[JsonProperty\("parent_resource_id"\)\]/);
|
|
372
|
+
expect(optContent).not.toMatch(/\[JsonProperty\("parent_resource_type_slug"\)\]/);
|
|
373
|
+
expect(optContent).not.toMatch(/\[JsonProperty\("parent_resource_external_id"\)\]/);
|
|
374
|
+
|
|
375
|
+
// Service file should contain group query serialization
|
|
376
|
+
const serviceFile = files.find((f) => f.path.endsWith('Service.cs'))!;
|
|
377
|
+
expect(serviceFile).toBeDefined();
|
|
378
|
+
const svcContent = serviceFile.content;
|
|
379
|
+
|
|
380
|
+
// Pattern matching for group variants
|
|
381
|
+
expect(svcContent).toContain('ParentResourceById');
|
|
382
|
+
expect(svcContent).toContain('ParentResourceByExternalId');
|
|
383
|
+
expect(svcContent).toContain('AddQueryParam("parent_resource_id"');
|
|
384
|
+
expect(svcContent).toContain('AddQueryParam("parent_resource_type_slug"');
|
|
385
|
+
expect(svcContent).toContain('AddQueryParam("parent_resource_external_id"');
|
|
386
|
+
});
|
|
255
387
|
});
|
|
@@ -105,15 +105,15 @@ describe('dotnet/tests', () => {
|
|
|
105
105
|
const content = testFile.content;
|
|
106
106
|
|
|
107
107
|
expect(content).toContain('TestError401');
|
|
108
|
-
expect(content).toContain('
|
|
108
|
+
expect(content).toContain('AuthenticationException');
|
|
109
109
|
expect(content).toContain('TestError404');
|
|
110
|
-
expect(content).toContain('
|
|
110
|
+
expect(content).toContain('NotFoundException');
|
|
111
111
|
expect(content).toContain('TestError422');
|
|
112
|
-
expect(content).toContain('
|
|
112
|
+
expect(content).toContain('UnprocessableEntityException');
|
|
113
113
|
expect(content).toContain('TestError429');
|
|
114
|
-
expect(content).toContain('
|
|
114
|
+
expect(content).toContain('RateLimitExceededException');
|
|
115
115
|
expect(content).toContain('TestError500');
|
|
116
|
-
expect(content).toContain('
|
|
116
|
+
expect(content).toContain('ServerException');
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
it('generates fixture JSON files', () => {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Verify the public entrypoint exports the plugin bundle
|
|
5
|
+
* and all intended direct symbols (emitters + extractors).
|
|
6
|
+
*/
|
|
7
|
+
describe('public entrypoint (@workos/oagen-emitters)', () => {
|
|
8
|
+
it('exports workosEmittersPlugin', async () => {
|
|
9
|
+
const mod = await import('../src/index.js');
|
|
10
|
+
expect(mod.workosEmittersPlugin).toBeDefined();
|
|
11
|
+
expect(mod.workosEmittersPlugin.emitters).toBeDefined();
|
|
12
|
+
expect(mod.workosEmittersPlugin.extractors).toBeDefined();
|
|
13
|
+
expect(mod.workosEmittersPlugin.smokeRunners).toBeDefined();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('exports all individual emitters', async () => {
|
|
17
|
+
const mod = await import('../src/index.js');
|
|
18
|
+
const expectedEmitters = [
|
|
19
|
+
'nodeEmitter',
|
|
20
|
+
'pythonEmitter',
|
|
21
|
+
'phpEmitter',
|
|
22
|
+
'goEmitter',
|
|
23
|
+
'dotnetEmitter',
|
|
24
|
+
'kotlinEmitter',
|
|
25
|
+
'rubyEmitter',
|
|
26
|
+
];
|
|
27
|
+
for (const name of expectedEmitters) {
|
|
28
|
+
expect(mod).toHaveProperty(name);
|
|
29
|
+
expect((mod as Record<string, unknown>)[name]).toHaveProperty('language');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('exports all individual extractors', async () => {
|
|
34
|
+
const mod = await import('../src/index.js');
|
|
35
|
+
const expectedExtractors = [
|
|
36
|
+
'nodeExtractor',
|
|
37
|
+
'rubyExtractor',
|
|
38
|
+
'pythonExtractor',
|
|
39
|
+
'phpExtractor',
|
|
40
|
+
'goExtractor',
|
|
41
|
+
'rustExtractor',
|
|
42
|
+
'kotlinExtractor',
|
|
43
|
+
'dotnetExtractor',
|
|
44
|
+
'elixirExtractor',
|
|
45
|
+
];
|
|
46
|
+
for (const name of expectedExtractors) {
|
|
47
|
+
expect(mod).toHaveProperty(name);
|
|
48
|
+
expect((mod as Record<string, unknown>)[name]).toHaveProperty('language');
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('plugin bundle emitters match individual emitter exports', async () => {
|
|
53
|
+
const mod = await import('../src/index.js');
|
|
54
|
+
const pluginLanguages = mod.workosEmittersPlugin.emitters!.map((e) => e.language);
|
|
55
|
+
const directEmitters = [
|
|
56
|
+
mod.nodeEmitter,
|
|
57
|
+
mod.pythonEmitter,
|
|
58
|
+
mod.phpEmitter,
|
|
59
|
+
mod.goEmitter,
|
|
60
|
+
mod.dotnetEmitter,
|
|
61
|
+
mod.kotlinEmitter,
|
|
62
|
+
mod.rubyEmitter,
|
|
63
|
+
];
|
|
64
|
+
for (const emitter of directEmitters) {
|
|
65
|
+
expect(pluginLanguages).toContain(emitter.language);
|
|
66
|
+
}
|
|
67
|
+
expect(pluginLanguages).toHaveLength(directEmitters.length);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('plugin bundle extractors match individual extractor exports', async () => {
|
|
71
|
+
const mod = await import('../src/index.js');
|
|
72
|
+
const pluginLanguages = mod.workosEmittersPlugin.extractors!.map((e) => e.language);
|
|
73
|
+
const directExtractors = [
|
|
74
|
+
mod.nodeExtractor,
|
|
75
|
+
mod.rubyExtractor,
|
|
76
|
+
mod.pythonExtractor,
|
|
77
|
+
mod.phpExtractor,
|
|
78
|
+
mod.goExtractor,
|
|
79
|
+
mod.rustExtractor,
|
|
80
|
+
mod.kotlinExtractor,
|
|
81
|
+
mod.dotnetExtractor,
|
|
82
|
+
mod.elixirExtractor,
|
|
83
|
+
];
|
|
84
|
+
for (const extractor of directExtractors) {
|
|
85
|
+
expect(pluginLanguages).toContain(extractor.language);
|
|
86
|
+
}
|
|
87
|
+
expect(pluginLanguages).toHaveLength(directExtractors.length);
|
|
88
|
+
});
|
|
89
|
+
});
|
package/test/go/client.test.ts
CHANGED
|
@@ -55,9 +55,9 @@ describe('go/client', () => {
|
|
|
55
55
|
const content = workosFile.content;
|
|
56
56
|
|
|
57
57
|
expect(content).toContain('package workos');
|
|
58
|
-
expect(content).toContain('organizations *
|
|
58
|
+
expect(content).toContain('organizations *OrganizationService');
|
|
59
59
|
expect(content).toContain('func NewClient(apiKey string, opts ...ClientOption) *Client {');
|
|
60
|
-
expect(content).toContain('func (c *Client) Organizations() *
|
|
60
|
+
expect(content).toContain('func (c *Client) Organizations() *OrganizationService {');
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('does not emit static options or HTTP infrastructure', () => {
|
|
@@ -84,9 +84,9 @@ describe('go/client', () => {
|
|
|
84
84
|
const workosFile = files.find((f) => f.path === 'workos.go')!;
|
|
85
85
|
const content = workosFile.content;
|
|
86
86
|
|
|
87
|
-
expect(content).toContain('apiKeys *
|
|
88
|
-
expect(content).toContain('sso *
|
|
89
|
-
expect(content).toContain('func (c *Client) APIKeys() *
|
|
90
|
-
expect(content).toContain('func (c *Client) SSO() *
|
|
87
|
+
expect(content).toContain('apiKeys *APIKeyService');
|
|
88
|
+
expect(content).toContain('sso *SSOService');
|
|
89
|
+
expect(content).toContain('func (c *Client) APIKeys() *APIKeyService {');
|
|
90
|
+
expect(content).toContain('func (c *Client) SSO() *SSOService {');
|
|
91
91
|
});
|
|
92
92
|
});
|
|
@@ -114,12 +114,12 @@ describe('go/resources', () => {
|
|
|
114
114
|
expect(files.length).toBeGreaterThanOrEqual(1);
|
|
115
115
|
const content = files[0].content;
|
|
116
116
|
expect(content).toContain('package workos');
|
|
117
|
-
expect(content).toContain('type
|
|
117
|
+
expect(content).toContain('type OrganizationService struct {');
|
|
118
118
|
expect(content).toContain('Limit *int `url:"limit,omitempty" json:"-"`');
|
|
119
|
-
expect(content).toContain('func (s *
|
|
120
|
-
expect(content).toContain('func (s *
|
|
121
|
-
expect(content).toContain('func (s *
|
|
122
|
-
expect(content).toContain('func (s *
|
|
119
|
+
expect(content).toContain('func (s *OrganizationService) List(');
|
|
120
|
+
expect(content).toContain('func (s *OrganizationService) Get(');
|
|
121
|
+
expect(content).toContain('func (s *OrganizationService) Create(');
|
|
122
|
+
expect(content).toContain('func (s *OrganizationService) Delete(');
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it('generates path interpolation with fmt.Sprintf', () => {
|
|
@@ -139,7 +139,7 @@ describe('go/resources', () => {
|
|
|
139
139
|
const spec = makeSpec(services);
|
|
140
140
|
const files = generateResources(services, makeCtx(spec));
|
|
141
141
|
const content = files[0].content;
|
|
142
|
-
expect(content).toContain('fmt.Sprintf("/users/%s", id)');
|
|
142
|
+
expect(content).toContain('fmt.Sprintf("/users/%s", url.PathEscape(id))');
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
it('generates paginated methods returning Iterator', () => {
|
|
@@ -165,7 +165,7 @@ describe('go/resources', () => {
|
|
|
165
165
|
const files = generateResources(services, makeCtx(spec));
|
|
166
166
|
const content = files[0].content;
|
|
167
167
|
expect(content).toContain('*Iterator[User]');
|
|
168
|
-
expect(content).toContain('newIterator[User](ctx, s.client, "GET", "/users", nil, "after", "data", opts
|
|
168
|
+
expect(content).toContain('newIterator[User](ctx, s.client, "GET", "/users", nil, "after", "data", opts,');
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
it('generates delete methods returning error', () => {
|
|
@@ -405,4 +405,153 @@ describe('go/resources', () => {
|
|
|
405
405
|
expect(content).toContain('Body interface{} `json:"-"`');
|
|
406
406
|
expect(content).toContain('request(ctx, "POST", "/connect/applications", nil, params.Body, &result, opts)');
|
|
407
407
|
});
|
|
408
|
+
|
|
409
|
+
describe('mutually-exclusive parameter groups', () => {
|
|
410
|
+
const groupedOp = makeOp({
|
|
411
|
+
name: 'listResources',
|
|
412
|
+
httpMethod: 'get',
|
|
413
|
+
path: '/authorization/organization_memberships/{organization_membership_id}/resources',
|
|
414
|
+
pathParams: [{ name: 'organization_membership_id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
415
|
+
queryParams: [
|
|
416
|
+
{ name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
417
|
+
{ name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
418
|
+
{ name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
|
|
419
|
+
{ name: 'order', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
420
|
+
{ name: 'permission_slug', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
421
|
+
{ name: 'parent_resource_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
422
|
+
{ name: 'parent_resource_type_slug', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
423
|
+
{ name: 'parent_resource_external_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
424
|
+
],
|
|
425
|
+
parameterGroups: [
|
|
426
|
+
{
|
|
427
|
+
name: 'parent_resource',
|
|
428
|
+
optional: false,
|
|
429
|
+
variants: [
|
|
430
|
+
{
|
|
431
|
+
name: 'by_id',
|
|
432
|
+
parameters: [
|
|
433
|
+
{ name: 'parent_resource_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
434
|
+
],
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'by_external_id',
|
|
438
|
+
parameters: [
|
|
439
|
+
{ name: 'parent_resource_type_slug', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
440
|
+
{ name: 'parent_resource_external_id', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
441
|
+
],
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
pagination: {
|
|
447
|
+
strategy: 'cursor' as const,
|
|
448
|
+
param: 'after',
|
|
449
|
+
dataPath: 'data',
|
|
450
|
+
itemType: { kind: 'model' as const, name: 'AuthorizationResource' },
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
function makeGroupedServices(): Service[] {
|
|
455
|
+
return [{ name: 'Authorization', operations: [groupedOp] }];
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
it('generates a sealed interface for the parameter group', () => {
|
|
459
|
+
const services = makeGroupedServices();
|
|
460
|
+
const spec = makeSpec(services);
|
|
461
|
+
const files = generateResources(services, makeCtx(spec));
|
|
462
|
+
const content = files[0].content;
|
|
463
|
+
|
|
464
|
+
// Interface declaration with unexported marker + applyToQuery
|
|
465
|
+
expect(content).toContain('type AuthorizationParentResource interface {');
|
|
466
|
+
expect(content).toContain('isAuthorizationParentResource()');
|
|
467
|
+
expect(content).toContain('applyToQuery(url.Values)');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('generates variant structs with shortened field names', () => {
|
|
471
|
+
const services = makeGroupedServices();
|
|
472
|
+
const spec = makeSpec(services);
|
|
473
|
+
const files = generateResources(services, makeCtx(spec));
|
|
474
|
+
const content = files[0].content;
|
|
475
|
+
|
|
476
|
+
// ByID variant
|
|
477
|
+
expect(content).toContain('type AuthorizationParentResourceByID struct {');
|
|
478
|
+
expect(content).toContain('\tID string');
|
|
479
|
+
|
|
480
|
+
// ByExternalID variant
|
|
481
|
+
expect(content).toContain('type AuthorizationParentResourceByExternalID struct {');
|
|
482
|
+
expect(content).toContain('\tTypeSlug string');
|
|
483
|
+
expect(content).toContain('\tExternalID string');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('generates marker methods on each variant', () => {
|
|
487
|
+
const services = makeGroupedServices();
|
|
488
|
+
const spec = makeSpec(services);
|
|
489
|
+
const files = generateResources(services, makeCtx(spec));
|
|
490
|
+
const content = files[0].content;
|
|
491
|
+
|
|
492
|
+
expect(content).toContain('func (p AuthorizationParentResourceByID) isAuthorizationParentResource()');
|
|
493
|
+
expect(content).toContain('func (p AuthorizationParentResourceByExternalID) isAuthorizationParentResource()');
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('generates applyToQuery methods using original wire names', () => {
|
|
497
|
+
const services = makeGroupedServices();
|
|
498
|
+
const spec = makeSpec(services);
|
|
499
|
+
const files = generateResources(services, makeCtx(spec));
|
|
500
|
+
const content = files[0].content;
|
|
501
|
+
|
|
502
|
+
// ByID variant sets parent_resource_id
|
|
503
|
+
expect(content).toContain('func (p AuthorizationParentResourceByID) applyToQuery(v url.Values)');
|
|
504
|
+
expect(content).toContain('v.Set("parent_resource_id", p.ID)');
|
|
505
|
+
|
|
506
|
+
// ByExternalID variant sets both wire-name params
|
|
507
|
+
expect(content).toContain('func (p AuthorizationParentResourceByExternalID) applyToQuery(v url.Values)');
|
|
508
|
+
expect(content).toContain('v.Set("parent_resource_type_slug", p.TypeSlug)');
|
|
509
|
+
expect(content).toContain('v.Set("parent_resource_external_id", p.ExternalID)');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('params struct uses group interface instead of flat pointers', () => {
|
|
513
|
+
const services = makeGroupedServices();
|
|
514
|
+
const spec = makeSpec(services);
|
|
515
|
+
const files = generateResources(services, makeCtx(spec));
|
|
516
|
+
const content = files[0].content;
|
|
517
|
+
|
|
518
|
+
// Should have the group field
|
|
519
|
+
expect(content).toContain('ParentResource AuthorizationParentResource `url:"-" json:"-"`');
|
|
520
|
+
|
|
521
|
+
// Should NOT have the flat pointer fields
|
|
522
|
+
expect(content).not.toMatch(/ParentResourceID\s+\*string/);
|
|
523
|
+
expect(content).not.toMatch(/ParentResourceTypeSlug\s+\*string/);
|
|
524
|
+
expect(content).not.toMatch(/ParentResourceExternalID\s+\*string/);
|
|
525
|
+
|
|
526
|
+
// Should still have non-grouped params
|
|
527
|
+
expect(content).toContain('PermissionSlug string');
|
|
528
|
+
expect(content).toContain('PaginationParams');
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it('method body builds url.Values and calls applyToQuery', () => {
|
|
532
|
+
const services = makeGroupedServices();
|
|
533
|
+
const spec = makeSpec(services);
|
|
534
|
+
const files = generateResources(services, makeCtx(spec));
|
|
535
|
+
const content = files[0].content;
|
|
536
|
+
|
|
537
|
+
// Should build url.Values manually
|
|
538
|
+
expect(content).toContain('query := url.Values{}');
|
|
539
|
+
// Should encode the non-grouped required param
|
|
540
|
+
expect(content).toContain('query.Set("permission_slug", params.PermissionSlug)');
|
|
541
|
+
// Should call applyToQuery on the group
|
|
542
|
+
expect(content).toContain('params.ParentResource.applyToQuery(query)');
|
|
543
|
+
// Should pass query to the iterator (not params)
|
|
544
|
+
expect(content).toContain('newIterator[AuthorizationResource](ctx, s.client, "GET"');
|
|
545
|
+
expect(content).toContain(', query, "after", "data", opts, nil)');
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it('imports net/url when parameter groups are present', () => {
|
|
549
|
+
const services = makeGroupedServices();
|
|
550
|
+
const spec = makeSpec(services);
|
|
551
|
+
const files = generateResources(services, makeCtx(spec));
|
|
552
|
+
const content = files[0].content;
|
|
553
|
+
|
|
554
|
+
expect(content).toContain('"net/url"');
|
|
555
|
+
});
|
|
556
|
+
});
|
|
408
557
|
});
|
|
@@ -57,7 +57,7 @@ describe('kotlin/models', () => {
|
|
|
57
57
|
const content = modelFile.content;
|
|
58
58
|
expect(content).toContain('data class Organization');
|
|
59
59
|
expect(content).toContain('@JsonProperty("id")');
|
|
60
|
-
expect(content).toContain('@JvmField');
|
|
60
|
+
expect(content).not.toContain('@JvmField');
|
|
61
61
|
expect(content).toContain('OffsetDateTime');
|
|
62
62
|
expect(content).toContain('externalId: String?');
|
|
63
63
|
});
|