Skip to content

Commit

Permalink
Add default responses to schema
Browse files Browse the repository at this point in the history
  • Loading branch information
GDownes committed Oct 17, 2023
1 parent e83be2d commit 7286eca
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 17 deletions.
4 changes: 2 additions & 2 deletions src/mock-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export interface Mock {
}

export const responseFrom = (oas: OAS, method: OASOperation): [string, any] => {
const statusCode = Object.keys(method.responses).find(x => x)!;
const refResponse = method.responses[statusCode];
const statusCode = Object.keys(method.responses!).find(x => x)!;
const refResponse = method.responses![statusCode];
const response = Object.prototype.hasOwnProperty.call(refResponse, '$ref') ? traversePath<OASResponse>((refResponse as OASRef)['$ref'], oas) : refResponse as OASResponse;
const contentType = Object.keys(response.content ?? '')[0]
const content = response.content ? response.content[contentType] : undefined;
Expand Down
2 changes: 1 addition & 1 deletion src/oas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export interface OASOperation {
operationId?: string;
parameters?: Array<OASParameter | OASRef>;
requestBody?: OASRequestBody | OASRef;
responses: { [statusCode: string]: OASResponse | OASRef };
responses?: { [statusCode: string]: OASResponse | OASRef };
callbacks?: { [key: string]: OASPath | OASRef };
deprecated?: boolean;
security?: OASSecurity[];
Expand Down
52 changes: 48 additions & 4 deletions src/schema-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
OASEncoding,
OASInfo,
OASMedia,
OASOAuthFlows,
OASParameter,
OASOAuthFlows, OASOperation,
OASParameter, OASPath, OASRef,
OASResponse,
OASSecurityScheme
OASSecurityScheme,
} from "./oas";
import {OAS, OASComponents} from "./oas.js";

Expand Down Expand Up @@ -169,6 +169,8 @@ export class OpenApiSpecificationBuilder<S extends {components: {schemas?: any,
private constructor(public oas: OAS) {
}

private _defaultResponses: OASOperation["responses"] = {};

build(): OAS {
return this.oas;
}
Expand Down Expand Up @@ -273,6 +275,11 @@ export class OpenApiSpecificationBuilder<S extends {components: {schemas?: any,
return {'$ref': `#/components/${componentKey as string}/${key as string}`} as any;
}

defaultResponses(itemBuilder: (builder: this) => Exclude<OASComponents["responses"], undefined>): this {
this._defaultResponses = itemBuilder(this);
return this
}

addComponent<K extends keyof OASComponents, B extends (builder: this) => OASComponents[K]>(location: K, itemBuilder: B): B extends (builder: any) => infer R ? OpenApiSpecificationBuilder<S & { components: { [k in K]: R } }> : never {
const item = itemBuilder(this);
const current = this.oas.components ?? {};
Expand All @@ -294,13 +301,50 @@ export class OpenApiSpecificationBuilder<S extends {components: {schemas?: any,
return this as any;
}

private modifyOperationWithDefaultResponses(operation?: OASOperation): OASOperation | undefined {
if(operation) {
const { responses, ...rest } = operation;
return {...rest, responses: { ...this._defaultResponses, ...responses } }
}
}
private addDefaultResponsesToPath(path: OASPath | OASRef): OASPath | OASRef {
if((path as OASRef).$ref) return path;
const {
get,
put,
post,
delete: deleteOp,
options,
head,
patch,
trace,
...rest
} = path as OASPath;
return {
...rest,
...(get ? {get: this.modifyOperationWithDefaultResponses(get)} : {}),
...(put ? {put: this.modifyOperationWithDefaultResponses(put)} : {}),
...(post ? {post: this.modifyOperationWithDefaultResponses(post)} : {}),
...(deleteOp ? {delete: this.modifyOperationWithDefaultResponses(deleteOp)} : {}),
...(options ? {options: this.modifyOperationWithDefaultResponses(options)} : {}),
...(head ? {head: this.modifyOperationWithDefaultResponses(head)} : {}),
...(patch ? {patch: this.modifyOperationWithDefaultResponses(patch)} : {}),
...(trace ? {trace: this.modifyOperationWithDefaultResponses(trace)} : {}),
...rest
}
}

private addDefaultResponses(paths: OAS["paths"]): OAS["paths"] {
return Object.keys(paths).reduce((previousValue, path) => ({ ...previousValue, [path]: this.addDefaultResponsesToPath(paths[path])}), {})
}

add<K extends keyof OAS>(location: K, itemBuilder: (builder: this) => OAS[K]): this {
const item = itemBuilder(this);
if (typeof item === "object") {
if (Array.isArray(item)) {
this.oas = {...this.oas, [location]: [...(this.oas[location] as any[] ?? []), ...item]};
} else {
this.oas = {...this.oas, [location]: {...(this.oas[location] as any ?? {}), ...(item as any)}};
this.oas = {...this.oas, [location]: {...(this.oas[location] as any ?? {}), ...(location === "paths" ? this.addDefaultResponses(item as any) : (item as any))}};
}
} else {
this.oas = {...this.oas, [location]: item};
Expand Down
8 changes: 4 additions & 4 deletions src/sdk-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ export function typeFrom(oas: OAS, response: OASResponse | OASRef): [string, str
}

function returnType(oas: OAS, method: OASOperation, imports: string[]): string {
return Object.keys(method.responses).map(statusCode => {
const response = method.responses[statusCode];
return Object.keys(method.responses!).map(statusCode => {
const response = method.responses![statusCode];
const [type, newImports] = typeFrom(oas, response);
imports.push(...newImports);
return `{ statusCode: ${statusCode}; result: ${type} }`;
Expand All @@ -104,8 +104,8 @@ function ifCode(code: string, json: boolean): string {
}

function bodyValue(oas: OAS, method: OASOperation): string {
return Object.keys(method.responses).map(statusCode => {
const response = method.responses[statusCode];
return Object.keys(method.responses!).map(statusCode => {
const response = method.responses![statusCode];
const def = Object.prototype.hasOwnProperty.call(response, '$ref') ? traversePath<OASResponse>((response as OASRef)['$ref'], oas) : response as OASResponse;
const types = Object.keys(def.content ?? {});
const json = types[0] === 'application/json';
Expand Down
12 changes: 6 additions & 6 deletions test/schema-example.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {OpenApiSpecificationBuilder, SchemaBuilder, OASServer} from "../dist";
import {OpenApiSpecificationBuilder, SchemaBuilder, OASServer} from "../src";

const builder = SchemaBuilder.create();

Expand Down Expand Up @@ -35,6 +35,9 @@ const schemas = builder.add('Chicken', s => s.object({
export default OpenApiSpecificationBuilder
.create(schemas, { title: 'Chicken Store API', version: '1.0.0'})
.add('servers', () => servers)
.defaultResponses(o => ({
200: o.response('', o.textContent())
}))
.addComponent('securitySchemes', o => ({ Auth: o.openIdConnectScheme('.well-known/xyz')}))
.add('paths', o => ({
'/chicken': {
Expand Down Expand Up @@ -83,7 +86,7 @@ export default OpenApiSpecificationBuilder
content: o.jsonContent('ChickenCreateRequest')
},
responses: {
200: o.response('The Chicken', {...o.jsonContent('Chicken'), ...o.textContent()}, ['Location']),
200: o.response('The Chicken', {...o.jsonContent('Chicken'), ...o.textContent()}),
404: o.response('Not Found', o.textContent()),
}
},
Expand All @@ -92,10 +95,7 @@ export default OpenApiSpecificationBuilder
parameters: [
o.path('chickenId'),
o.header('X-Encryption-Key')
],
responses: {
200: {description: 'Success', content: o.textContent('Deleted')},
}
]
},
},
'/schema': {
Expand Down

0 comments on commit 7286eca

Please sign in to comment.