ts-typed-api 0.1.21 → 0.1.22
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/openapi-self.js +39 -2
- package/package.json +1 -1
- package/src/openapi-self.ts +42 -2
- package/tests/openapi-spec.test.ts +130 -0
package/dist/openapi-self.js
CHANGED
|
@@ -166,10 +166,47 @@ class SchemaRegistry {
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
if (zodSchema instanceof zod_1.ZodArray) {
|
|
169
|
-
|
|
169
|
+
// Try multiple ways to get the array item type
|
|
170
|
+
let itemType;
|
|
171
|
+
// Method 1: Try _def.element (this is the correct property for ZodArray)
|
|
172
|
+
try {
|
|
173
|
+
const def = getZodDef(zodSchema);
|
|
174
|
+
if (def && def.element && typeof def.element === 'object' && def.element.constructor) {
|
|
175
|
+
itemType = def.element;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// Continue to next method
|
|
180
|
+
}
|
|
181
|
+
// Method 2: Try getZodType if method 1 failed
|
|
182
|
+
if (!itemType) {
|
|
183
|
+
const typeResult = getZodType(zodSchema);
|
|
184
|
+
if (typeResult && typeof typeResult === 'object' && typeResult.constructor) {
|
|
185
|
+
itemType = typeResult;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Method 3: Direct access to _def.element as fallback
|
|
189
|
+
if (!itemType) {
|
|
190
|
+
try {
|
|
191
|
+
const directElement = zodSchema._def?.element;
|
|
192
|
+
if (directElement && typeof directElement === 'object' && directElement.constructor) {
|
|
193
|
+
itemType = directElement;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
// Continue to fallback
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (itemType) {
|
|
201
|
+
return {
|
|
202
|
+
type: 'array',
|
|
203
|
+
items: this.zodToOpenAPI(itemType, shouldRegister)
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Ultimate fallback
|
|
170
207
|
return {
|
|
171
208
|
type: 'array',
|
|
172
|
-
items:
|
|
209
|
+
items: { type: 'string' }
|
|
173
210
|
};
|
|
174
211
|
}
|
|
175
212
|
if (zodSchema instanceof zod_1.ZodObject) {
|
package/package.json
CHANGED
package/src/openapi-self.ts
CHANGED
|
@@ -270,10 +270,50 @@ class SchemaRegistry {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
if (zodSchema instanceof ZodArray) {
|
|
273
|
-
|
|
273
|
+
// Try multiple ways to get the array item type
|
|
274
|
+
let itemType: ZodTypeAny | undefined;
|
|
275
|
+
|
|
276
|
+
// Method 1: Try _def.element (this is the correct property for ZodArray)
|
|
277
|
+
try {
|
|
278
|
+
const def = getZodDef(zodSchema);
|
|
279
|
+
if (def && def.element && typeof def.element === 'object' && def.element.constructor) {
|
|
280
|
+
itemType = def.element;
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// Continue to next method
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Method 2: Try getZodType if method 1 failed
|
|
287
|
+
if (!itemType) {
|
|
288
|
+
const typeResult = getZodType(zodSchema);
|
|
289
|
+
if (typeResult && typeof typeResult === 'object' && typeResult.constructor) {
|
|
290
|
+
itemType = typeResult;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Method 3: Direct access to _def.element as fallback
|
|
295
|
+
if (!itemType) {
|
|
296
|
+
try {
|
|
297
|
+
const directElement = (zodSchema as any)._def?.element;
|
|
298
|
+
if (directElement && typeof directElement === 'object' && directElement.constructor) {
|
|
299
|
+
itemType = directElement;
|
|
300
|
+
}
|
|
301
|
+
} catch (error) {
|
|
302
|
+
// Continue to fallback
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (itemType) {
|
|
307
|
+
return {
|
|
308
|
+
type: 'array',
|
|
309
|
+
items: this.zodToOpenAPI(itemType, shouldRegister)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Ultimate fallback
|
|
274
314
|
return {
|
|
275
315
|
type: 'array',
|
|
276
|
-
items:
|
|
316
|
+
items: { type: 'string' }
|
|
277
317
|
};
|
|
278
318
|
}
|
|
279
319
|
|
|
@@ -186,4 +186,134 @@ describe('OpenAPI Specification Generation', () => {
|
|
|
186
186
|
// The enum should either have values or be a basic string type (no empty enum arrays)
|
|
187
187
|
expect(resultProperty.enum.length).toBeGreaterThan(0);
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
test('should handle nested object arrays correctly', () => {
|
|
191
|
+
// Test case for nested object array structure
|
|
192
|
+
const s1 = z.object({
|
|
193
|
+
version: z.number(),
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const s2 = z.object({
|
|
197
|
+
versions: z.array(s1),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
const NestedArrayTestDefinition = CreateApiDefinition({
|
|
201
|
+
endpoints: {
|
|
202
|
+
test: {
|
|
203
|
+
nestedArrayEndpoint: {
|
|
204
|
+
method: 'POST' as const,
|
|
205
|
+
path: '/test/nested-array',
|
|
206
|
+
body: s2,
|
|
207
|
+
responses: {
|
|
208
|
+
200: z.object({
|
|
209
|
+
data: s2,
|
|
210
|
+
success: z.boolean()
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const spec = generateOpenApiSpec(NestedArrayTestDefinition);
|
|
219
|
+
|
|
220
|
+
expect(spec).toBeDefined();
|
|
221
|
+
expect(spec.paths).toBeDefined();
|
|
222
|
+
expect(spec.components?.schemas).toBeDefined();
|
|
223
|
+
|
|
224
|
+
const nestedArrayEndpoint = spec.paths['/test/nested-array'];
|
|
225
|
+
expect(nestedArrayEndpoint).toBeDefined();
|
|
226
|
+
expect(nestedArrayEndpoint.post).toBeDefined();
|
|
227
|
+
|
|
228
|
+
// Check request body schema
|
|
229
|
+
const requestBodySchema = nestedArrayEndpoint.post?.requestBody?.content?.['application/json']?.schema;
|
|
230
|
+
expect(requestBodySchema).toBeDefined();
|
|
231
|
+
|
|
232
|
+
// The schema should be a reference to components
|
|
233
|
+
expect(requestBodySchema?.$ref).toBeDefined();
|
|
234
|
+
const schemaName = requestBodySchema?.$ref?.split('/').pop();
|
|
235
|
+
const actualSchema = spec.components?.schemas?.[schemaName!];
|
|
236
|
+
expect(actualSchema).toBeDefined();
|
|
237
|
+
|
|
238
|
+
expect(actualSchema?.type).toBe('object');
|
|
239
|
+
expect(actualSchema?.properties).toBeDefined();
|
|
240
|
+
|
|
241
|
+
// Verify the versions property is an array
|
|
242
|
+
const versionsProperty = actualSchema?.properties?.versions;
|
|
243
|
+
expect(versionsProperty).toBeDefined();
|
|
244
|
+
expect(versionsProperty?.type).toBe('array');
|
|
245
|
+
expect(versionsProperty?.items).toBeDefined();
|
|
246
|
+
|
|
247
|
+
// The array items should properly reference the nested object schema (s1)
|
|
248
|
+
const arrayItemsSchema = versionsProperty?.items;
|
|
249
|
+
if (arrayItemsSchema?.$ref) {
|
|
250
|
+
// If it's a reference, resolve it and check the object structure
|
|
251
|
+
const itemSchemaName = arrayItemsSchema.$ref.split('/').pop();
|
|
252
|
+
const itemSchema = spec.components?.schemas?.[itemSchemaName!];
|
|
253
|
+
expect(itemSchema).toBeDefined();
|
|
254
|
+
expect(itemSchema?.type).toBe('object');
|
|
255
|
+
expect(itemSchema?.properties?.version).toBeDefined();
|
|
256
|
+
expect(itemSchema?.properties?.version?.type).toBe('number');
|
|
257
|
+
} else {
|
|
258
|
+
// If it's inline, it should be an object with version property
|
|
259
|
+
expect(arrayItemsSchema?.type).toBe('object');
|
|
260
|
+
expect(arrayItemsSchema?.properties?.version).toBeDefined();
|
|
261
|
+
expect(arrayItemsSchema?.properties?.version?.type).toBe('number');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Check response schema structure
|
|
265
|
+
const responseSchema = nestedArrayEndpoint.post?.responses?.['200']?.content?.['application/json']?.schema;
|
|
266
|
+
expect(responseSchema).toBeDefined();
|
|
267
|
+
|
|
268
|
+
expect(responseSchema?.$ref).toBeDefined();
|
|
269
|
+
const responseSchemaName = responseSchema?.$ref?.split('/').pop();
|
|
270
|
+
const actualResponseSchema = spec.components?.schemas?.[responseSchemaName!];
|
|
271
|
+
expect(actualResponseSchema).toBeDefined();
|
|
272
|
+
|
|
273
|
+
expect(actualResponseSchema?.type).toBe('object');
|
|
274
|
+
expect(actualResponseSchema?.properties).toBeDefined();
|
|
275
|
+
expect(actualResponseSchema?.properties?.data).toBeDefined();
|
|
276
|
+
expect(actualResponseSchema?.properties?.success).toBeDefined();
|
|
277
|
+
expect(actualResponseSchema?.properties?.success?.type).toBe('boolean');
|
|
278
|
+
|
|
279
|
+
// Verify that the nested data property has the correct structure
|
|
280
|
+
const dataProperty = actualResponseSchema?.properties?.data;
|
|
281
|
+
if (dataProperty?.$ref) {
|
|
282
|
+
// If data is a reference, resolve it
|
|
283
|
+
const dataSchemaName = dataProperty.$ref.split('/').pop();
|
|
284
|
+
const dataSchema = spec.components?.schemas?.[dataSchemaName!];
|
|
285
|
+
expect(dataSchema).toBeDefined();
|
|
286
|
+
expect(dataSchema?.type).toBe('object');
|
|
287
|
+
expect(dataSchema?.properties?.versions).toBeDefined();
|
|
288
|
+
expect(dataSchema?.properties?.versions?.type).toBe('array');
|
|
289
|
+
|
|
290
|
+
// The versions array items should be objects with version property
|
|
291
|
+
const dataVersionsItems = dataSchema?.properties?.versions?.items;
|
|
292
|
+
if (dataVersionsItems?.$ref) {
|
|
293
|
+
const dataItemSchemaName = dataVersionsItems.$ref.split('/').pop();
|
|
294
|
+
const dataItemSchema = spec.components?.schemas?.[dataItemSchemaName!];
|
|
295
|
+
expect(dataItemSchema?.type).toBe('object');
|
|
296
|
+
expect(dataItemSchema?.properties?.version?.type).toBe('number');
|
|
297
|
+
} else {
|
|
298
|
+
expect(dataVersionsItems?.type).toBe('object');
|
|
299
|
+
expect(dataVersionsItems?.properties?.version?.type).toBe('number');
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
// If data is inline
|
|
303
|
+
expect(dataProperty?.type).toBe('object');
|
|
304
|
+
expect(dataProperty?.properties?.versions).toBeDefined();
|
|
305
|
+
expect(dataProperty?.properties?.versions?.type).toBe('array');
|
|
306
|
+
|
|
307
|
+
const dataVersionsItems = dataProperty?.properties?.versions?.items;
|
|
308
|
+
if (dataVersionsItems?.$ref) {
|
|
309
|
+
const dataItemSchemaName = dataVersionsItems.$ref.split('/').pop();
|
|
310
|
+
const dataItemSchema = spec.components?.schemas?.[dataItemSchemaName!];
|
|
311
|
+
expect(dataItemSchema?.type).toBe('object');
|
|
312
|
+
expect(dataItemSchema?.properties?.version?.type).toBe('number');
|
|
313
|
+
} else {
|
|
314
|
+
expect(dataVersionsItems?.type).toBe('object');
|
|
315
|
+
expect(dataVersionsItems?.properties?.version?.type).toBe('number');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
189
319
|
});
|