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
1,642 changes: 1,642 additions & 0 deletions client/msw-handlers.ts

Large diffs are not rendered by default.

214 changes: 125 additions & 89 deletions lib/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
} from "../util";
import { initIO } from "../io";
import { refToSchemaName, Schema } from "../schema/base";
import { contentRef, docComment, getSortedSchemas } from "./base";
import {
contentRef,
docComment,
getSortedSchemas,
iterParams,
iterPathConfig,
} from "./base";
import { schemaToTypes } from "../schema/types";

const io = initIO("Api.ts");
Expand Down Expand Up @@ -90,40 +96,77 @@ export function generateApi(spec: OpenAPIV3.Document) {
w(";\n");
}

for (const path in spec.paths) {
const handlers = spec.paths[path]!;
let method: keyof typeof HttpMethods;
for (method in HttpMethods) {
const conf = handlers[HttpMethods[method]];
if (!conf?.operationId) continue;

const opName = snakeToPascal(conf.operationId);
const params = conf.parameters;
w(`export interface ${opName}Params {`);
for (const param of params || []) {
if ("name" in param) {
if (param.schema) {
const isQuery = param.in === "query";
if ("description" in param.schema || "title" in param.schema) {
docComment(
[param.schema.title, param.schema.description]
.filter((n) => n)
.join("\n\n"),
schemaNames,
io
);
}

w0(` ${processParamName(param.name)}`);
w0(`${isQuery ? "?" : ""}: `);
schemaToTypes(param.schema, io);
w(",");
// To generate separate path and query params
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit will generate separate query and path params. All we have to do is turn it on and replace the other references.

// for (const { params, opId } of iterParams(spec.paths)) {
// const opName = snakeToPascal(opId);
// if (params[0].length > 0) {
// w(`export interface ${opName}PathParams {`);
// for (const param of params[0]) {
// if ("description" in param.schema || "title" in param.schema) {
// docComment(
// [param.schema.title, param.schema.description]
// .filter((n) => n)
// .join("\n\n"),
// schemaNames,
// io
// );
// }
// w0(` ${processParamName(param.name)}:`);
// schemaToTypes(param.schema, io);
// w(",");
// }
// w("}\n");
// }

// if (params[1].length > 0) {
// w(`export interface ${opName}QueryParams {`);
// for (const param of params[1]) {
// if ("description" in param.schema || "title" in param.schema) {
// docComment(
// [param.schema.title, param.schema.description]
// .filter((n) => n)
// .join("\n\n"),
// schemaNames,
// io
// );
// }

// w0(` ${processParamName(param.name)}?:`);
// schemaToTypes(param.schema, io);
// w(",");
// }
// w("}\n");
// }
// }

for (const { conf, opId } of iterPathConfig(spec.paths)) {
const opName = snakeToPascal(opId);
const params = conf.parameters;

w(`export interface ${opName}Params {`);
for (const param of params || []) {
if ("name" in param) {
if (param.schema) {
const isQuery = param.in === "query";
if ("description" in param.schema || "title" in param.schema) {
docComment(
[param.schema.title, param.schema.description]
.filter((n) => n)
.join("\n\n"),
schemaNames,
io
);
}

w0(` ${processParamName(param.name)}`);
w0(`${isQuery ? "?" : ""}: `);
schemaToTypes(param.schema, io);
w(",");
}
}
w(`}`);
w("");
}
w(`}`);
w("");
}

const operations = Object.values(spec.paths)
Expand Down Expand Up @@ -176,72 +219,65 @@ export function generateApi(spec: OpenAPIV3.Document) {
w(`export class Api extends HttpClient {
methods = {`);

for (const path in spec.paths) {
const handlers = spec.paths[path]!;
let method: keyof typeof HttpMethods;
for (method in HttpMethods) {
const conf = handlers[HttpMethods[method]];
if (!conf?.operationId) continue;
const methodName = snakeToCamel(conf.operationId);

const paramsType = snakeToPascal(conf.operationId) + "Params";
const params = conf.parameters || [];
const pathParams = params.filter(
(p) => "in" in p && p.in === "path"
) as OpenAPIV3.ParameterObject[];
const queryParams = params.filter(
(p) => "in" in p && p.in === "query"
) as OpenAPIV3.ParameterObject[];

const bodyTypeRef = contentRef(conf.requestBody);
const bodyType = bodyTypeRef ? refToSchemaName(bodyTypeRef) : null;

const successResponse =
conf.responses["200"] ||
conf.responses["201"] ||
conf.responses["202"] ||
conf.responses["204"];

const successTypeRef = contentRef(successResponse);
const successType = successTypeRef
? refToSchemaName(successTypeRef)
: "void";

docComment(conf.summary || conf.description, schemaNames, io);

w(`${methodName}: (`);
if (pathParams.length > 0) {
w0("{ ");
const params = pathParams
.map((p) => processParamName(p.name))
.join(", ");
w0(params);
if (queryParams.length > 0) {
w0(", ...query");
}
w(`}: ${paramsType},`);
} else {
w(`query: ${paramsType},`);
}
if (bodyType) {
w(`body: ${bodyType},`);
for (const { conf, opId, method, path } of iterPathConfig(spec.paths)) {
const methodName = snakeToCamel(opId);

const paramsType = snakeToPascal(opId) + "Params";
const params = conf.parameters || [];
const pathParams = params.filter(
(p) => "in" in p && p.in === "path"
) as OpenAPIV3.ParameterObject[];
const queryParams = params.filter(
(p) => "in" in p && p.in === "query"
) as OpenAPIV3.ParameterObject[];

const bodyTypeRef = contentRef(conf.requestBody);
const bodyType = bodyTypeRef ? refToSchemaName(bodyTypeRef) : null;

const successResponse =
conf.responses["200"] ||
conf.responses["201"] ||
conf.responses["202"] ||
conf.responses["204"];

const successTypeRef = contentRef(successResponse);
const successType = successTypeRef
? refToSchemaName(successTypeRef)
: "void";

docComment(conf.summary || conf.description, schemaNames, io);

w(`${methodName}: (`);
if (pathParams.length > 0) {
w0("{ ");
const params = pathParams.map((p) => processParamName(p.name)).join(", ");
w0(params);
if (queryParams.length > 0) {
w0(", ...query");
}
w(` params: RequestParams = {},
w(`}: ${paramsType},`);
} else {
w(`query: ${paramsType},`);
}
if (bodyType) {
w(`body: ${bodyType},`);
}
w(` params: RequestParams = {},
) =>
this.request<${successType}>({
path: ${pathToTemplateStr(path)},
method: "${method.toUpperCase()}",`);
if (bodyType) {
w(` body,`);
}
if (queryParams.length > 0) {
w(" query,");
}
w(` ...params,
if (bodyType) {
w(` body,`);
}
if (queryParams.length > 0) {
w(" query,");
}
w(` ...params,
}),
`);
}
}

w(` }
}`);
out.end();
Expand Down
49 changes: 48 additions & 1 deletion lib/client/base.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { OpenAPIV3 } from "openapi-types";
import { topologicalSort } from "../util";
import { snakeToPascal, topologicalSort } from "../util";
import { IO } from "../io";
import { Schema } from "../schema/base";
import { OpenAPIV3 as O } from "openapi-types";
const HttpMethods = O.HttpMethods;

/**
* Returns a list of schema names sorted by dependency order.
Expand Down Expand Up @@ -54,3 +56,48 @@ export function docComment(
w(" */");
}
}

type PathConfig = ReturnType<typeof iterPathConfig>[number];
export function iterPathConfig(paths: OpenAPIV3.Document["paths"]) {
return Object.entries(paths)
.flatMap(([path, handlers]) => {
return Object.values(HttpMethods).map((method) => {
const conf = handlers![method]!;

return {
path,
conf,
method,
opId: conf?.operationId!,
};
});
})
.filter(({ conf }) => conf && conf.operationId);
}

type Param = Omit<OpenAPIV3.ParameterObject, "schema"> &
Required<Pick<OpenAPIV3.ParameterObject, "schema">>;
type ParamGroup = [pathParams: Param[], queryParams: Param[]];
interface IterParamsResult extends PathConfig {
params: ParamGroup;
}

export function iterParams(paths: OpenAPIV3.Document["paths"]) {
const collectedParams: IterParamsResult[] = [];
for (const { conf, ...others } of iterPathConfig(paths)) {
const params = conf.parameters;
const group: ParamGroup = [[], []];
for (const param of params || []) {
if ("name" in param && param.schema) {
if (param.in === "path") {
group[0].push(param as Param);
}
if (param.in === "query") {
group[1].push(param as Param);
}
}
}
collectedParams.push({ conf, ...others, params: group });
}
return collectedParams;
}
Loading