Skip to content

Commit df7147e

Browse files
committed
fix: bug #1433
1 parent f0fba82 commit df7147e

7 files changed

Lines changed: 207 additions & 30 deletions

File tree

.changeset/ninety-jeans-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"swagger-typescript-api": patch
3+
---
4+
5+
fixed bug #1433 (multiline descriptions bug)

src/resolved-swagger-schema.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import type { resolve } from "@apidevtools/swagger-parser";
44
import SwaggerParser from "@apidevtools/swagger-parser";
55
import consola from "consola";
66
import type { OpenAPI } from "openapi-types";
7-
import * as YAML from "yaml";
87
import type { AnyObject, Maybe, Primitive } from "yummies/types";
98
import type { CodeGenConfig } from "./configuration.js";
9+
import { parseSchemaContent } from "./util/parse-schema-content.js";
1010

1111
export interface RefDetails {
1212
ref: string;
@@ -140,15 +140,12 @@ export class ResolvedSwaggerSchema {
140140
const content = await response.text();
141141

142142
try {
143-
const parsed = JSON.parse(content);
143+
const parsed = parseSchemaContent(content);
144144
if (parsed && typeof parsed === "object") {
145145
return parsed as AnyObject;
146146
}
147147
} catch {
148-
const parsed = YAML.parse(content);
149-
if (parsed && typeof parsed === "object") {
150-
return parsed as AnyObject;
151-
}
148+
return null;
152149
}
153150
} catch (e) {
154151
consola.debug(e);
@@ -498,20 +495,11 @@ export class ResolvedSwaggerSchema {
498495

499496
try {
500497
const content = fs.readFileSync(filePath, "utf8");
501-
const parsed = JSON.parse(content);
498+
const parsed = parseSchemaContent(content);
502499
this.externalSchemaCache.set(filePath, parsed);
503500
return parsed;
504501
} catch {
505-
try {
506-
const content = fs.readFileSync(filePath, "utf8");
507-
const parsed = YAML.parse(content);
508-
if (parsed && typeof parsed === "object") {
509-
this.externalSchemaCache.set(filePath, parsed as AnyObject);
510-
return parsed as AnyObject;
511-
}
512-
} catch (e) {
513-
consola.debug(e);
514-
}
502+
consola.debug("Failed to parse external schema", filePath);
515503
}
516504

517505
return null;

src/swagger-schema-resolver.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@ import { consola } from "consola";
22
import { compact, merge, uniq } from "es-toolkit";
33
import { get } from "es-toolkit/compat";
44
import type { OpenAPI, OpenAPIV2, OpenAPIV3 } from "openapi-types";
5-
import type { AnyObject } from "yummies/types";
65
import * as swagger2openapi from "swagger2openapi";
7-
import * as YAML from "yaml";
86
import type { CodeGenConfig } from "./configuration.js";
97
import { ResolvedSwaggerSchema } from "./resolved-swagger-schema.js";
8+
import { parseSchemaContent } from "./util/parse-schema-content.js";
109
import type { FileSystem } from "./util/file-system.js";
1110
import { Request } from "./util/request.js";
1211

@@ -133,12 +132,7 @@ export class SwaggerSchemaResolver {
133132

134133
processSwaggerSchemaFile(file: string) {
135134
if (typeof file !== "string") return file;
136-
137-
try {
138-
return JSON.parse(file);
139-
} catch {
140-
return YAML.parse(file);
141-
}
135+
return parseSchemaContent(file);
142136
}
143137

144138
private normalizeRefValue(ref: string): string {

src/util/parse-schema-content.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as YAML from "yaml";
2+
import type { AnyObject } from "yummies/types";
3+
4+
function normalizeYamlEscapedLineBreaks(content: string): string {
5+
let normalized = "";
6+
let inDoubleQuotedScalar = false;
7+
let escaped = false;
8+
9+
for (let index = 0; index < content.length; index += 1) {
10+
const currentChar = content[index];
11+
12+
if (!inDoubleQuotedScalar) {
13+
if (currentChar === '"') {
14+
inDoubleQuotedScalar = true;
15+
}
16+
normalized += currentChar;
17+
continue;
18+
}
19+
20+
if (escaped) {
21+
normalized += currentChar;
22+
escaped = false;
23+
continue;
24+
}
25+
26+
if (currentChar === "\\") {
27+
const nextChar = content[index + 1];
28+
29+
if (nextChar === "\n" || nextChar === "\r") {
30+
index += 1;
31+
32+
if (nextChar === "\r" && content[index + 1] === "\n") {
33+
index += 1;
34+
}
35+
36+
while (
37+
content[index + 1] === " " ||
38+
content[index + 1] === "\t"
39+
) {
40+
index += 1;
41+
}
42+
43+
continue;
44+
}
45+
46+
normalized += currentChar;
47+
escaped = true;
48+
continue;
49+
}
50+
51+
if (currentChar === '"') {
52+
inDoubleQuotedScalar = false;
53+
}
54+
55+
normalized += currentChar;
56+
}
57+
58+
return normalized;
59+
}
60+
61+
export function parseSchemaContent(content: string): AnyObject {
62+
try {
63+
return JSON.parse(content) as AnyObject;
64+
} catch {
65+
return YAML.parse(normalizeYamlEscapedLineBreaks(content)) as AnyObject;
66+
}
67+
}

tests/parse-schema-content.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { describe, expect, test } from "vitest";
2+
import { parseSchemaContent } from "../src/util/parse-schema-content.js";
3+
4+
describe("parseSchemaContent", () => {
5+
test("parses regular JSON schemas without changes", () => {
6+
const schema = parseSchemaContent(
7+
JSON.stringify({
8+
openapi: "3.0.3",
9+
info: {
10+
title: "Regular JSON schema",
11+
version: "1.0.0",
12+
},
13+
paths: {},
14+
}),
15+
);
16+
17+
expect(schema).toEqual({
18+
openapi: "3.0.3",
19+
info: {
20+
title: "Regular JSON schema",
21+
version: "1.0.0",
22+
},
23+
paths: {},
24+
});
25+
});
26+
27+
test("parses regular YAML schemas with common escapes", () => {
28+
const schema = parseSchemaContent(`
29+
openapi: "3.0.3"
30+
info:
31+
title: "Regular YAML schema"
32+
version: "1.0.0"
33+
description: "Path C:\\\\temp\\\\file and quote: \\"ok\\""
34+
paths: {}
35+
`);
36+
37+
expect(schema).toMatchObject({
38+
openapi: "3.0.3",
39+
info: {
40+
title: "Regular YAML schema",
41+
version: "1.0.0",
42+
description: 'Path C:\\temp\\file and quote: "ok"',
43+
},
44+
paths: {},
45+
});
46+
});
47+
48+
test("parses YAML double-quoted strings continued with escaped line breaks", () => {
49+
const schema = parseSchemaContent(`
50+
openapi: "3.0.3"
51+
info:
52+
title: "App NG"
53+
version: "1.0.0"
54+
description: "This is a multine\\
55+
description"
56+
paths: {}
57+
`);
58+
59+
expect(schema).toMatchObject({
60+
info: {
61+
description: "This is a multinedescription",
62+
},
63+
});
64+
});
65+
66+
test("parses escaped CRLF line continuations in YAML double-quoted strings", () => {
67+
const schema = parseSchemaContent(
68+
[
69+
'openapi: "3.0.3"',
70+
"info:",
71+
' title: "App NG"',
72+
' version: "1.0.0"',
73+
' description: "First line\\',
74+
" second line\"",
75+
"paths: {}",
76+
"",
77+
].join("\r\n"),
78+
);
79+
80+
expect(schema).toMatchObject({
81+
info: {
82+
description: "First linesecond line",
83+
},
84+
});
85+
});
86+
});

tests/spec/issue-1433/__snapshots__/basic.test.ts.snap

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ exports[`issue-1321 > to match snapshot 1`] = `
1313
* ---------------------------------------------------------------
1414
*/
1515
16+
export type Test = object;
17+
1618
export interface Call1Params {
1719
pathParam1: string;
1820
pathParam2: string;
@@ -286,7 +288,7 @@ export class Api<
286288
> extends HttpClient<SecurityDataType> {
287289
call1 = {
288290
/**
289-
* No description
291+
* @description This is a multinedescription
290292
*
291293
* @name Call1
292294
* @request GET:/call1
@@ -303,7 +305,7 @@ export class Api<
303305
};
304306
call2 = {
305307
/**
306-
* @description This is a multiline description
308+
* No description
307309
*
308310
* @name Call2
309311
* @request GET:/call2
@@ -315,6 +317,22 @@ export class Api<
315317
...params,
316318
}),
317319
};
320+
call3 = {
321+
/**
322+
* No description
323+
*
324+
* @name Call3
325+
* @request POST:/call3
326+
*/
327+
call3: (data: Test, params: RequestParams = {}) =>
328+
this.request<void, any>({
329+
path: \`/call3\`,
330+
method: "POST",
331+
body: data,
332+
type: ContentType.Json,
333+
...params,
334+
}),
335+
};
318336
}
319337
"
320338
`;

tests/spec/issue-1433/schema.yaml

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ paths:
1818
required: true
1919
schema:
2020
type: string
21+
description: "This is a multine\
22+
description"
2123
responses:
2224
204:
2325
description: "Accepted"
2426
/call2:
2527
get:
2628
operationId: "call2"
27-
description: "This is a multiline
28-
description"
2929
parameters:
3030
- in: path
3131
name: pathParam
@@ -34,4 +34,23 @@ paths:
3434
type: string
3535
responses:
3636
204:
37-
description: "Accepted"
37+
description: "Accepted"
38+
/call3:
39+
post:
40+
operationId: "call3"
41+
requestBody:
42+
description: "Die benötigten Daten für die Benutzernamen Änderung: Verschlü\
43+
sselte Daten des ersten Aufrufs und der neue Benutzername"
44+
required: true
45+
content:
46+
application/json;charset=UTF-8:
47+
schema:
48+
$ref: "#/components/schemas/Test"
49+
responses:
50+
204:
51+
description: "Accepted"
52+
53+
components:
54+
schemas:
55+
Test:
56+
type: "object"

0 commit comments

Comments
 (0)