@rvoh/psychic 0.37.0-beta.4 → 0.37.0-beta.6
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/dist/cjs/src/error/openapi/AttemptedToDeriveDescendentSerializersFromNonSerializer.js +1 -0
- package/dist/cjs/src/error/openapi/NonSerializerPassedToSerializerOpenapiRenderer.js +1 -0
- package/dist/cjs/src/error/openapi/NonSerializerSerializerOverrideProvided.js +5 -1
- package/dist/cjs/src/openapi-renderer/SerializerOpenapiRenderer.js +40 -34
- package/dist/cjs/src/openapi-renderer/body-segment.js +52 -15
- package/dist/cjs/src/openapi-renderer/endpoint.js +2 -8
- package/dist/cjs/src/openapi-renderer/helpers/allSerializersFromHandWrittenOpenapi.js +47 -3
- package/dist/cjs/src/openapi-renderer/helpers/allSerializersToRefsInOpenapi.js +68 -6
- package/dist/esm/src/error/openapi/AttemptedToDeriveDescendentSerializersFromNonSerializer.js +1 -0
- package/dist/esm/src/error/openapi/NonSerializerPassedToSerializerOpenapiRenderer.js +1 -0
- package/dist/esm/src/error/openapi/NonSerializerSerializerOverrideProvided.js +5 -1
- package/dist/esm/src/openapi-renderer/SerializerOpenapiRenderer.js +40 -34
- package/dist/esm/src/openapi-renderer/body-segment.js +51 -14
- package/dist/esm/src/openapi-renderer/endpoint.js +9 -15
- package/dist/esm/src/openapi-renderer/helpers/allSerializersFromHandWrittenOpenapi.js +47 -3
- package/dist/esm/src/openapi-renderer/helpers/allSerializersToRefsInOpenapi.js +68 -6
- package/dist/types/src/helpers/typeHelpers.d.ts +2 -0
- package/dist/types/src/openapi-renderer/body-segment.d.ts +51 -14
- package/dist/types/src/openapi-renderer/endpoint.d.ts +2 -1
- package/dist/types/src/openapi-renderer/helpers/allSerializersFromHandWrittenOpenapi.d.ts +39 -0
- package/dist/types/src/openapi-renderer/helpers/allSerializersToRefsInOpenapi.d.ts +44 -0
- package/package.json +2 -2
package/dist/cjs/src/error/openapi/AttemptedToDeriveDescendentSerializersFromNonSerializer.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const node_util_1 = require("node:util");
|
|
4
4
|
class AttemptedToDeriveDescendentSerializersFromNonSerializer extends Error {
|
|
5
5
|
serializer;
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
7
|
constructor(serializer) {
|
|
7
8
|
super();
|
|
8
9
|
this.serializer = serializer;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const node_util_1 = require("node:util");
|
|
4
4
|
class NonSerializerPassedToSerializerOpenapiRenderer extends Error {
|
|
5
5
|
serializer;
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
7
|
constructor(serializer) {
|
|
7
8
|
super();
|
|
8
9
|
this.serializer = serializer;
|
|
@@ -4,7 +4,11 @@ const node_util_1 = require("node:util");
|
|
|
4
4
|
class NonSerializerSerializerOverrideProvided extends Error {
|
|
5
5
|
rendersAssociation;
|
|
6
6
|
serializer;
|
|
7
|
-
constructor(
|
|
7
|
+
constructor(
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
rendersAssociation,
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
serializer) {
|
|
8
12
|
super();
|
|
9
13
|
this.rendersAssociation = rendersAssociation;
|
|
10
14
|
this.serializer = serializer;
|
|
@@ -108,10 +108,6 @@ class SerializerOpenapiRenderer {
|
|
|
108
108
|
const DataTypeForOpenapi = $typeForOpenapi;
|
|
109
109
|
let referencedSerializers = [];
|
|
110
110
|
let renderedOpenapi = {};
|
|
111
|
-
const openapiRenderingOpts = {
|
|
112
|
-
casing: this.casing,
|
|
113
|
-
suppressResponseEnums: this.suppressResponseEnums,
|
|
114
|
-
};
|
|
115
111
|
renderedOpenapi = this.serializerBuilder['attributes'].reduce((accumulator, attribute) => {
|
|
116
112
|
const attributeType = attribute.type;
|
|
117
113
|
let newlyReferencedSerializers = [];
|
|
@@ -171,10 +167,11 @@ class SerializerOpenapiRenderer {
|
|
|
171
167
|
case 'rendersOne': {
|
|
172
168
|
try {
|
|
173
169
|
const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
|
|
174
|
-
const referencedSerializersAndOpenapiSchemaBodyShorthand = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers
|
|
170
|
+
const { associationOpts, referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
|
|
171
|
+
const optional = attribute.options.optional ?? associationOpts.optional;
|
|
175
172
|
newlyReferencedSerializers =
|
|
176
173
|
referencedSerializersAndOpenapiSchemaBodyShorthand.referencedSerializers;
|
|
177
|
-
if (attribute.options.flatten &&
|
|
174
|
+
if (attribute.options.flatten && optional) {
|
|
178
175
|
this.allOfSiblings.push({
|
|
179
176
|
anyOf: [referencedSerializersAndOpenapiSchemaBodyShorthand.openapi, NULL_OBJECT_OPENAPI],
|
|
180
177
|
});
|
|
@@ -184,7 +181,7 @@ class SerializerOpenapiRenderer {
|
|
|
184
181
|
this.allOfSiblings.push(referencedSerializersAndOpenapiSchemaBodyShorthand.openapi);
|
|
185
182
|
//
|
|
186
183
|
}
|
|
187
|
-
else if (
|
|
184
|
+
else if (optional) {
|
|
188
185
|
accumulator[outputAttributeName] = {
|
|
189
186
|
anyOf: [referencedSerializersAndOpenapiSchemaBodyShorthand.openapi, NULL_OBJECT_OPENAPI],
|
|
190
187
|
};
|
|
@@ -211,7 +208,7 @@ class SerializerOpenapiRenderer {
|
|
|
211
208
|
case 'rendersMany': {
|
|
212
209
|
try {
|
|
213
210
|
const outputAttributeName = this.setCase(attribute.options.as ?? attribute.name);
|
|
214
|
-
const referencedSerializersAndOpenapiSchemaBodyShorthand = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers
|
|
211
|
+
const { referencedSerializersAndOpenapiSchemaBodyShorthand } = associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers);
|
|
215
212
|
newlyReferencedSerializers =
|
|
216
213
|
referencedSerializersAndOpenapiSchemaBodyShorthand.referencedSerializers;
|
|
217
214
|
accumulator[outputAttributeName] = {
|
|
@@ -238,7 +235,7 @@ class SerializerOpenapiRenderer {
|
|
|
238
235
|
}
|
|
239
236
|
}
|
|
240
237
|
})();
|
|
241
|
-
const recursiveNewlyReferencedSerializers = newlyReferencedSerializers.flatMap(serializer => descendantSerializers(serializer, alreadyExtractedDescendantSerializers
|
|
238
|
+
const recursiveNewlyReferencedSerializers = newlyReferencedSerializers.flatMap(serializer => descendantSerializers(serializer, alreadyExtractedDescendantSerializers));
|
|
242
239
|
referencedSerializers = [
|
|
243
240
|
...referencedSerializers,
|
|
244
241
|
...newlyReferencedSerializers,
|
|
@@ -266,16 +263,19 @@ class SerializerOpenapiRenderer {
|
|
|
266
263
|
}
|
|
267
264
|
}
|
|
268
265
|
exports.default = SerializerOpenapiRenderer;
|
|
269
|
-
function associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers
|
|
266
|
+
function associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDescendantSerializers) {
|
|
270
267
|
const serializerOverride = attribute.options.serializer;
|
|
271
268
|
if (serializerOverride) {
|
|
272
269
|
try {
|
|
273
270
|
return {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
associationOpts: { optional: false },
|
|
272
|
+
referencedSerializersAndOpenapiSchemaBodyShorthand: {
|
|
273
|
+
referencedSerializers: [
|
|
274
|
+
serializerOverride,
|
|
275
|
+
...descendantSerializers(serializerOverride, alreadyExtractedDescendantSerializers),
|
|
276
|
+
],
|
|
277
|
+
openapi: new SerializerOpenapiRenderer(serializerOverride).serializerRef,
|
|
278
|
+
},
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
catch (error) {
|
|
@@ -288,8 +288,8 @@ function associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDesce
|
|
|
288
288
|
const association = DataTypeForOpenapi?.isDream &&
|
|
289
289
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
290
290
|
DataTypeForOpenapi['getAssociationMetadata'](attribute.name);
|
|
291
|
+
const optional = !!association?.optional;
|
|
291
292
|
if (association) {
|
|
292
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
293
293
|
associatedClasses = (0, dream_1.expandStiClasses)(association.modelCB());
|
|
294
294
|
//
|
|
295
295
|
}
|
|
@@ -315,30 +315,36 @@ function associationOpenapi(attribute, DataTypeForOpenapi, alreadyExtractedDesce
|
|
|
315
315
|
associatedClasses = [associatedClass];
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
-
const
|
|
319
|
-
if (
|
|
318
|
+
const serializers = associatedClasses.flatMap(associatedClass => (0, dream_1.inferSerializersFromDreamClassOrViewModelClass)(associatedClass, attribute.options.serializerKey));
|
|
319
|
+
if (serializers.length === 0)
|
|
320
320
|
throw new NoSerializerFoundForRendersOneAndMany_js_1.default(attribute.name);
|
|
321
|
-
if (
|
|
322
|
-
const serializer =
|
|
321
|
+
if (serializers.length === 1) {
|
|
322
|
+
const serializer = serializers[0];
|
|
323
323
|
return {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
324
|
+
associationOpts: { optional },
|
|
325
|
+
referencedSerializersAndOpenapiSchemaBodyShorthand: {
|
|
326
|
+
referencedSerializers: [
|
|
327
|
+
serializer,
|
|
328
|
+
...descendantSerializers(serializer, alreadyExtractedDescendantSerializers),
|
|
329
|
+
],
|
|
330
|
+
openapi: new SerializerOpenapiRenderer(serializer).serializerRef,
|
|
331
|
+
},
|
|
329
332
|
};
|
|
330
333
|
}
|
|
331
334
|
return {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
335
|
+
associationOpts: { optional },
|
|
336
|
+
referencedSerializersAndOpenapiSchemaBodyShorthand: {
|
|
337
|
+
referencedSerializers: [
|
|
338
|
+
...serializers,
|
|
339
|
+
...serializers.flatMap(serializer => descendantSerializers(serializer, alreadyExtractedDescendantSerializers)),
|
|
340
|
+
],
|
|
341
|
+
openapi: {
|
|
342
|
+
anyOf: (0, dream_1.sortBy)((0, dream_1.uniq)(serializers.map(serializer => new SerializerOpenapiRenderer(serializer).serializerRef), ref => ref['$ref']), ref => (ref['$ref'] ? ref['$ref'] : (0, node_util_1.inspect)(ref, { depth: 2 }))),
|
|
343
|
+
},
|
|
338
344
|
},
|
|
339
345
|
};
|
|
340
346
|
}
|
|
341
|
-
function descendantSerializers(serializer, alreadyExtractedDescendantSerializers
|
|
347
|
+
function descendantSerializers(serializer, alreadyExtractedDescendantSerializers) {
|
|
342
348
|
// alreadyExtractedDescendantSerializers is used not only to avoid duplicate
|
|
343
349
|
// work (and thereby speed up OpenAPI spec generation), but also to avoid
|
|
344
350
|
// infinite loops (a recursive OpenAPI structure is valid)
|
|
@@ -346,10 +352,10 @@ function descendantSerializers(serializer, alreadyExtractedDescendantSerializers
|
|
|
346
352
|
return [];
|
|
347
353
|
if (!(0, dream_1.isDreamSerializer)(serializer))
|
|
348
354
|
throw new AttemptedToDeriveDescendentSerializersFromNonSerializer_js_1.default(serializer);
|
|
349
|
-
const immediateDescendantSerializers = new SerializerOpenapiRenderer(serializer
|
|
355
|
+
const immediateDescendantSerializers = new SerializerOpenapiRenderer(serializer).renderedOpenapi(alreadyExtractedDescendantSerializers).referencedSerializers;
|
|
350
356
|
return [
|
|
351
357
|
...immediateDescendantSerializers,
|
|
352
|
-
...immediateDescendantSerializers.flatMap(descendantSerializer => descendantSerializers(descendantSerializer, alreadyExtractedDescendantSerializers
|
|
358
|
+
...immediateDescendantSerializers.flatMap(descendantSerializer => descendantSerializers(descendantSerializer, alreadyExtractedDescendantSerializers)),
|
|
353
359
|
];
|
|
354
360
|
}
|
|
355
361
|
// When attempting to expand STI children, we might call `.serializers` on
|
|
@@ -12,20 +12,57 @@ const maybeNullOpenapiShorthandToOpenapiShorthand_js_1 = __importDefault(require
|
|
|
12
12
|
const primitiveOpenapiStatementToOpenapi_js_1 = __importDefault(require("./helpers/primitiveOpenapiStatementToOpenapi.js"));
|
|
13
13
|
const schemaToRef_js_1 = __importDefault(require("./helpers/schemaToRef.js"));
|
|
14
14
|
const SerializerOpenapiRenderer_js_1 = __importDefault(require("./SerializerOpenapiRenderer.js"));
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* @internal
|
|
17
|
+
*
|
|
18
|
+
* Internal class used to recursively expand OpenAPI shorthand notation into full OpenAPI schema objects.
|
|
19
|
+
*
|
|
20
|
+
* This class handles the transformation of various shorthand formats:
|
|
21
|
+
* - Primitive shorthands like `'string'` → `{ type: 'string' }`
|
|
22
|
+
* - Nullable shorthands like `['string', 'null']` → `{ type: ['string', 'null'] }`
|
|
23
|
+
* - Array shorthands like `'string[]'` → `{ type: 'array', items: { type: 'string' } }`
|
|
24
|
+
* - Nullable array shorthands like `['string[]', 'null']` → `{ type: ['array', 'null'], items: { type: 'string' } }`
|
|
25
|
+
* - Serializer references like `{ $serializer: SomeSerializer }` → `{ $ref: '#/components/schemas/SerializerOpenapiName' }`
|
|
26
|
+
* - Serializable references like `{ $serializable: SomeModel, key: 'summary' }` → resolved serializer reference
|
|
27
|
+
*
|
|
28
|
+
* The class recursively processes nested structures (objects, arrays, unions) and maintains
|
|
29
|
+
* a collection of referenced serializers that need to be included in the final OpenAPI document.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Input shorthand
|
|
34
|
+
* {
|
|
35
|
+
* type: 'object',
|
|
36
|
+
* properties: {
|
|
37
|
+
* name: 'string',
|
|
38
|
+
* tags: 'string[]',
|
|
39
|
+
* user: { $serializer: UserSerializer }
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* // Output expanded
|
|
44
|
+
* {
|
|
45
|
+
* type: 'object',
|
|
46
|
+
* properties: {
|
|
47
|
+
* name: { type: 'string' },
|
|
48
|
+
* tags: { type: 'array', items: { type: 'string' } },
|
|
49
|
+
* user: { $ref: '#/components/schemas/UserSerializer' }
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
class OpenapiSegmentExpander {
|
|
16
55
|
bodySegment;
|
|
17
56
|
casing;
|
|
18
57
|
suppressResponseEnums;
|
|
19
58
|
target;
|
|
20
|
-
openapiName;
|
|
21
59
|
/**
|
|
22
60
|
* @internal
|
|
23
61
|
*
|
|
24
|
-
* Used to recursively
|
|
62
|
+
* Used to recursively expand nested object structures
|
|
25
63
|
* within nested openapi objects
|
|
26
64
|
*/
|
|
27
|
-
constructor(bodySegment, {
|
|
28
|
-
this.openapiName = openapiName;
|
|
65
|
+
constructor(bodySegment, { renderOpts, target }) {
|
|
29
66
|
this.bodySegment = bodySegment;
|
|
30
67
|
this.casing = renderOpts.casing;
|
|
31
68
|
this.suppressResponseEnums = renderOpts.suppressResponseEnums;
|
|
@@ -41,7 +78,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
41
78
|
/**
|
|
42
79
|
* @internal
|
|
43
80
|
*
|
|
44
|
-
* Recursively
|
|
81
|
+
* Recursively expand nested objects and arrays,
|
|
45
82
|
* as well as primitive types
|
|
46
83
|
*/
|
|
47
84
|
recursivelyParseBody(bodySegment) {
|
|
@@ -167,7 +204,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
167
204
|
/**
|
|
168
205
|
* @internal
|
|
169
206
|
*
|
|
170
|
-
* recursively
|
|
207
|
+
* recursively expands a oneOf statement
|
|
171
208
|
*/
|
|
172
209
|
oneOfStatement(bodySegment) {
|
|
173
210
|
const oneOfBodySegment = bodySegment;
|
|
@@ -191,7 +228,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
191
228
|
/**
|
|
192
229
|
* @internal
|
|
193
230
|
*
|
|
194
|
-
* recursively
|
|
231
|
+
* recursively expand an anyOf statement
|
|
195
232
|
*/
|
|
196
233
|
anyOfStatement(bodySegment) {
|
|
197
234
|
const anyOfBodySegment = bodySegment;
|
|
@@ -215,7 +252,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
215
252
|
/**
|
|
216
253
|
* @internal
|
|
217
254
|
*
|
|
218
|
-
* recursively
|
|
255
|
+
* recursively expand an allOf statement
|
|
219
256
|
*/
|
|
220
257
|
allOfStatement(bodySegment) {
|
|
221
258
|
const allOfBodySegment = bodySegment;
|
|
@@ -239,7 +276,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
239
276
|
/**
|
|
240
277
|
* @internal
|
|
241
278
|
*
|
|
242
|
-
* recursively
|
|
279
|
+
* recursively expand an array statement
|
|
243
280
|
*/
|
|
244
281
|
arrayStatement(bodySegment) {
|
|
245
282
|
const results = this.recursivelyParseBody(bodySegment.items);
|
|
@@ -256,7 +293,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
256
293
|
/**
|
|
257
294
|
* @internal
|
|
258
295
|
*
|
|
259
|
-
* recursively
|
|
296
|
+
* recursively expand an object statement
|
|
260
297
|
*/
|
|
261
298
|
objectStatement(bodySegment) {
|
|
262
299
|
const objectBodySegment = bodySegment;
|
|
@@ -293,7 +330,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
293
330
|
/**
|
|
294
331
|
* @internal
|
|
295
332
|
*
|
|
296
|
-
*
|
|
333
|
+
* expand either the `properties` or `additionalProperties` values
|
|
297
334
|
* on an object
|
|
298
335
|
*/
|
|
299
336
|
parseObjectPropertyStatement(propertySegment) {
|
|
@@ -316,7 +353,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
316
353
|
/**
|
|
317
354
|
* @internal
|
|
318
355
|
*
|
|
319
|
-
* recursively
|
|
356
|
+
* recursively expand a primitive literal type (i.e. string or boolean[])
|
|
320
357
|
*/
|
|
321
358
|
primitiveLiteralStatement(bodySegment) {
|
|
322
359
|
return (0, primitiveOpenapiStatementToOpenapi_js_1.default)(bodySegment);
|
|
@@ -324,7 +361,7 @@ class OpenapiBodySegmentRenderer {
|
|
|
324
361
|
/**
|
|
325
362
|
* @internal
|
|
326
363
|
*
|
|
327
|
-
* recursively
|
|
364
|
+
* recursively expand a primitive object type (i.e. { type: 'string[]' })
|
|
328
365
|
*/
|
|
329
366
|
primitiveObjectStatement(bodySegment) {
|
|
330
367
|
const safeBodySegment = bodySegment;
|
|
@@ -454,4 +491,4 @@ The following values will be allowed:
|
|
|
454
491
|
return returnObj;
|
|
455
492
|
}
|
|
456
493
|
}
|
|
457
|
-
exports.default =
|
|
494
|
+
exports.default = OpenapiSegmentExpander;
|
|
@@ -283,7 +283,7 @@ class OpenapiEndpointRenderer {
|
|
|
283
283
|
* Generates the header portion of the openapi payload's
|
|
284
284
|
* "parameters" field for a single endpoint.
|
|
285
285
|
*/
|
|
286
|
-
queryArray({
|
|
286
|
+
queryArray({ renderOpts, }) {
|
|
287
287
|
const queryParams = Object.keys(this.query || {}).map((queryName) => {
|
|
288
288
|
const queryParam = this.query[queryName];
|
|
289
289
|
let output = {
|
|
@@ -307,7 +307,6 @@ class OpenapiEndpointRenderer {
|
|
|
307
307
|
...output,
|
|
308
308
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
309
309
|
schema: new body_segment_js_1.default(queryParam.schema, {
|
|
310
|
-
openapiName,
|
|
311
310
|
renderOpts,
|
|
312
311
|
target: 'request',
|
|
313
312
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -347,7 +346,6 @@ class OpenapiEndpointRenderer {
|
|
|
347
346
|
return this.generateRequestBodyForModel({ openapiName, renderOpts });
|
|
348
347
|
}
|
|
349
348
|
let schema = new body_segment_js_1.default(this.requestBody, {
|
|
350
|
-
openapiName,
|
|
351
349
|
renderOpts,
|
|
352
350
|
target: 'request',
|
|
353
351
|
}).render().openapi;
|
|
@@ -412,7 +410,7 @@ class OpenapiEndpointRenderer {
|
|
|
412
410
|
* that model that are safe to ingest will be automatically added to
|
|
413
411
|
* the request body.
|
|
414
412
|
*/
|
|
415
|
-
generateRequestBodyForModel({
|
|
413
|
+
generateRequestBodyForModel({ renderOpts, }) {
|
|
416
414
|
const forDreamClass = this.requestBody?.for;
|
|
417
415
|
const dreamClass = forDreamClass || this.getSingleDreamModelClass();
|
|
418
416
|
if (!dreamClass)
|
|
@@ -525,7 +523,6 @@ class OpenapiEndpointRenderer {
|
|
|
525
523
|
paramsShape.properties = {
|
|
526
524
|
...paramsShape.properties,
|
|
527
525
|
[columnName]: new body_segment_js_1.default(metadata.type, {
|
|
528
|
-
openapiName,
|
|
529
526
|
renderOpts,
|
|
530
527
|
target: 'request',
|
|
531
528
|
}).render().openapi,
|
|
@@ -570,7 +567,6 @@ class OpenapiEndpointRenderer {
|
|
|
570
567
|
}
|
|
571
568
|
}
|
|
572
569
|
let processedSchema = new body_segment_js_1.default(paramsShape, {
|
|
573
|
-
openapiName,
|
|
574
570
|
renderOpts,
|
|
575
571
|
target: 'request',
|
|
576
572
|
}).render().openapi;
|
|
@@ -594,7 +590,6 @@ class OpenapiEndpointRenderer {
|
|
|
594
590
|
parseResponses({ openapiName, renderOpts, }) {
|
|
595
591
|
let responseData = {};
|
|
596
592
|
const rendererOpts = {
|
|
597
|
-
openapiName,
|
|
598
593
|
renderOpts,
|
|
599
594
|
target: 'response',
|
|
600
595
|
};
|
|
@@ -986,7 +981,6 @@ function serializersToSchemaObjects(controllerClass, actionName, serializers, {
|
|
|
986
981
|
const renderer = new SerializerOpenapiRenderer_js_1.default(serializer, renderOpts);
|
|
987
982
|
const results = renderer.renderedOpenapi(alreadyExtractedDescendantSerializers);
|
|
988
983
|
const segmentRendererResults = new body_segment_js_1.default(results.openapi, {
|
|
989
|
-
openapiName,
|
|
990
984
|
renderOpts,
|
|
991
985
|
target: 'response',
|
|
992
986
|
}).render();
|
|
@@ -4,7 +4,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.default = allSerializersFromHandWrittenOpenapi;
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
9
|
+
const dream_1 = require("@rvoh/dream");
|
|
7
10
|
const isObject_js_1 = __importDefault(require("../../helpers/isObject.js"));
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
*
|
|
14
|
+
* Recursively scans an OpenAPI schema structure and returns an array of all Serializers referenced within it.
|
|
15
|
+
*
|
|
16
|
+
* This function traverses the provided OpenAPI schema definition looking for `$serializer` properties
|
|
17
|
+
* and collects all unique serializer references found. It performs a deep scan of nested objects and
|
|
18
|
+
* arrays to find all serializer references at any level of nesting.
|
|
19
|
+
*
|
|
20
|
+
* **Important**: This function does _not_ recurse into the OpenAPI schemas generated by the discovered
|
|
21
|
+
* Serializers themselves. It only extracts serializers from the hand-written OpenAPI structure provided
|
|
22
|
+
* as input.
|
|
23
|
+
*
|
|
24
|
+
* @param openapi - The OpenAPI schema definition to scan for serializer references
|
|
25
|
+
* @returns An array of unique serializers found in the schema structure
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const schema = {
|
|
30
|
+
* type: 'object',
|
|
31
|
+
* properties: {
|
|
32
|
+
* user: { $serializer: UserSerializer },
|
|
33
|
+
* posts: {
|
|
34
|
+
* type: 'array',
|
|
35
|
+
* items: { $serializer: PostSerializer }
|
|
36
|
+
* },
|
|
37
|
+
* metadata: {
|
|
38
|
+
* type: 'object',
|
|
39
|
+
* properties: {
|
|
40
|
+
* author: { $serializer: UserSerializer } // Duplicate, will be deduplicated
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* const serializers = allSerializersFromHandWrittenOpenapi(schema)
|
|
47
|
+
* // Returns: [UserSerializer, PostSerializer]
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
8
50
|
function allSerializersFromHandWrittenOpenapi(openapi) {
|
|
9
51
|
const serializers = new Set();
|
|
10
52
|
extractSerializers(openapi, serializers);
|
|
@@ -15,15 +57,17 @@ function extractSerializers(
|
|
|
15
57
|
value, serializers) {
|
|
16
58
|
if (!value)
|
|
17
59
|
return;
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
19
60
|
if (value.$serializer) {
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument
|
|
21
61
|
serializers.add(value.$serializer);
|
|
22
62
|
//
|
|
23
63
|
}
|
|
64
|
+
else if (value.$serializable) {
|
|
65
|
+
const foundSerializers = (0, dream_1.inferSerializersFromDreamClassOrViewModelClass)(value.$serializable, value.$serializableSerializerKey);
|
|
66
|
+
foundSerializers.forEach(serializer => serializers.add(serializer));
|
|
67
|
+
//
|
|
68
|
+
}
|
|
24
69
|
else if ((0, isObject_js_1.default)(value)) {
|
|
25
70
|
// Recurse into objects
|
|
26
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
27
71
|
Object.values(value).forEach(val => extractSerializers(val, serializers));
|
|
28
72
|
//
|
|
29
73
|
}
|
|
@@ -4,8 +4,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.default = allSerializersToRefsInOpenapi;
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
9
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
11
|
+
const dream_1 = require("@rvoh/dream");
|
|
7
12
|
const isObject_js_1 = __importDefault(require("../../helpers/isObject.js"));
|
|
8
13
|
const SerializerOpenapiRenderer_js_1 = __importDefault(require("../SerializerOpenapiRenderer.js"));
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*
|
|
17
|
+
* Transforms OpenAPI schema definitions by replacing serializer shorthand references with proper `$ref` statements.
|
|
18
|
+
*
|
|
19
|
+
* This function is used by the SerializerOpenapiRenderer to ensure that the schemas it generates from
|
|
20
|
+
* Serializers contain `$ref` statements instead of `$serializer` or `$serializable` statements. This
|
|
21
|
+
* transformation enables consistent ordering of `anyOf` statements since `anyOf` arrays are sorted by
|
|
22
|
+
* the `$ref` value, which follows the pattern `'#/components/schemas/TheSerializerOpenapiName'`.
|
|
23
|
+
*
|
|
24
|
+
* The function recursively traverses the schema and transforms:
|
|
25
|
+
* - `{ $serializer: SomeSerializer }` → `{ $ref: '#/components/schemas/SerializerOpenapiName' }`
|
|
26
|
+
* - `{ $serializable: SomeModel, key: 'summary' }` → `{ $ref: '#/components/schemas/ModelSummarySerializer' }`
|
|
27
|
+
*
|
|
28
|
+
* @param openapi - The OpenAPI schema definition that may contain serializer shorthand references
|
|
29
|
+
* @returns The transformed schema with `$ref` statements replacing serializer shorthands
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const input = {
|
|
34
|
+
* type: 'object',
|
|
35
|
+
* properties: {
|
|
36
|
+
* user: { $serializer: UserSerializer },
|
|
37
|
+
* posts: {
|
|
38
|
+
* type: 'array',
|
|
39
|
+
* items: { $serializer: PostSerializer }
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* const output = allSerializersToRefsInOpenapi(input)
|
|
45
|
+
* // Returns:
|
|
46
|
+
* // {
|
|
47
|
+
* // type: 'object',
|
|
48
|
+
* // properties: {
|
|
49
|
+
* // user: { $ref: '#/components/schemas/UserSerializer' },
|
|
50
|
+
* // posts: {
|
|
51
|
+
* // type: 'array',
|
|
52
|
+
* // items: { $ref: '#/components/schemas/PostSerializer' }
|
|
53
|
+
* // }
|
|
54
|
+
* // }
|
|
55
|
+
* // }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
9
58
|
function allSerializersToRefsInOpenapi(openapi) {
|
|
10
59
|
if (!openapi)
|
|
11
60
|
return {};
|
|
@@ -16,24 +65,38 @@ function transformValue(value) {
|
|
|
16
65
|
if (!value)
|
|
17
66
|
return value;
|
|
18
67
|
// If this is an object with a $serializer property, replace it with $ref
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
20
68
|
if (value.$serializer) {
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
22
69
|
const { $serializer, ...rest } = value;
|
|
23
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
24
70
|
const openapiRenderer = new SerializerOpenapiRenderer_js_1.default($serializer).serializerRef;
|
|
25
71
|
return {
|
|
26
72
|
...rest,
|
|
27
73
|
...openapiRenderer,
|
|
28
74
|
};
|
|
75
|
+
//
|
|
76
|
+
}
|
|
77
|
+
else if (value.$serializable) {
|
|
78
|
+
const { $serializable, $serializableSerializerKey, ...rest } = value;
|
|
79
|
+
const foundSerializers = (0, dream_1.inferSerializersFromDreamClassOrViewModelClass)($serializable, $serializableSerializerKey);
|
|
80
|
+
const refs = foundSerializers.map(serializer => new SerializerOpenapiRenderer_js_1.default(serializer).serializerRef);
|
|
81
|
+
if (refs.length === 0)
|
|
82
|
+
return rest;
|
|
83
|
+
if (refs.length === 1) {
|
|
84
|
+
return {
|
|
85
|
+
...rest,
|
|
86
|
+
...refs[0],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
...rest,
|
|
91
|
+
anyOf: refs,
|
|
92
|
+
};
|
|
93
|
+
//
|
|
29
94
|
}
|
|
30
95
|
else if ((0, isObject_js_1.default)(value)) {
|
|
31
96
|
// Recurse into objects
|
|
32
97
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
98
|
const transformed = {};
|
|
34
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
35
99
|
for (const [key, val] of Object.entries(value)) {
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
37
100
|
transformed[key] = transformValue(val);
|
|
38
101
|
}
|
|
39
102
|
return transformed;
|
|
@@ -41,7 +104,6 @@ function transformValue(value) {
|
|
|
41
104
|
}
|
|
42
105
|
else if (Array.isArray(value)) {
|
|
43
106
|
// Recurse into arrays
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
45
107
|
return value.map(val => transformValue(val));
|
|
46
108
|
//
|
|
47
109
|
}
|
package/dist/esm/src/error/openapi/AttemptedToDeriveDescendentSerializersFromNonSerializer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
export default class AttemptedToDeriveDescendentSerializersFromNonSerializer extends Error {
|
|
3
3
|
serializer;
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
5
|
constructor(serializer) {
|
|
5
6
|
super();
|
|
6
7
|
this.serializer = serializer;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
2
|
export default class NonSerializerPassedToSerializerOpenapiRenderer extends Error {
|
|
3
3
|
serializer;
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
5
|
constructor(serializer) {
|
|
5
6
|
super();
|
|
6
7
|
this.serializer = serializer;
|
|
@@ -2,7 +2,11 @@ import { inspect } from 'node:util';
|
|
|
2
2
|
export default class NonSerializerSerializerOverrideProvided extends Error {
|
|
3
3
|
rendersAssociation;
|
|
4
4
|
serializer;
|
|
5
|
-
constructor(
|
|
5
|
+
constructor(
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
rendersAssociation,
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
serializer) {
|
|
6
10
|
super();
|
|
7
11
|
this.rendersAssociation = rendersAssociation;
|
|
8
12
|
this.serializer = serializer;
|