Skip to content

Commit

Permalink
feat: support validateStatus
Browse files Browse the repository at this point in the history
  • Loading branch information
haoziqaq committed Jan 19, 2025
1 parent 3206004 commit 19735a3
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 37 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ See the bottom of the document for template variable definitions.
export interface Config {
/**
* The path to the OpenAPI/Swagger schema file.
* @default './schema.json'
*/
input?: string
/**
* The path to the output directory.
* @default './src/apis/generated'
*/
output?: string
/**
Expand All @@ -114,28 +116,38 @@ export interface Config {
base?: string
/**
* The filename of the generated openapi types file.
* @default '_types.ts'
*/
typesFilename?: string
/**
* The transformer api options, used to override the default transformation rules.
*/
transformer?: Partial<Transformer>
/**
* Whether to generate TypeScript code.
* @default true
*/
ts?: boolean
/**
* Whether to generate only types.
* @default false
*/
typesOnly?: boolean
/**
* Whether to override the existing files, or an array of filenames to override.
* @default true
*/
overrides?: boolean | string[]
/**
* The preset ejs template to use.
* @default 'axle'
*/
preset?: Preset
/**
* Defines which return status codes will be typed
* @default (status) => status >= 200 && status < 300
*/
validateStatus?: (status: number) => boolean
/**
* The transformer api options, used to override the default transformation rules.
*/
transformer?: Partial<Transformer>
}
```

Expand Down
41 changes: 29 additions & 12 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getConfig } from './config'
import { CWD, SUPPORTED_HTTP_METHODS } from './constants'
import { createTransformer, Transformer, TransformerBaseArgs } from './transformer'
import {
getSuccessfulResponseMeme,
getValidResponseMetadataItems,
hasQueryParameter,
isRequiredRequestBody,
Preset,
Expand Down Expand Up @@ -105,10 +105,12 @@ export interface ApiModulePayload {
export interface GenerateOptions {
/**
* The path to the OpenAPI/Swagger schema file.
* @default './schema.json'
*/
input?: string
/**
* The path to the output directory.
* @default './src/apis/generated'
*/
output?: string
/**
Expand All @@ -117,40 +119,50 @@ export interface GenerateOptions {
base?: string
/**
* The filename of the generated openapi types file.
* @default '_types.ts'
*/
typesFilename?: string
/**
* The transformer api options, used to override the default transformation rules.
*/
transformer?: Partial<Transformer>
/**
* Whether to generate TypeScript code.
* @default true
*/
ts?: boolean
/**
* Whether to generate only types.
* @default false
*/
typesOnly?: boolean
/**
* Whether to override the existing files, or an array of filenames to override.
* @default true
*/
overrides?: boolean | string[]
/**
* The preset ejs template to use.
* @default 'axle'
*/
preset?: Preset
/**
* Defines which return status codes will be typed
* @default (status) => status >= 200 && status < 300
*/
validateStatus?: (status: number) => boolean
/**
* The transformer api options, used to override the default transformation rules.
*/
transformer?: Partial<Transformer>
}

export function transformPayloads(
pathItems: Record<string, OperationObject>,
options: {
path: string

transformer: Transformer
base: string | undefined
validateStatus: (status: number) => boolean
},
) {
const { transformer, path, base } = options
const { transformer, path, base, validateStatus } = options
return Object.entries(pathItems)
.filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key))
.reduce((payloads, [method, operation]) => {
Expand Down Expand Up @@ -180,11 +192,11 @@ export function transformPayloads(
})
: 'undefined'

const { mime, statusCode } = getSuccessfulResponseMeme(operation)
const typeResponseBody = transformer.typeResponseBody({ ...args, type, verb, entity })
const responseMetadataItems = getValidResponseMetadataItems(operation, validateStatus)
const typeResponseBodyValue =
mime && statusCode
? transformer.typeResponseBodyValue({ ...args, type, verb, entity, statusCode, mime })
responseMetadataItems.length > 0
? transformer.typeResponseBodyValue({ ...args, type, verb, entity, responseMetadataItems })
: 'undefined'

payloads.push({
Expand Down Expand Up @@ -212,9 +224,10 @@ export function partitionApiModules(
options: {
transformer: Transformer
base: string | undefined
validateStatus: (status: number) => boolean
},
): ApiModule[] {
const { base, transformer } = options
const { base, transformer, validateStatus } = options

const schemaPaths = schema.paths ?? {}
const schemaPathKeys = base ? Object.keys(schemaPaths).map((key) => key.replace(base, '')) : Object.keys(schemaPaths)
Expand All @@ -223,7 +236,9 @@ export function partitionApiModules(
const payloads = paths.reduce((payloads, path) => {
const pathItems = schemaPaths[path] as Record<string, OperationObject>

payloads.push(...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer }))
payloads.push(
...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer, validateStatus }),
)

return payloads
}, [] as ApiModulePayload[])
Expand Down Expand Up @@ -310,6 +325,7 @@ export async function generate(userOptions: GenerateOptions = {}) {
input = './schema.json',
output = './src/apis/generated',
typesFilename = '_types.ts',
validateStatus = (status: number) => status >= 200 && status < 300,
transformer = {},
} = options

Expand All @@ -326,6 +342,7 @@ export async function generate(userOptions: GenerateOptions = {}) {
const apiModules = partitionApiModules(schema, {
base,
transformer: mergedTransformer,
validateStatus,
})

await renderApiModules(apiModules, { output, typesFilename, ts, typesOnly, overrides, preset })
Expand Down
11 changes: 6 additions & 5 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { OperationObject } from 'openapi-typescript'
import pluralize from 'pluralize'
import { camelize, pascalCase } from 'rattail'
import { ResponseMetadataItem } from './utils'

export type TransformerBaseArgs = {
path: string
Expand Down Expand Up @@ -94,16 +95,16 @@ export function transformTypeResponseBody({

export function transformTypeResponseBodyValue({
type,
statusCode,
mime,
responseMetadataItems,
}: {
type: string
verb: string
entity: string
statusCode: number
mime: string
responseMetadataItems: ResponseMetadataItem[]
} & TransformerBaseArgs) {
return `${type}['responses']['${statusCode}']['content']['${mime}']`
return responseMetadataItems
.map(({ status, mime }) => `${type}['responses']['${status}']['content']['${mime}']`)
.join(' | ')
}

export interface Transformer {
Expand Down
36 changes: 20 additions & 16 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,29 @@ export function isRequiredRequestBody(value: RequestBodyObject | ReferenceObject
return 'required' in value && value.required === true
}

export function getSuccessfulResponseMeme(operation: OperationObject) {
export type ResponseMetadataItem = { status: number; mime: string }

export function getValidResponseMetadataItems(
operation: OperationObject,
validateStatus: (status: number) => boolean,
): ResponseMetadataItem[] {
const responses = operation.responses ?? {}
const codeKey = Object.keys(responses)
const validStatusResults = Object.keys(responses)
.sort((a, b) => Number(a) - Number(b))
.find((codeKey) => Number(codeKey) >= 200 && Number(codeKey) <= 299)
.filter((key) => validateStatus(Number(key)))
.map(Number)

if (!codeKey) {
return {
statusCode: undefined,
mime: undefined,
}
}
const results = validStatusResults
.map((status) => {
const content = (operation.responses?.[status] as ResponseObject | undefined)?.content
const mime = content?.['application/json'] ? 'application/json' : content?.['*/*'] ? '*/*' : undefined

const statusCode = Number(codeKey)
const content = (operation.responses?.[statusCode] as ResponseObject | undefined)?.content
const mime = content?.['application/json'] ? 'application/json' : content?.['*/*'] ? '*/*' : undefined
return {
status,
mime,
}
})
.filter((result) => result.mime) as ResponseMetadataItem[]

return {
statusCode,
mime,
}
return results
}

0 comments on commit 19735a3

Please sign in to comment.