Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 133 additions & 3 deletions packages/openapi-code-generator/src/core/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Responses,
Schema,
Server,
Style,
xInternalPreproccess,
} from "./openapi-types"
import type {
Expand All @@ -22,6 +23,11 @@ import type {
IRModelString,
IROperation,
IRParameter,
IRParameterBase,
IRParameterCookie,
IRParameterHeader,
IRParameterPath,
IRParameterQuery,
IRPreprocess,
IRRef,
IRResponse,
Expand Down Expand Up @@ -333,18 +339,142 @@ export class Input {
return parameters
.map((it) => this.loader.parameter(it))
.map((it: Parameter): IRParameter => {
return {
const base = {
name: it.name,
in: it.in,
schema: this.normalizeSchemaObject(it.schema),
description: it.description,
required: it.required ?? false,
deprecated: it.deprecated ?? false,
allowEmptyValue: it.allowEmptyValue ?? false,
} satisfies Omit<IRParameterBase, "explode">

function throwUnsupportedStyle(style: Style): never {
throw new Error(
`unsupported parameter style: '${style}' for in: '${it.in}'`,
)
}

switch (it.in) {
case "path": {
const style = it.style ?? "simple"
const explode = this.explodeForParameter(it, style)

if (!this.isStyleForPathParameter(style)) {
throwUnsupportedStyle(style)
}

return {
...base,
in: "path",
style,
explode,
} satisfies IRParameterPath
}

case "query": {
const style = it.style ?? "form"
const explode = this.explodeForParameter(it, style)

if (!this.isStyleForQueryParameter(style)) {
throwUnsupportedStyle(style)
}

return {
...base,
in: "query",
style,
explode,
allowEmptyValue: it.allowEmptyValue ?? false,
} satisfies IRParameterQuery
}

case "header": {
const style = it.style ?? "simple"
const explode = this.explodeForParameter(it, style)

if (!this.isStyleForHeaderParameter(style)) {
throwUnsupportedStyle(style)
}

return {
...base,
in: "header",
style,
explode,
} satisfies IRParameterHeader
}

case "cookie": {
const style = it.style ?? "form"
const explode = this.explodeForParameter(it, style)

if (!this.isStyleForCookieParameter(style)) {
throwUnsupportedStyle(style)
}

return {
...base,
in: "cookie",
style,
explode,
} satisfies IRParameterCookie
}

default: {
throw new Error(
`unsupported parameter location: '${it.in satisfies never}'`,
)
}
}
})
}

private isStyleForPathParameter(
style: Style,
): style is IRParameterPath["style"] {
return ["simple", "label", "matrix", "template"].includes(style)
}

private isStyleForQueryParameter(
style: Style,
): style is IRParameterQuery["style"] {
return ["form", "spaceDelimited", "pipeDelimited", "deepObject"].includes(
style,
)
}

private isStyleForHeaderParameter(
style: Style,
): style is IRParameterHeader["style"] {
return ["simple"].includes(style)
}

private isStyleForCookieParameter(
style: Style,
): style is IRParameterCookie["style"] {
if (style === "cookie") {
// todo: openapi v3.2.0
throw new Error("support for style: cookie not implemented.")
}

return ["form"].includes(style)
}

private explodeForParameter(parameter: Parameter, style: Style): boolean {
if (typeof parameter.explode === "boolean") {
return parameter.explode
}

/**
* "When style is "form" or "cookie", the default value is true. For all other styles, the default value is false."
* ref: {@link https://spec.openapis.org/oas/v3.2.0.html#parameter-explode}
*/
if (style === "form" || style === "cookie") {
return true
}

return false
}

private normalizeOperationId(
operationId: string | undefined,
method: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,52 @@ export interface IRServerVariable {
description: string | undefined
}

export interface IRParameter {
export interface IRParameterBase {
name: string
in: "path" | "query" | "header" | "cookie" | "body"
schema: MaybeIRModel
description: string | undefined
required: boolean
deprecated: boolean
allowEmptyValue: boolean
schema: MaybeIRModel
explode: boolean | undefined
}

export interface IRParameterPath extends IRParameterBase {
in: "path"
// todo: matrix/label not supported
style: "matrix" | "label" | "simple"
}

export interface IRParameterQuery extends IRParameterBase {
in: "query"
style: "form" | "spaceDelimited" | "pipeDelimited" | "deepObject" // default: form
explode: boolean | undefined // default: true for form/cookie, false for other styles
allowEmptyValue: boolean //default: false
}

export interface IRParameterHeader extends IRParameterBase {
in: "header"
style: "simple"
}

export interface IRParameterCookie extends IRParameterBase {
in: "cookie"
style: "form"
// todo: openapi v3.2.0 - support style: "cookie"
// | "cookie"
}

// note: not part of spec, but used internally
export interface IRParameterRequestBody extends IRParameterBase {
in: "body"
}

export type IRParameter =
| IRParameterPath
| IRParameterQuery
| IRParameterHeader
| IRParameterCookie
| IRParameterRequestBody

export interface IROperation {
route: string
method: HttpMethod
Expand Down
9 changes: 9 additions & 0 deletions packages/openapi-code-generator/src/core/openapi-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export type Style =
| "pipeDelimited"
| "simple"
| "spaceDelimited"
| "cookie"

export interface Encoding {
allowReserved?: boolean
Expand Down Expand Up @@ -202,7 +203,15 @@ export interface Header {
export interface Parameter {
name: string
in: "path" | "query" | "header" | "cookie"
// todo: openapi v3.2.0 - support querystring
// | "querystring"
schema: Schema | Reference
// todo: support content on parameters
// content?: {
// [contentType: string]: MediaType
// }
style?: Style
explode?: boolean
description?: string
required?: boolean
deprecated?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {Encoding} from "../../core/openapi-types"
import type {
IRMediaType,
IROperation,
IRParameter,
IRParameterRequestBody,
} from "../../core/openapi-types-normalized"
import {isDefined} from "../../core/utils"

Expand Down Expand Up @@ -148,7 +148,7 @@ export type Serializer = "JSON.stringify" | "String" | "URLSearchParams"

export type RequestBodyAsParameter = {
isSupported: boolean
parameter: IRParameter
parameter: IRParameterRequestBody
contentType: string
serializer: Serializer | undefined
encoding?: Record<string, Encoding>
Expand Down Expand Up @@ -249,8 +249,8 @@ export function requestBodyAsParameter(
description: requestBody.description,
in: "body",
required: requestBody.required,
explode: undefined,
schema: result.mediaType.schema,
allowEmptyValue: false,
deprecated: false,
},
serializer: result.serializer,
Expand All @@ -276,8 +276,8 @@ export function requestBodyAsParameter(
description: requestBody.description,
in: "body",
required: requestBody.required,
explode: undefined,
schema: {type: "never", nullable: false, readOnly: false},
allowEmptyValue: false,
deprecated: false,
},
serializer: undefined,
Expand Down
1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ ignoredBuiltDependencies:
- '@parcel/watcher'
- '@swc/core'
- esbuild
- lmdb
- nx
- sharp
- unrs-resolver
Expand Down