Extract inline schemas
We have experimental support for "extracting inline schemas" behind the
--extract-inline-schemas
/ OPENAPI_EXTRACT_INLINE_SCHEMAS=true
configuration flag.
What does this mean?
There are basically two ways you can define schemas in your openapi specifications:
- Named schemas
- Inline schemas
These are handled differently by code generation. Enabling --extract-inline-schemas
aims to
make inline schemas emit similar code to named schemas.
Named schema example
Normally when writing openapi specifications it is desirable to make use of $ref
and define your schemas as named
components.
paths:
/list/{listId}:
parameters:
- $ref: '#/components/parameters/listId'
get:
operationId: getTodoListById
responses:
200:
description: 'success'
content:
application/json:
schema:
$ref: '#/components/schemas/TodoList'
components:
schemas:
TodoList:
type: object
required:
- id
- name
- totalItemCount
- incompleteItemCount
- created
properties:
id:
type: string
format: uuid
name:
type: string
totalItemCount:
type: number
incompleteItemCount:
type: number
created:
type: string
format: date-time
When we run code generation for this, we expect a type and a schema for the TodoList
to be generated, something like:
import {z} from 'zod'
export type t_TodoList = {
id: string
name: string
totalItemCount: number
incompleteItemCount: number
created: string
}
export const s_TodoList = z.object({
id: z.string(),
name: z.string(),
totalItemCount: z.coerce.number(),
incompleteItemCount: z.coerce.number(),
created: z.string().datetime({ offset: true }),
})
This is useful, as it means that we can easily reference the type, or use the schema as we require.
Inline Schema Example
However, not everyone will write their specifications using named $ref
s, and instead inline schemas may be used.
This is especially prolific when generating the specification from implementation code in our experience.
Consider the same example as above, but with the schema inlined:
paths:
/list/{listId}:
parameters:
- $ref: '#/components/parameters/listId'
get:
operationId: getTodoListById
responses:
200:
description: 'success'
content:
application/json:
schema:
type: object
required:
- id
- name
- totalItemCount
- incompleteItemCount
- created
properties:
id:
type: string
format: uuid
name:
type: string
totalItemCount:
type: number
incompleteItemCount:
type: number
created:
type: string
format: date-time
components:
schemas: {}
By default, this will be emitted as in-line types / schemas
export type GetTodoListById = (
params: Params<t_GetTodoListByIdParamSchema, void, void>,
respond: GetTodoListByIdResponder,
ctx: RouterContext,
) => Promise<
| KoaRuntimeResponse<unknown>
| Response<
200,
{
id: string
name: string
totalItemCount: number
incompleteItemCount: number
created: string
}
>
| Response<StatusCode4xx, t_Error>
| Response<StatusCode, void>
>
const getTodoListByIdResponseValidator = responseValidationFactory(
[
[
"200",
z.object({
id: z.string(),
name: z.string(),
totalItemCount: z.coerce.number(),
incompleteItemCount: z.coerce.number(),
created: z.string().datetime({ offset: true }),
}),
],
["4XX", s_Error],
],
z.undefined(),
)
router.get("getTodoListById", "/list/:listId", async (ctx, next) => {
// ...
const responder = {
with200() {
return new KoaRuntimeResponse<{
id: string
name: string
totalItemCount: number
incompleteItemCount: number
created: string
}>(200)
}
}
// ...
})
With --extract-inline-schemas
enabled
Notice how this:
- Creates a lot of duplication, we have to repeat the definition anytime it is used
- Makes it inconvenient, or impossible to reference the type/schema in our implementation code
With --extract-inline-schemas
enabled, the code generator will synthesis a name for each inline schema based on
its usage, and emit exported types/schemas, eg:
export type t_getTodoListByIdJson200Response = {
id: string
name: string
totalItemCount: number
incompleteItemCount: number
created: string
}
export const s_getTodoListByIdJson200Response = z.object({
id: z.string(),
name: z.string(),
totalItemCount: z.coerce.number(),
incompleteItemCount: z.coerce.number(),
created: z.string().datetime({ offset: true }),
})
This can be a handy trick to make the code generated from schemas you don't own/control easier to work with. In general you should prefer to improve the specifications to be more suitable for code generation, which generally also improves the result of documentation tools like Redoc (opens in a new tab)