Skip to content

feat: improve multline description handling #172

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion lib/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ cli.command("<input>", "path/url to OpenAPI/Swagger document as json/yaml")
"When true, will make all properties of an object required by default (rather than the current opposite), unless an explicitly `required` array is set"
)
.option("--with-deprecated", "when true, will keep deprecated endpoints in the api output")
.option("--with-description", "when true, will add z.describe(xxx)")
.option("--with-description", "when true, will add z.describe(xxx), 'multiline' will keep newlines as is")
.option(
"--group-strategy",
"groups endpoints by a given strategy, possible values are: 'none' | 'tag' | 'method' | 'tag-file' | 'method-file'"
Expand Down
5 changes: 2 additions & 3 deletions lib/src/getZodiosEndpointDefinitionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ export const getZodiosEndpointDefinitionList = (doc: OpenAPIObject, options?: Te
);
}


// this fallback is needed to autofix openapi docs that put the $ref in the wrong place
// (it should be in the mediaTypeObject.schema, not in the mediaTypeObject itself)
// https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#style-values (just above this anchor)
Expand All @@ -238,8 +237,8 @@ export const getZodiosEndpointDefinitionList = (doc: OpenAPIObject, options?: Te
: paramItem.schema;
}

if (options?.withDescription && paramSchema) {
(paramSchema as SchemaObject).description = (paramItem.description ?? "")?.replace("\n", "");
if (options?.withDescription && paramSchema && paramItem.description) {
(paramSchema as SchemaObject).description = paramItem.description;
}

// resolve ref if needed, and fallback to default (unknown) value if needed
Expand Down
6 changes: 4 additions & 2 deletions lib/src/openApiToZod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,10 @@ export const getZodChain = ({ schema, meta, options }: ZodChainArgs) => {
.with("array", () => chains.push(getZodChainableArrayValidations(schema)))
.otherwise(() => void 0);

if (typeof schema.description === "string" && schema.description !== "" && options?.withDescription) {
chains.push(`describe("${schema.description}")`);
const desc = schema.description;
if (options?.withDescription && typeof desc === "string" && desc !== "") {
const result = options?.withDescription === "multiline" ? desc : desc?.replaceAll(/\n\s*/g, " ");
chains.push(`describe(\`${result}\`)`);
}

const output = chains
Expand Down
8 changes: 6 additions & 2 deletions lib/src/template-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,12 @@ export type TemplateContextOptions = {
defaultStatusBehavior?: "spec-compliant" | "auto-correct";
willSuppressWarnings?: boolean;
/**
* when true, will add z.describe(xxx)
* adds z.describe(xxx).
*
* - when true, will truncate newlines and trailing spaces into a single space.
* - when "multiline", will not truncate newlines.
*
* @see https://github.com/astahmer/openapi-zod-client/pull/143
*/
withDescription?: boolean;
withDescription?: boolean | "multiline";
};
124 changes: 89 additions & 35 deletions lib/tests/description-in-zod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,49 @@ import type { OpenAPIObject } from "openapi3-ts";
import { expect, test } from "vitest";
import { generateZodClientFromOpenAPI } from "../src";

test("description-in-zod", async () => {
const openApiDoc: OpenAPIObject = {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "Numerical enums",
},
paths: {
"/sample": {
get: {
parameters: [
{
in: "query",
name: "foo",
schema: {
type: "integer",
enum: [1, -2, 3],
},
description: "foo description",
},
{
in: "query",
name: "bar",
schema: {
type: "number",
enum: [1.2, 34, -56.789],
},
description: "bar description",
const openApiDoc: OpenAPIObject = {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "Numerical enums",
},
paths: {
"/sample": {
get: {
parameters: [
{
in: "query",
name: "foo",
schema: {
type: "integer",
enum: [1, -2, 3],
},
],
responses: {
"200": {
description: "resoponse",
description: "foo description",
},
{
in: "query",
name: "bar",
schema: {
type: "number",
enum: [1.2, 34, -56.789],
},
description: `multi
line
bar
description`,
},
],
responses: {
"200": {
description: "resoponse",
},
},
},
},
};
},
};

test("description-in-zod", async () => {
const output = await generateZodClientFromOpenAPI({
disableWriteToFile: true,
openApiDoc,
Expand All @@ -62,15 +65,15 @@ test("description-in-zod", async () => {
type: "Query",
schema: z
.union([z.literal(1), z.literal(-2), z.literal(3)])
.describe("foo description")
.describe(\`foo description\`)
.optional(),
},
{
name: "bar",
type: "Query",
schema: z
.union([z.literal(1.2), z.literal(34), z.literal(-56.789)])
.describe("bar description")
.describe(\`multi line bar description\`)
.optional(),
},
],
Expand All @@ -86,3 +89,54 @@ test("description-in-zod", async () => {
"
`);
});

test("description-in-zod-multiline", async () => {
const output = await generateZodClientFromOpenAPI({
disableWriteToFile: true,
openApiDoc,
options: { withDescription: "multiline" },
});
expect(output).toMatchInlineSnapshot(`
"import { makeApi, Zodios, type ZodiosOptions } from "@zodios/core";
import { z } from "zod";

const endpoints = makeApi([
{
method: "get",
path: "/sample",
requestFormat: "json",
parameters: [
{
name: "foo",
type: "Query",
schema: z
.union([z.literal(1), z.literal(-2), z.literal(3)])
.describe(\`foo description\`)
.optional(),
},
{
name: "bar",
type: "Query",
schema: z
.union([z.literal(1.2), z.literal(34), z.literal(-56.789)])
.describe(
\`multi
line
bar
description\`
)
.optional(),
},
],
response: z.void(),
},
]);

export const api = new Zodios(endpoints);

export function createApiClient(baseUrl: string, options?: ZodiosOptions) {
return new Zodios(baseUrl, endpoints, options);
}
"
`)
})