Skip to content

Commit bde0ea4

Browse files
authored
[spec-model] Add toJSONAsync() option to embed errors (#37831)
- Useful when building a large spec model, to process the non-error elements - Useful for regression-testing changes to spec-model - Compare JSON before and after, should match including errors - Default remains to throw on any error
1 parent 43a962b commit bde0ea4

File tree

6 files changed

+121
-49
lines changed

6 files changed

+121
-49
lines changed

.github/shared/cmd/spec-model.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
import { ConsoleLogger } from "../src/logger.js";
3+
import { debugLogger } from "../src/logger.js";
44
import { SpecModel } from "../src/spec-model.js";
55

66
const USAGE =
@@ -32,8 +32,16 @@ if (args.length > 1) {
3232

3333
const specPath = args[0];
3434

35-
const specModel = new SpecModel(specPath, {
36-
logger: new ConsoleLogger(debug),
37-
});
35+
// Default to 'undefined' instead of 'defaultLogger', so output is always a valid JSON object (no logging)
36+
const logger = debug ? debugLogger : undefined;
3837

39-
console.log(JSON.stringify(await specModel.toJSONAsync({ includeRefs, relativePaths }), null, 2));
38+
const specModel = new SpecModel(specPath, { logger });
39+
40+
console.log(
41+
JSON.stringify(
42+
// Always embed errors, since we always want to return a valid JSON object instead of throwing
43+
await specModel.toJSONAsync({ embedErrors: true, includeRefs, relativePaths }),
44+
null,
45+
2,
46+
),
47+
);

.github/shared/src/readme.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import yaml from "js-yaml";
55
import { marked } from "marked";
66
import { dirname, normalize, relative, resolve } from "path";
77
import { mapAsync } from "./array.js";
8+
import { embedError } from "./spec-model.js";
89
import { Tag } from "./tag.js";
910

1011
/**
@@ -207,19 +208,21 @@ export class Readme {
207208
* @returns {Promise<Object>}
208209
*/
209210
async toJSONAsync(options) {
210-
const tags = await mapAsync(
211-
[...(await this.getTags()).values()].sort((a, b) => a.name.localeCompare(b.name)),
212-
async (t) => await t.toJSONAsync(options),
213-
);
214-
215-
return {
216-
path:
217-
options?.relativePaths && this.#specModel
218-
? relative(this.#specModel.folder, this.#path)
219-
: this.#path,
220-
globalConfig: await this.getGlobalConfig(),
221-
tags,
222-
};
211+
return await embedError(async () => {
212+
const tags = await mapAsync(
213+
[...(await this.getTags()).values()].sort((a, b) => a.name.localeCompare(b.name)),
214+
async (t) => await t.toJSONAsync(options),
215+
);
216+
217+
return {
218+
path:
219+
options?.relativePaths && this.#specModel
220+
? relative(this.#specModel.folder, this.#path)
221+
: this.#path,
222+
globalConfig: await this.getGlobalConfig(),
223+
tags,
224+
};
225+
}, options);
223226
}
224227

225228
/**

.github/shared/src/spec-model.js

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const specModelCache = new Map();
1212

1313
/**
1414
* @typedef {Object} ToJSONOptions
15+
* @prop {boolean} [embedErrors]
1516
* @prop {boolean} [includeRefs]
1617
* @prop {boolean} [relativePaths]
1718
*
@@ -216,15 +217,16 @@ export class SpecModel {
216217
* @returns {Promise<Object>}
217218
*/
218219
async toJSONAsync(options) {
219-
const readmes = await mapAsync(
220-
[...(await this.getReadmes()).values()].sort((a, b) => a.path.localeCompare(b.path)),
221-
async (r) => await r.toJSONAsync(options),
222-
);
223-
224-
return {
225-
folder: this.#folder,
226-
readmes,
227-
};
220+
return await embedError(async () => {
221+
const readmes = await mapAsync(
222+
[...(await this.getReadmes()).values()].sort((a, b) => a.path.localeCompare(b.path)),
223+
async (r) => await r.toJSONAsync(options),
224+
);
225+
return {
226+
folder: this.#folder,
227+
readmes,
228+
};
229+
}, options);
228230
}
229231

230232
/**
@@ -234,3 +236,21 @@ export class SpecModel {
234236
return `SpecModel(${this.#folder}, {logger: ${this.#logger}}})`;
235237
}
236238
}
239+
240+
/**
241+
* @template T
242+
* @param {() => Promise<T>} fn
243+
* @param {{embedErrors?: boolean}} [options]
244+
* @returns {Promise<T | {error: string}>}
245+
*/
246+
export async function embedError(fn, options) {
247+
try {
248+
return await fn();
249+
} catch (error) {
250+
if (options?.embedErrors && error instanceof Error) {
251+
return { error: error.message };
252+
} else {
253+
throw error;
254+
}
255+
}
256+
}

.github/shared/src/swagger.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { mapAsync } from "./array.js";
77
import { example } from "./changed-files.js";
88
import { includesFolder } from "./path.js";
99
import { SpecModelError } from "./spec-model-error.js";
10+
import { embedError } from "./spec-model.js";
1011

1112
/**
1213
* @typedef {import('./spec-model.js').Tag} Tag
@@ -202,20 +203,23 @@ export class Swagger {
202203
* @returns {Promise<Object>}
203204
*/
204205
async toJSONAsync(options) {
205-
return {
206-
path:
207-
options?.relativePaths && this.#tag?.readme?.specModel
208-
? relative(this.#tag?.readme?.specModel.folder, this.#path)
209-
: this.#path,
210-
refs: options?.includeRefs
211-
? await mapAsync(
212-
[...(await this.getRefs()).values()].sort((a, b) => a.path.localeCompare(b.path)),
213-
async (s) =>
214-
// Do not include swagger refs transitively, otherwise we could get in infinite loop
215-
await s.toJSONAsync({ ...options, includeRefs: false }),
216-
)
217-
: undefined,
218-
};
206+
return await embedError(
207+
async () => ({
208+
path:
209+
options?.relativePaths && this.#tag?.readme?.specModel
210+
? relative(this.#tag?.readme?.specModel.folder, this.#path)
211+
: this.#path,
212+
refs: options?.includeRefs
213+
? await mapAsync(
214+
[...(await this.getRefs()).values()].sort((a, b) => a.path.localeCompare(b.path)),
215+
async (s) =>
216+
// Do not include swagger refs transitively, otherwise we could get in infinite loop
217+
await s.toJSONAsync({ ...options, includeRefs: false }),
218+
)
219+
: undefined,
220+
}),
221+
options,
222+
);
219223
}
220224

221225
toString() {

.github/shared/src/tag.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @ts-check
22

33
import { mapAsync } from "./array.js";
4+
import { embedError } from "./spec-model.js";
45
import { Swagger } from "./swagger.js";
56

67
/**
@@ -70,13 +71,16 @@ export class Tag {
7071
* @returns {Promise<Object>}
7172
*/
7273
async toJSONAsync(options) {
73-
return {
74-
name: this.#name,
75-
inputFiles: await mapAsync(
76-
[...this.#inputFiles.values()].sort((a, b) => a.path.localeCompare(b.path)),
77-
async (s) => await s.toJSONAsync(options),
78-
),
79-
};
74+
return await embedError(
75+
async () => ({
76+
name: this.#name,
77+
inputFiles: await mapAsync(
78+
[...this.#inputFiles.values()].sort((a, b) => a.path.localeCompare(b.path)),
79+
async (s) => await s.toJSONAsync(options),
80+
),
81+
}),
82+
options,
83+
);
8084
}
8185

8286
toString() {

.github/shared/test/spec-model.test.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ describe("SpecModel", () => {
139139
await expect(
140140
mapAsync([...readmes.values()], async (r) => await r.getTags()),
141141
).rejects.toThrowError(/multiple.*tag/i);
142+
143+
await expect(specModel.toJSONAsync()).rejects.toThrowError(/multiple.*tag/i);
144+
145+
await expect(specModel.toJSONAsync({ embedErrors: true })).resolves.toMatchObject({
146+
readmes: [
147+
{
148+
error: expect.stringMatching(/multiple.*tag/i),
149+
},
150+
],
151+
});
142152
});
143153

144154
describe("getAffectedReadmeTags", () => {
@@ -208,9 +218,32 @@ describe("SpecModel", () => {
208218
);
209219
const specModel = new SpecModel(folder, options);
210220

211-
expect(
221+
await expect(
212222
specModel.getAffectedReadmeTags(resolve(folder, "data-plane/a.json")),
213223
).rejects.toThrowError(/is not a valid JSON Schema/i);
224+
225+
await expect(specModel.toJSONAsync({ includeRefs: true })).rejects.toThrowError(
226+
/is not a valid JSON Schema/i,
227+
);
228+
229+
await expect(
230+
specModel.toJSONAsync({ embedErrors: true, includeRefs: true }),
231+
).resolves.toMatchObject({
232+
readmes: [
233+
{
234+
tags: [
235+
{
236+
inputFiles: [
237+
{
238+
error: expect.stringMatching(/is not a valid JSON Schema/i),
239+
},
240+
],
241+
name: "package-2021-11-01",
242+
},
243+
],
244+
},
245+
],
246+
});
214247
});
215248
});
216249

0 commit comments

Comments
 (0)