Skip to content

Commit 61e9db9

Browse files
authored
refactor: split input into more testable classes (#388)
no behavior changes yet, split from #386
1 parent 4a7d7f9 commit 61e9db9

File tree

1 file changed

+159
-142
lines changed
  • packages/openapi-code-generator/src/core

1 file changed

+159
-142
lines changed

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

Lines changed: 159 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export class Input {
5757
constructor(
5858
readonly loader: OpenapiLoader,
5959
readonly config: InputConfig,
60+
private readonly schemaNormalizer = new SchemaNormalizer(config),
61+
private readonly parameterNormalizer = new ParameterNormalizer(
62+
schemaNormalizer,
63+
),
6064
) {}
6165

6266
name(): string {
@@ -84,7 +88,10 @@ export class Input {
8488
return Object.fromEntries(
8589
Object.entries(schemas).map(([name, maybeSchema]) => {
8690
// TODO: double normalization?
87-
return [name, this.schema(this.normalizeSchemaObject(maybeSchema))]
91+
return [
92+
name,
93+
this.schema(this.schemaNormalizer.normalizeSchemaObject(maybeSchema)),
94+
]
8895
}),
8996
)
9097
}
@@ -231,7 +238,7 @@ export class Input {
231238

232239
schema(maybeRef: Reference | Schema): IRModel {
233240
const schema = this.loader.schema(maybeRef)
234-
return this.normalizeSchemaObject(schema)
241+
return this.schemaNormalizer.normalizeSchemaObject(schema)
235242
}
236243

237244
preprocess(maybePreprocess: Reference | xInternalPreproccess): IRPreprocess {
@@ -338,141 +345,7 @@ export class Input {
338345
): IRParameter[] {
339346
return parameters
340347
.map((it) => this.loader.parameter(it))
341-
.map((it: Parameter): IRParameter => {
342-
const base = {
343-
name: it.name,
344-
schema: this.normalizeSchemaObject(it.schema),
345-
description: it.description,
346-
required: it.required ?? false,
347-
deprecated: it.deprecated ?? false,
348-
} satisfies Omit<IRParameterBase, "explode">
349-
350-
function throwUnsupportedStyle(style: Style): never {
351-
throw new Error(
352-
`unsupported parameter style: '${style}' for in: '${it.in}'`,
353-
)
354-
}
355-
356-
switch (it.in) {
357-
case "path": {
358-
const style = it.style ?? "simple"
359-
const explode = this.explodeForParameter(it, style)
360-
361-
if (!this.isStyleForPathParameter(style)) {
362-
throwUnsupportedStyle(style)
363-
}
364-
365-
return {
366-
...base,
367-
in: "path",
368-
style,
369-
explode,
370-
} satisfies IRParameterPath
371-
}
372-
373-
case "query": {
374-
const style = it.style ?? "form"
375-
const explode = this.explodeForParameter(it, style)
376-
377-
if (!this.isStyleForQueryParameter(style)) {
378-
throwUnsupportedStyle(style)
379-
}
380-
381-
return {
382-
...base,
383-
in: "query",
384-
style,
385-
explode,
386-
allowEmptyValue: it.allowEmptyValue ?? false,
387-
} satisfies IRParameterQuery
388-
}
389-
390-
case "header": {
391-
const style = it.style ?? "simple"
392-
const explode = this.explodeForParameter(it, style)
393-
394-
if (!this.isStyleForHeaderParameter(style)) {
395-
throwUnsupportedStyle(style)
396-
}
397-
398-
return {
399-
...base,
400-
in: "header",
401-
style,
402-
explode,
403-
} satisfies IRParameterHeader
404-
}
405-
406-
case "cookie": {
407-
const style = it.style ?? "form"
408-
const explode = this.explodeForParameter(it, style)
409-
410-
if (!this.isStyleForCookieParameter(style)) {
411-
throwUnsupportedStyle(style)
412-
}
413-
414-
return {
415-
...base,
416-
in: "cookie",
417-
style,
418-
explode,
419-
} satisfies IRParameterCookie
420-
}
421-
422-
default: {
423-
throw new Error(
424-
`unsupported parameter location: '${it.in satisfies never}'`,
425-
)
426-
}
427-
}
428-
})
429-
}
430-
431-
private isStyleForPathParameter(
432-
style: Style,
433-
): style is IRParameterPath["style"] {
434-
return ["simple", "label", "matrix", "template"].includes(style)
435-
}
436-
437-
private isStyleForQueryParameter(
438-
style: Style,
439-
): style is IRParameterQuery["style"] {
440-
return ["form", "spaceDelimited", "pipeDelimited", "deepObject"].includes(
441-
style,
442-
)
443-
}
444-
445-
private isStyleForHeaderParameter(
446-
style: Style,
447-
): style is IRParameterHeader["style"] {
448-
return ["simple"].includes(style)
449-
}
450-
451-
private isStyleForCookieParameter(
452-
style: Style,
453-
): style is IRParameterCookie["style"] {
454-
if (style === "cookie") {
455-
// todo: openapi v3.2.0
456-
throw new Error("support for style: cookie not implemented.")
457-
}
458-
459-
return ["form"].includes(style)
460-
}
461-
462-
private explodeForParameter(parameter: Parameter, style: Style): boolean {
463-
if (typeof parameter.explode === "boolean") {
464-
return parameter.explode
465-
}
466-
467-
/**
468-
* "When style is "form" or "cookie", the default value is true. For all other styles, the default value is false."
469-
* ref: {@link https://spec.openapis.org/oas/v3.2.0.html#parameter-explode}
470-
*/
471-
if (style === "form" || style === "cookie") {
472-
return true
473-
}
474-
475-
return false
348+
.map((it) => this.parameterNormalizer.normalizeParameter(it))
476349
}
477350

478351
private normalizeOperationId(
@@ -525,7 +398,7 @@ export class Input {
525398
const syntheticName = `${operationId}${mediaTypeToIdentifier(
526399
mediaType,
527400
)}${suffix}`
528-
const result = this.normalizeSchemaObject(schema)
401+
const result = this.schemaNormalizer.normalizeSchemaObject(schema)
529402

530403
const shouldCreateVirtualType =
531404
this.config.extractInlineSchemas &&
@@ -539,13 +412,157 @@ export class Input {
539412
? this.loader.addVirtualType(operationId, syntheticName, result)
540413
: result
541414
}
415+
}
416+
417+
export class ParameterNormalizer {
418+
constructor(private readonly schemaNormalizer: SchemaNormalizer) {}
419+
420+
public normalizeParameter(it: Parameter): IRParameter {
421+
const base = {
422+
name: it.name,
423+
schema: this.schemaNormalizer.normalizeSchemaObject(it.schema),
424+
description: it.description,
425+
required: it.required ?? false,
426+
deprecated: it.deprecated ?? false,
427+
} satisfies Omit<IRParameterBase, "explode">
428+
429+
function throwUnsupportedStyle(style: Style): never {
430+
throw new Error(
431+
`unsupported parameter style: '${style}' for in: '${it.in}'`,
432+
)
433+
}
434+
435+
switch (it.in) {
436+
case "path": {
437+
const style = it.style ?? "simple"
438+
const explode = this.explodeForParameter(it, style)
439+
440+
if (!this.isStyleForPathParameter(style)) {
441+
throwUnsupportedStyle(style)
442+
}
443+
444+
return {
445+
...base,
446+
in: "path",
447+
style,
448+
explode,
449+
} satisfies IRParameterPath
450+
}
451+
452+
case "query": {
453+
const style = it.style ?? "form"
454+
const explode = this.explodeForParameter(it, style)
455+
456+
if (!this.isStyleForQueryParameter(style)) {
457+
throwUnsupportedStyle(style)
458+
}
459+
460+
return {
461+
...base,
462+
in: "query",
463+
style,
464+
explode,
465+
allowEmptyValue: it.allowEmptyValue ?? false,
466+
} satisfies IRParameterQuery
467+
}
468+
469+
case "header": {
470+
const style = it.style ?? "simple"
471+
const explode = this.explodeForParameter(it, style)
472+
473+
if (!this.isStyleForHeaderParameter(style)) {
474+
throwUnsupportedStyle(style)
475+
}
476+
477+
return {
478+
...base,
479+
in: "header",
480+
style,
481+
explode,
482+
} satisfies IRParameterHeader
483+
}
484+
485+
case "cookie": {
486+
const style = it.style ?? "form"
487+
const explode = this.explodeForParameter(it, style)
488+
489+
if (!this.isStyleForCookieParameter(style)) {
490+
throwUnsupportedStyle(style)
491+
}
492+
493+
return {
494+
...base,
495+
in: "cookie",
496+
style,
497+
explode,
498+
} satisfies IRParameterCookie
499+
}
500+
501+
default: {
502+
throw new Error(
503+
`unsupported parameter location: '${it.in satisfies never}'`,
504+
)
505+
}
506+
}
507+
}
508+
509+
private isStyleForPathParameter(
510+
style: Style,
511+
): style is IRParameterPath["style"] {
512+
return ["simple", "label", "matrix", "template"].includes(style)
513+
}
514+
515+
private isStyleForQueryParameter(
516+
style: Style,
517+
): style is IRParameterQuery["style"] {
518+
return ["form", "spaceDelimited", "pipeDelimited", "deepObject"].includes(
519+
style,
520+
)
521+
}
522+
523+
private isStyleForHeaderParameter(
524+
style: Style,
525+
): style is IRParameterHeader["style"] {
526+
return ["simple"].includes(style)
527+
}
528+
529+
private isStyleForCookieParameter(
530+
style: Style,
531+
): style is IRParameterCookie["style"] {
532+
if (style === "cookie") {
533+
// todo: openapi v3.2.0
534+
throw new Error("support for style: cookie not implemented.")
535+
}
536+
537+
return ["form"].includes(style)
538+
}
539+
540+
private explodeForParameter(parameter: Parameter, style: Style): boolean {
541+
if (typeof parameter.explode === "boolean") {
542+
return parameter.explode
543+
}
544+
545+
/**
546+
* "When style is "form" or "cookie", the default value is true. For all other styles, the default value is false."
547+
* ref: {@link https://spec.openapis.org/oas/v3.2.0.html#parameter-explode}
548+
*/
549+
if (style === "form" || style === "cookie") {
550+
return true
551+
}
552+
553+
return false
554+
}
555+
}
556+
557+
export class SchemaNormalizer {
558+
constructor(readonly config: InputConfig) {}
542559

543-
private normalizeSchemaObject(schemaObject: Schema): IRModel
544-
private normalizeSchemaObject(schemaObject: Reference): IRRef
545-
private normalizeSchemaObject(
560+
public normalizeSchemaObject(schemaObject: Schema): IRModel
561+
public normalizeSchemaObject(schemaObject: Reference): IRRef
562+
public normalizeSchemaObject(
546563
schemaObject: Schema | Reference,
547564
): IRModel | IRRef
548-
private normalizeSchemaObject(
565+
public normalizeSchemaObject(
549566
schemaObject: Schema | Reference,
550567
): IRModel | IRRef {
551568
const self = this

0 commit comments

Comments
 (0)