Skip to content

Commit 46b988f

Browse files
committed
wip: decouple
1 parent 12cd5f1 commit 46b988f

14 files changed

+309
-278
lines changed

packages/openapi-code-generator/src/core/input.ts

Lines changed: 129 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
IRModelObject,
2424
IRModelString,
2525
IROperation,
26+
IROperationParams,
2627
IRParameter,
2728
IRParameterBase,
2829
IRParameterCookie,
@@ -31,6 +32,7 @@ import type {
3132
IRParameterQuery,
3233
IRPreprocess,
3334
IRRef,
35+
IRRequestBody,
3436
IRResponse,
3537
IRServer,
3638
IRServerVariable,
@@ -59,7 +61,7 @@ export class Input {
5961
constructor(
6062
readonly loader: OpenapiLoader,
6163
readonly config: InputConfig,
62-
private readonly schemaNormalizer = new SchemaNormalizer(config),
64+
readonly schemaNormalizer = new SchemaNormalizer(config),
6365
private readonly parameterNormalizer = new ParameterNormalizer(
6466
schemaNormalizer,
6567
),
@@ -89,8 +91,10 @@ export class Input {
8991

9092
return Object.fromEntries(
9193
Object.entries(schemas).map(([name, maybeSchema]) => {
92-
// TODO: double normalization?
93-
return [name, this.schema(this.schemaNormalizer.normalize(maybeSchema))]
94+
const schema = this.schemaNormalizer.normalize(
95+
this.loader.schema(maybeSchema),
96+
)
97+
return [name, schema]
9498
}),
9599
)
96100
}
@@ -119,8 +123,6 @@ export class Input {
119123
)) {
120124
paths = this.loader.paths(paths)
121125

122-
const params = this.normalizeParameters(paths.parameters)
123-
124126
const additionalAttributes = Object.fromEntries(
125127
Object.entries(paths).filter(
126128
([key]) => key !== "parameters" && !isHttpMethod(key),
@@ -154,16 +156,18 @@ export class Input {
154156
throw new Error("callbacks are not supported")
155157
}
156158

159+
const parameters = (paths.parameters ?? []).concat(
160+
definition.parameters ?? [],
161+
)
162+
157163
result.push({
158164
...additionalAttributes,
159165
route,
160166
method,
161167
servers: this.normalizeServers(
162168
coalesce(definition.servers, paths.servers, []),
163169
),
164-
parameters: params.concat(
165-
this.normalizeParameters(definition.parameters),
166-
),
170+
params: this.normalizeParameters(operationId, parameters),
167171
operationId,
168172
tags: definition.tags ?? [],
169173
requestBody: this.normalizeRequestBodyObject(
@@ -235,9 +239,13 @@ export class Input {
235239
).map(([name, operations]) => ({name, operations}))
236240
}
237241

238-
schema(maybeRef: Reference | Schema): IRModel {
239-
const schema = this.loader.schema(maybeRef)
240-
return this.schemaNormalizer.normalize(schema)
242+
schema(maybeRef: MaybeIRModel): IRModel {
243+
if (isRef(maybeRef)) {
244+
const schema = this.loader.schema(maybeRef)
245+
return this.schemaNormalizer.normalize(schema)
246+
}
247+
248+
return maybeRef
241249
}
242250

243251
preprocess(maybePreprocess: Reference | xInternalPreproccess): IRPreprocess {
@@ -288,7 +296,7 @@ export class Input {
288296
private normalizeRequestBodyObject(
289297
operationId: string,
290298
requestBody?: RequestBody | Reference,
291-
) {
299+
): IRRequestBody | undefined {
292300
if (!requestBody) {
293301
return undefined
294302
}
@@ -340,11 +348,73 @@ export class Input {
340348
}
341349

342350
private normalizeParameters(
351+
operationId: string,
343352
parameters: (Parameter | Reference)[] = [],
344-
): IRParameter[] {
345-
return parameters
346-
.map((it) => this.loader.parameter(it))
347-
.map((it) => this.parameterNormalizer.normalizeParameter(it))
353+
): IROperationParams {
354+
const allParameters = parameters.map((it) => this.loader.parameter(it))
355+
356+
const pathParameters = allParameters.filter((it) => it.in === "path")
357+
const queryParameters = allParameters.filter((it) => it.in === "query")
358+
const headerParameters = allParameters.filter((it) => it.in === "header")
359+
360+
const normalizedParameters = allParameters.map((it) =>
361+
this.parameterNormalizer.normalizeParameter(it),
362+
)
363+
364+
return {
365+
all: normalizedParameters,
366+
path: {
367+
name: `${operationId}ParamSchema`,
368+
list: normalizedParameters.filter((it) => it.in === "path"),
369+
$ref: this.loader.addVirtualType(
370+
operationId,
371+
upperFirst(`${operationId}ParamSchema`),
372+
this.reduceParametersToOpenApiSchema(pathParameters),
373+
),
374+
},
375+
query: {
376+
name: `${operationId}QuerySchema`,
377+
list: normalizedParameters.filter((it) => it.in === "query"),
378+
$ref: this.loader.addVirtualType(
379+
operationId,
380+
upperFirst(`${operationId}QuerySchema`),
381+
this.reduceParametersToOpenApiSchema(queryParameters),
382+
),
383+
},
384+
header: {
385+
name: `${operationId}RequestHeaderSchema`,
386+
list: normalizedParameters.filter((it) => it.in === "header"),
387+
$ref: this.loader.addVirtualType(
388+
operationId,
389+
upperFirst(`${operationId}RequestHeaderSchema`),
390+
this.reduceParametersToOpenApiSchema(headerParameters),
391+
),
392+
},
393+
}
394+
}
395+
396+
private reduceParametersToOpenApiSchema(
397+
parameters: Parameter[],
398+
): SchemaObject {
399+
const properties: Record<string, Schema | Reference> = {}
400+
const required: string[] = []
401+
402+
for (const parameter of parameters) {
403+
properties[parameter.name] = parameter.schema
404+
405+
if (parameter.required) {
406+
required.push(parameter.name)
407+
}
408+
}
409+
410+
return {
411+
isIRModel: false,
412+
type: "object",
413+
properties,
414+
required,
415+
additionalProperties: false,
416+
nullable: false,
417+
}
348418
}
349419

350420
private normalizeOperationId(
@@ -374,28 +444,28 @@ export class Input {
374444

375445
return Object.fromEntries(
376446
filtered.map(([contentType, mediaType]) => {
377-
return [
378-
contentType,
379-
{
380-
schema: this.normalizeMediaTypeSchema(
381-
operationId,
382-
contentType,
383-
mediaType.schema,
384-
suffix,
385-
hasMultipleMediaTypes,
386-
),
387-
encoding: mediaType.encoding,
388-
},
389-
]
390-
}),
447+
return [
448+
contentType,
449+
{
450+
schema: this.normalizeMediaTypeSchema(
451+
operationId,
452+
contentType,
453+
mediaType.schema,
454+
suffix,
455+
hasMultipleMediaTypes,
456+
),
457+
encoding: mediaType.encoding,
458+
},
459+
]
460+
}),
391461
)
392462
}
393463

394464
private normalizeMediaTypeSchema(
395465
operationId: string,
396466
mediaType: string,
397467
schema: Schema | Reference,
398-
suffix: string,
468+
suffix: "RequestBody" | `${string}Response`,
399469
hasMultipleMediaTypes: boolean,
400470
): MaybeIRModel {
401471
const syntheticName = `${upperFirst(operationId)}${
@@ -405,15 +475,16 @@ export class Input {
405475
const result = this.schemaNormalizer.normalize(schema)
406476

407477
const shouldCreateVirtualType =
408-
this.config.extractInlineSchemas &&
478+
(this.config.extractInlineSchemas || suffix === "RequestBody") &&
409479
!isRef(result) &&
480+
!isRef(schema) &&
410481
(result.type === "object" ||
411482
(result.type === "array" &&
412483
!isRef(result.items) &&
413484
result.items.type === "object"))
414485

415486
return shouldCreateVirtualType
416-
? this.loader.addVirtualType(operationId, syntheticName, result)
487+
? this.loader.addVirtualType(operationId, syntheticName, schema)
417488
: result
418489
}
419490
}
@@ -461,6 +532,17 @@ export class ParameterNormalizer {
461532
throwUnsupportedStyle(style)
462533
}
463534

535+
// todo: add if dereferenced(base.schema).type === "array
536+
/*
537+
538+
"x-internal-preprocess": {
539+
deserialize: {
540+
fn: "(it: unknown) => Array.isArray(it) || it === undefined ? it : [it]",
541+
},
542+
},
543+
544+
*/
545+
464546
return {
465547
...base,
466548
in: "query",
@@ -571,25 +653,30 @@ export class SchemaNormalizer {
571653
return schemaObject satisfies IRRef
572654
}
573655

656+
if (Reflect.get(schemaObject, "isIRModel")) {
657+
throw new Error("double normalization!")
658+
}
659+
574660
// TODO: HACK: translates a type array into a a oneOf - unsure if this makes sense,
575661
// or is the cleanest way to do it. I'm fairly sure this will work fine
576662
// for most things though.
577663
if (Array.isArray(schemaObject.type)) {
578664
const nullable = Boolean(schemaObject.type.find((it) => it === "null"))
579665
return self.normalize({
666+
isIRModel: false,
667+
type: "object",
580668
oneOf: schemaObject.type
581669
.filter((it) => it !== "null")
582-
.map((it) =>
583-
self.normalize({
584-
...schemaObject,
585-
type: it,
586-
nullable,
587-
}),
588-
),
670+
.map((it) => ({
671+
...schemaObject,
672+
type: it,
673+
nullable,
674+
})),
589675
})
590676
}
591677

592678
const base: IRModelBase = {
679+
isIRModel: true,
593680
nullable: schemaObject.nullable || false,
594681
readOnly: schemaObject.readOnly || false,
595682
default: schemaObject.default,
@@ -795,10 +882,11 @@ export class SchemaNormalizer {
795882
type: "never",
796883
}
797884
}
798-
default:
885+
default: {
799886
throw new Error(
800887
`unsupported type '${schemaObject.type satisfies never}'`,
801888
)
889+
}
802890
}
803891

804892
function normalizeProperties(

packages/openapi-code-generator/src/core/openapi-types-normalized.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {Style} from "./openapi-types"
1+
import type {Reference, Style} from "./openapi-types"
22
import type {HttpMethod} from "./utils"
33

44
export interface IRRef {
@@ -7,6 +7,7 @@ export interface IRRef {
77
}
88

99
export interface IRModelBase {
10+
isIRModel: true
1011
// Note: meaningless for top level objects, maybe we can exclude these somehow in that case
1112
nullable: boolean /* false */
1213
readOnly: boolean /* false */
@@ -166,14 +167,29 @@ export type IRParameter =
166167
| IRParameterPath
167168
| IRParameterQuery
168169
| IRParameterHeader
169-
| IRParameterCookie
170170
| IRParameterRequestBody
171+
| IRParameterCookie
172+
173+
/**
174+
* name - variable name for generated code
175+
* $ref - location of the schema encapsulating params into an object
176+
* list - list of the parameters
177+
*/
178+
export interface IROperationParams {
179+
all: IRParameter[]
180+
path: {name: string; list: IRParameterPath[]; $ref: Reference}
181+
query: {name: string; list: IRParameterQuery[]; $ref: Reference}
182+
header: {name: string; list: IRParameterHeader[]; $ref: Reference}
183+
}
171184

172185
export interface IROperation {
186+
operationId: string
187+
173188
route: string
174189
method: HttpMethod
175-
parameters: IRParameter[]
176-
operationId: string
190+
191+
params: IROperationParams
192+
177193
tags: string[]
178194
requestBody: IRRequestBody | undefined
179195
responses:

0 commit comments

Comments
 (0)