Skip to content

Commit 5733ff5

Browse files
committed
Implement error
1 parent f547bb4 commit 5733ff5

File tree

2 files changed

+173
-10
lines changed

2 files changed

+173
-10
lines changed

examples/vite/src/App.tsx

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,25 @@ function App() {
1919
// query: { postId: 1 },
2020
})
2121
.then((res) => {
22-
console.log(res.data?.[0]?.createdAt, res.error?.message)
22+
console.log(res.data?.[0]?.createdAt)
23+
})
24+
25+
api
26+
.get('/users/{id}', {
27+
params: { id: 1 },
28+
// query: { postId: 1 },
29+
})
30+
.then((res) => {
31+
console.log(res.data?.createdAt, res.error?.message)
32+
})
33+
34+
api
35+
.get('getUserById', {
36+
params: { id: 1 },
37+
// query: { postId: 1 },
38+
})
39+
.then((res) => {
40+
console.log(res.data?.createdAt, res.error?.message)
2341
})
2442

2543
api

packages/generator/src/generate-interface.ts

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface EndpointDefinition {
2323
body?: unknown
2424
query?: Record<string, ParameterDefinition>
2525
response?: unknown
26+
error?: unknown
2627
}
2728

2829
// Helper function to extract schema names from $ref
@@ -96,6 +97,14 @@ export function generateInterface(
9697
// Track which schemas are used in request body and responses
9798
const requestSchemaNames = new Set<string>()
9899
const responseSchemaNames = new Set<string>()
100+
const errorSchemaNames = new Set<string>()
101+
102+
// Helper function to check if a status code is an error response
103+
const isErrorStatusCode = (statusCode: string): boolean => {
104+
if (statusCode === 'default') return true
105+
const code = parseInt(statusCode, 10)
106+
return code >= 400 && code < 600
107+
}
99108

100109
// First, collect schema names used in request body and responses
101110
if (schema.paths) {
@@ -126,14 +135,21 @@ export function generateInterface(
126135
}
127136
}
128137

129-
// Collect response schemas
138+
// Collect response and error schemas
130139
if (operation.responses) {
131-
for (const response of Object.values(operation.responses)) {
140+
for (const [statusCode, response] of Object.entries(
141+
operation.responses,
142+
)) {
143+
const isError = isErrorStatusCode(statusCode)
132144
if ('$ref' in response) {
133145
// Extract schema name from $ref if it's a schema reference
134146
const schemaName = extractSchemaNameFromRef(response.$ref)
135147
if (schemaName) {
136-
responseSchemaNames.add(schemaName)
148+
if (isError) {
149+
errorSchemaNames.add(schemaName)
150+
} else {
151+
responseSchemaNames.add(schemaName)
152+
}
137153
}
138154
} else if ('content' in response) {
139155
const content = response.content
@@ -143,7 +159,11 @@ export function generateInterface(
143159
'schema' in jsonContent &&
144160
jsonContent.schema
145161
) {
146-
collectSchemaNames(jsonContent.schema, responseSchemaNames)
162+
if (isError) {
163+
collectSchemaNames(jsonContent.schema, errorSchemaNames)
164+
} else {
165+
collectSchemaNames(jsonContent.schema, responseSchemaNames)
166+
}
147167
}
148168
}
149169
}
@@ -345,6 +365,110 @@ export function generateInterface(
345365
endpoint.response = responseType
346366
}
347367

368+
// Extract error
369+
// Check if error uses a component schema
370+
let errorType: unknown
371+
if (operation.responses) {
372+
// Find error responses (4xx, 5xx, or default)
373+
const errorResponse =
374+
operation.responses['400'] ||
375+
operation.responses['401'] ||
376+
operation.responses['403'] ||
377+
operation.responses['404'] ||
378+
operation.responses['422'] ||
379+
operation.responses['500'] ||
380+
operation.responses['default'] ||
381+
Object.entries(operation.responses).find(([statusCode]) =>
382+
isErrorStatusCode(statusCode),
383+
)?.[1]
384+
385+
if (errorResponse) {
386+
if ('$ref' in errorResponse) {
387+
// ResponseObject reference - skip for now
388+
// Could resolve if needed
389+
} else if ('content' in errorResponse) {
390+
const content = errorResponse.content
391+
const jsonContent = content?.['application/json']
392+
if (
393+
jsonContent &&
394+
'schema' in jsonContent &&
395+
jsonContent.schema
396+
) {
397+
// Check if schema is a direct reference to components.schemas
398+
if ('$ref' in jsonContent.schema) {
399+
const schemaName = extractSchemaNameFromRef(
400+
jsonContent.schema.$ref,
401+
)
402+
// Check if schema exists in components.schemas and is used in error
403+
if (
404+
schemaName &&
405+
schema.components?.schemas?.[schemaName] &&
406+
errorSchemaNames.has(schemaName)
407+
) {
408+
// Use component reference
409+
errorType = `DevupErrorComponentStruct['${schemaName}']`
410+
} else {
411+
// Extract schema type with response options
412+
const responseDefaultNonNullable =
413+
options?.responseDefaultNonNullable ?? true
414+
const { type: schemaType } = getTypeFromSchema(
415+
jsonContent.schema,
416+
schema,
417+
{ defaultNonNullable: responseDefaultNonNullable },
418+
)
419+
errorType = schemaType
420+
}
421+
} else {
422+
// Check if it's an array with items referencing a component schema
423+
const schemaObj =
424+
jsonContent.schema as OpenAPIV3_1.SchemaObject
425+
if (
426+
schemaObj.type === 'array' &&
427+
schemaObj.items &&
428+
'$ref' in schemaObj.items
429+
) {
430+
const schemaName = extractSchemaNameFromRef(
431+
schemaObj.items.$ref,
432+
)
433+
// Check if schema exists in components.schemas and is used in error
434+
if (
435+
schemaName &&
436+
schema.components?.schemas?.[schemaName] &&
437+
errorSchemaNames.has(schemaName)
438+
) {
439+
// Use component reference for array items
440+
errorType = `Array<DevupErrorComponentStruct['${schemaName}']>`
441+
} else {
442+
// Extract schema type with response options
443+
const responseDefaultNonNullable =
444+
options?.responseDefaultNonNullable ?? true
445+
const { type: schemaType } = getTypeFromSchema(
446+
jsonContent.schema,
447+
schema,
448+
{ defaultNonNullable: responseDefaultNonNullable },
449+
)
450+
errorType = schemaType
451+
}
452+
} else {
453+
// Extract schema type with response options
454+
const responseDefaultNonNullable =
455+
options?.responseDefaultNonNullable ?? true
456+
const { type: schemaType } = getTypeFromSchema(
457+
jsonContent.schema,
458+
schema,
459+
{ defaultNonNullable: responseDefaultNonNullable },
460+
)
461+
errorType = schemaType
462+
}
463+
}
464+
}
465+
}
466+
}
467+
}
468+
if (errorType !== undefined) {
469+
endpoint.error = errorType
470+
}
471+
348472
// Generate path key (normalize path by replacing {param} with converted param and removing slashes)
349473
const normalizedPath = path.replace(/\{([^}]+)\}/g, (_, param) => {
350474
// Convert param name based on case type
@@ -367,6 +491,7 @@ export function generateInterface(
367491
// Extract components schemas
368492
const requestComponents: Record<string, unknown> = {}
369493
const responseComponents: Record<string, unknown> = {}
494+
const errorComponents: Record<string, unknown> = {}
370495
if (schema.components?.schemas) {
371496
for (const [schemaName, schemaObj] of Object.entries(
372497
schema.components.schemas,
@@ -377,12 +502,16 @@ export function generateInterface(
377502
const responseDefaultNonNullable =
378503
options?.responseDefaultNonNullable ?? true
379504

505+
// Determine which defaultNonNullable to use based on where schema is used
506+
let defaultNonNullable = responseDefaultNonNullable
507+
if (requestSchemaNames.has(schemaName)) {
508+
defaultNonNullable = requestDefaultNonNullable
509+
}
510+
380511
const { type: schemaType } = getTypeFromSchema(
381512
schemaObj as OpenAPIV3_1.SchemaObject | OpenAPIV3_1.ReferenceObject,
382513
schema,
383-
requestSchemaNames.has(schemaName)
384-
? { defaultNonNullable: requestDefaultNonNullable }
385-
: { defaultNonNullable: responseDefaultNonNullable },
514+
{ defaultNonNullable },
386515
)
387516
// Keep original schema name as-is
388517
if (requestSchemaNames.has(schemaName)) {
@@ -391,6 +520,9 @@ export function generateInterface(
391520
if (responseSchemaNames.has(schemaName)) {
392521
responseComponents[schemaName] = schemaType
393522
}
523+
if (errorSchemaNames.has(schemaName)) {
524+
errorComponents[schemaName] = schemaType
525+
}
394526
}
395527
}
396528
}
@@ -443,9 +575,22 @@ export function generateInterface(
443575
? ` interface DevupResponseComponentStruct {\n${responseComponentEntries};\n }`
444576
: ' interface DevupResponseComponentStruct {}'
445577

578+
// Generate ErrorComponentStruct interface
579+
const errorComponentEntries = Object.entries(errorComponents)
580+
.map(([key, value]) => {
581+
const formattedValue = formatTypeValue(value, 2)
582+
return ` ${wrapInterfaceKeyGuard(key)}: ${formattedValue}`
583+
})
584+
.join(';\n')
585+
586+
const errorComponentInterface =
587+
errorComponentEntries.length > 0
588+
? ` interface DevupErrorComponentStruct {\n${errorComponentEntries};\n }`
589+
: ' interface DevupErrorComponentStruct {}'
590+
446591
const allInterfaces = interfaceContent
447-
? `${interfaceContent}\n\n${requestComponentInterface}\n\n${responseComponentInterface}`
448-
: `${requestComponentInterface}\n\n${responseComponentInterface}`
592+
? `${interfaceContent}\n\n${requestComponentInterface}\n\n${responseComponentInterface}\n\n${errorComponentInterface}`
593+
: `${requestComponentInterface}\n\n${responseComponentInterface}\n\n${errorComponentInterface}`
449594

450595
return `import "@devup-api/fetch";\n\ndeclare module "@devup-api/fetch" {\n${allInterfaces}\n}`
451596
}

0 commit comments

Comments
 (0)