A code generation tool for openapi 3 / 3.1 specifications written in typescript, primarily aimed at generating typescript clients and server stubs. Other target languages may be added in future.
Problem Statement
Some openapi definitions don't make use of $ref's to name their schemas, which until now has resulted in the generated types/schemas being in-lined at the site of usage.
I've found this is especially common when some packages that generate openapi schemas from your server implementation are used, as the additional metadata to create $ref's is often omitted (YMMV).
This can make the code harder to read, and more awkward to consume, as you cannot easily use the generated types (without extracting them from another type), and the schemas simply can't be referenced.
Solution
Introduce a new experimental cli flag --extract-inline-schemas that will cause synthetic $ref's to be created from inline schemas on request and response bodies where the following is true:
It's not already a $ref
It's not a primitive type, eg: not a string / number
It's not an array with $ref for items
It seems to work fairly well, and is primarily experimental as I may still adjust the name generation, and it could be interesting to attempt de-duplication of repeated schemas - though I suspect that'll be difficult to do whilst keeping semantically meaningful names.
I've not enabled it on any integration tests yet, but the change for the new example operations added in this PR looks like this:
Problem Statement Some
openapi
definitions don't make use of$ref
's to name their schemas, which until now has resulted in the generated types/schemas being in-lined at the site of usage.I've found this is especially common when some packages that generate
openapi
schemas from your server implementation are used, as the additional metadata to create$ref
's is often omitted (YMMV).This can make the code harder to read, and more awkward to consume, as you cannot easily use the generated types (without extracting them from another type), and the schemas simply can't be referenced.
Solution Introduce a new experimental cli flag
--extract-inline-schemas
that will cause synthetic$ref
's to be created from inline schemas on request and response bodies where the following is true:$ref
string
/number
array
with$ref
for itemsIt seems to work fairly well, and is primarily experimental as I may still adjust the name generation, and it could be interesting to attempt de-duplication of repeated schemas - though I suspect that'll be difficult to do whilst keeping semantically meaningful names.
I've not enabled it on any integration tests yet, but the change for the new example operations added in this PR looks like this:
typescript-koa
```diff diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts index f14385a..16f39be 100644 --- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts +++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/generated.ts @@ -13,8 +13,17 @@ import { t_TodoList, t_UpdateTodoListByIdBodySchema, t_UpdateTodoListByIdParamSchema, + t_getTodoListItemsJson200Response, + t_getTodoListItemsJson5XXResponse, } from "./models" -import { s_CreateUpdateTodoList, s_Error, s_TodoList } from "./schemas" +import { + s_CreateUpdateTodoList, + s_Error, + s_TodoList, + s_createTodoListItemJsonRequestBody, + s_getTodoListItemsJson200Response, + s_getTodoListItemsJson5XXResponse, +} from "./schemas" import KoaRouter from "@koa/router" import { KoaRuntimeError, @@ -110,16 +119,10 @@ export type DeleteTodoListById = ( > export type GetTodoListItemsResponder = { - with200(): KoaRuntimeResponse<{ - completedAt?: string - content: string - createdAt: string - id: string - }> - withStatusCode5xx(status: StatusCode5xx): KoaRuntimeResponse<{ - code: string - message: string - }> + with200(): KoaRuntimeResponse
+ withStatusCode5xx(
+ status: StatusCode5xx,
+ ): KoaRuntimeResponse
} & KoaRuntimeResponder
export type GetTodoListItems = (
@@ -128,22 +131,8 @@ export type GetTodoListItems = (
ctx: Context,
) => Promise<
| KoaRuntimeResponse
- | Response<
- 200,
- {
- completedAt?: string
- content: string
- createdAt: string
- id: string
- }
- >
- | Response<
- StatusCode5xx,
- {
- code: string
- message: string
- }
- >
+ | Response<200, t_getTodoListItemsJson200Response>
+ | Response
>
export type CreateTodoListItemResponder = {
@@ -376,16 +365,8 @@ export function createRouter(implementation: Implementation): KoaRouter {
const getTodoListItemsResponseValidator = responseValidationFactory(
[
- [
- "200",
- z.object({
- id: z.string(),
- content: z.string(),
- createdAt: z.string().datetime({ offset: true }),
- completedAt: z.string().datetime({ offset: true }).optional(),
- }),
- ],
- ["5XX", z.object({ message: z.string(), code: z.string() })],
+ ["200", s_getTodoListItemsJson200Response],
+ ["5XX", s_getTodoListItemsJson5XXResponse],
],
undefined,
)
@@ -403,18 +384,10 @@ export function createRouter(implementation: Implementation): KoaRouter {
const responder = {
with200() {
- return new KoaRuntimeResponse<{
- completedAt?: string
- content: string
- createdAt: string
- id: string
- }>(200)
+ return new KoaRuntimeResponse(200)
},
withStatusCode5xx(status: StatusCode5xx) {
- return new KoaRuntimeResponse<{
- code: string
- message: string
- }>(status)
+ return new KoaRuntimeResponse(status)
},
withStatus(status: StatusCode) {
return new KoaRuntimeResponse(status)
@@ -437,11 +410,7 @@ export function createRouter(implementation: Implementation): KoaRouter {
const createTodoListItemParamSchema = z.object({ listId: z.string() })
- const createTodoListItemBodySchema = z.object({
- id: z.string(),
- content: z.string(),
- completedAt: z.string().datetime({ offset: true }).optional(),
- })
+ const createTodoListItemBodySchema = s_createTodoListItemJsonRequestBody
const createTodoListItemResponseValidator = responseValidationFactory(
[["204", z.void()]],
diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts
index da6dcb2..1f92dda 100644
--- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts
+++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/models.ts
@@ -38,6 +38,18 @@ export type t_GetTodoListItemsParamSchema = {
listId: string
}
+export type t_getTodoListItemsJson200Response = {
+ completedAt?: string
+ content: string
+ createdAt: string
+ id: string
+}
+
+export type t_getTodoListItemsJson5XXResponse = {
+ code: string
+ message: string
+}
+
export type t_GetTodoListsQuerySchema = {
created?: string
status?: "incomplete" | "complete"
diff --git a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts
index 3c99ec6..f132969 100644
--- a/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts
+++ b/integration-tests/typescript-koa/src/generated/todo-lists.yaml/schemas.ts
@@ -19,3 +19,21 @@ export const s_Error = z.object({
message: z.string().optional(),
code: z.coerce.number().optional(),
})
+
+export const s_getTodoListItemsJson200Response = z.object({
+ id: z.string(),
+ content: z.string(),
+ createdAt: z.string().datetime({ offset: true }),
+ completedAt: z.string().datetime({ offset: true }).optional(),
+})
+
+export const s_getTodoListItemsJson5XXResponse = z.object({
+ message: z.string(),
+ code: z.string(),
+})
+
+export const s_createTodoListItemJsonRequestBody = z.object({
+ id: z.string(),
+ content: z.string(),
+ completedAt: z.string().datetime({ offset: true }).optional(),
+})
```
typescript-fetch
```diff diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts index e441863..78bfdea 100644 --- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts +++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/client.ts @@ -2,7 +2,14 @@ /* tslint:disable */ /* eslint-disable */ -import { t_CreateUpdateTodoList, t_Error, t_TodoList } from "./models" +import { + t_CreateUpdateTodoList, + t_Error, + t_TodoList, + t_createTodoListItemJsonRequestBody, + t_getTodoListItemsJson200Response, + t_getTodoListItemsJson5XXResponse, +} from "./models" import { AbstractFetchClient, AbstractFetchClientConfig, @@ -100,22 +107,8 @@ export class ApiClient extends AbstractFetchClient { opts?: RequestInit, ): Promise< TypedFetchResponse< - | Res< - 200, - { - completedAt?: string - content: string - createdAt: string - id: string - } - > - | Res< - StatusCode5xx, - { - code: string - message: string - } - > + | Res<200, t_getTodoListItemsJson200Response> + | Res
>
> {
const url = this.basePath + `/list/${p["listId"]}/items`
@@ -126,11 +119,7 @@ export class ApiClient extends AbstractFetchClient {
async createTodoListItem(
p: {
listId: string
- requestBody: {
- completedAt?: string
- content: string
- id: string
- }
+ requestBody: t_createTodoListItemJsonRequestBody
},
timeout?: number,
opts?: RequestInit,
diff --git a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts
index 2874055..9978df9 100644
--- a/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts
+++ b/integration-tests/typescript-fetch/src/generated/todo-lists.yaml/models.ts
@@ -19,3 +19,21 @@ export type t_TodoList = {
totalItemCount: number
updated: string
}
+
+export type t_createTodoListItemJsonRequestBody = {
+ completedAt?: string
+ content: string
+ id: string
+}
+
+export type t_getTodoListItemsJson200Response = {
+ completedAt?: string
+ content: string
+ createdAt: string
+ id: string
+}
+
+export type t_getTodoListItemsJson5XXResponse = {
+ code: string
+ message: string
+}
````