Skip to content

Commit c482edd

Browse files
committed
Allow no parser or resolver to be matched
1 parent 1c0f45a commit c482edd

File tree

6 files changed

+114
-25
lines changed

6 files changed

+114
-25
lines changed

lib/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ declare class $RefParser {
3030
*
3131
* See https://github.com/APIDevTools/json-schema-ref-parser/blob/master/docs/ref-parser.md#errors
3232
*/
33-
errors: Array<$RefParser.GenericError | $RefParser.ResolverError | $RefParser.ParserError | $RefParser.MissingPointerError>;
33+
errors: Array<$RefParser.GenericError | $RefParser.ResolverError | $RefParser.ParserError | $RefParser.MissingPointerError | $RefParser.UnmatchedParserError | $RefParser.UnmatchedResolverError>;
3434

3535
/**
3636
* Dereferences all `$ref` pointers in the JSON Schema, replacing each reference with its resolved value. This results in a schema object that does not contain any `$ref` pointers. Instead, it's a normal JavaScript object tree that can easily be crawled and used just like any other JavaScript object. This is great for programmatic usage, especially when using tools that don't understand JSON references.
@@ -418,8 +418,10 @@ declare namespace $RefParser {
418418
}
419419

420420
export class ParserError extends GenericError {}
421+
export class UnmatchedParserError extends GenericError {}
421422
export class ResolverError extends GenericError {
422423
readonly code?: string;
423424
}
425+
export class UnmatchedResolverError extends GenericError {}
424426
export class MissingPointerError extends GenericError {}
425427
}

lib/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const resolveExternal = require("./resolve-external");
88
const bundle = require("./bundle");
99
const dereference = require("./dereference");
1010
const url = require("./util/url");
11-
const { GenericError, MissingPointerError, ResolverError, ParserError, isHandledError } = require("./util/errors");
11+
const { GenericError, MissingPointerError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError } = require("./util/errors");
1212
const maybe = require("call-me-maybe");
1313
const { ono } = require("ono");
1414

@@ -18,6 +18,8 @@ module.exports.GenericError = GenericError;
1818
module.exports.MissingPointerError = MissingPointerError;
1919
module.exports.ResolverError = ResolverError;
2020
module.exports.ParserError = ParserError;
21+
module.exports.UnmatchedParserError = UnmatchedParserError;
22+
module.exports.UnmatchedResolverError = UnmatchedResolverError;
2123

2224
/**
2325
* This class parses a JSON schema, builds a map of its JSON references and their resolved values,
@@ -45,7 +47,7 @@ function $RefParser () {
4547

4648
/**
4749
* List of all errors
48-
* @type {Array<GenericError | ResolverError | ParserError | MissingPointerError>}
50+
* @type {Array<GenericError | ResolverError | ParserError | MissingPointerError | UnmatchedResolverError | UnmatchedResolverError>}
4951
*/
5052
Object.defineProperty($RefParser.prototype, "errors", {
5153
get () {

lib/parse.js

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const { ono } = require("ono");
44
const url = require("./util/url");
55
const plugins = require("./util/plugins");
6-
const { StoplightParserError, ResolverError, ParserError } = require("./util/errors");
6+
const { StoplightParserError, ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError } = require("./util/errors");
77

88
module.exports = parse;
99

@@ -42,14 +42,12 @@ async function parse (path, $refs, options) {
4242

4343
return parser.result;
4444
}
45-
catch (ex) {
46-
if (!("error" in ex)) {
47-
throw ex;
48-
}
49-
else {
50-
$ref.value = ex.error;
51-
throw ex.error;
45+
catch (err) {
46+
if (isHandledError(err)) {
47+
$ref.value = err;
5248
}
49+
50+
throw err;
5351
}
5452
}
5553

@@ -78,17 +76,20 @@ function readFile (file, options, $refs) {
7876
.then(resolve, onError);
7977

8078
function onError (err) {
81-
// Throw the original error, if it's one of our own (user-friendly) errors.
82-
// Otherwise, throw a generic, friendly error.
83-
if (!err || !(err instanceof SyntaxError)) {
79+
if (!err && !options.failFast) {
80+
// No resolver could be matched
81+
reject(new UnmatchedResolverError(file.url));
82+
}
83+
else if (!err || !("error" in err)) {
84+
// Throw a generic, friendly error.
8485
reject(ono.syntax(`Unable to resolve $ref pointer "${file.url}"`));
8586
}
87+
// Throw the original error, if it's one of our own (user-friendly) errors.
8688
else if (err.error instanceof ResolverError) {
87-
reject(err);
89+
reject(err.error);
8890
}
8991
else {
90-
err.error = new ResolverError(err, file.url);
91-
reject(err);
92+
reject(new ResolverError(err, file.url));
9293
}
9394
}
9495
}));
@@ -132,15 +133,18 @@ function parseFile (file, options, $refs) {
132133
}
133134

134135
function onError (err) {
135-
if (!err || !("error" in err)) {
136+
if (!err && !options.failFast) {
137+
// No resolver could be matched
138+
reject(new UnmatchedParserError(file.url));
139+
}
140+
else if (!err || !("error" in err)) {
136141
reject(ono.syntax(`Unable to parse ${file.url}`));
137142
}
138143
else if (err.error instanceof ParserError || err.error instanceof StoplightParserError) {
139-
reject(err);
144+
reject(err.error);
140145
}
141146
else {
142-
err.error = new ParserError(err.error.message, file.url);
143-
reject(err);
147+
reject(new ParserError(err.error.message, file.url));
144148
}
145149
}
146150
}));

lib/util/errors.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,17 @@ const ParserError = exports.ParserError = class ParserError extends GenericError
7676

7777
setErrorName(ParserError);
7878

79+
const UnmatchedParserError = exports.UnmatchedParserError = class UnmatchedParserError extends GenericError {
80+
constructor (source) {
81+
super(`Could not find parser for "${source}"`, source);
82+
}
83+
};
84+
85+
setErrorName(UnmatchedParserError);
86+
7987
const ResolverError = exports.ResolverError = class ResolverError extends GenericError {
8088
constructor (ex, source) {
81-
super(ex.message || `Error reading file ${source}`, source);
89+
super(ex.message || `Error reading file "${source}"`, source);
8290
if ("code" in ex) {
8391
this.code = String(ex.code);
8492
}
@@ -87,6 +95,14 @@ const ResolverError = exports.ResolverError = class ResolverError extends Generi
8795

8896
setErrorName(ResolverError);
8997

98+
const UnmatchedResolverError = exports.UnmatchedResolverError = class UnmatchedResolverError extends GenericError {
99+
constructor (source) {
100+
super(`Could not find resolver for "${source}"`, source);
101+
}
102+
};
103+
104+
setErrorName(UnmatchedResolverError);
105+
90106
const MissingPointerError = exports.MissingPointerError = class MissingPointerError extends GenericError {
91107
constructor (token, path) {
92108
super(`Token "${token}" does not exist.`, stripHash(path));

test/specs/parsers/parsers.spec.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const helper = require("../../utils/helper");
66
const path = require("../../utils/path");
77
const parsedSchema = require("./parsed");
88
const dereferencedSchema = require("./dereferenced");
9-
const { StoplightParserError, ParserError } = require("../../../lib/util/errors");
9+
const { StoplightParserError, ParserError, UnmatchedParserError } = require("../../../lib/util/errors");
1010

1111
describe("References to non-JSON files", () => {
1212
it("should parse successfully", async () => {
@@ -65,6 +65,24 @@ describe("References to non-JSON files", () => {
6565
expect(schema).to.deep.equal(dereferencedSchema.binaryParser);
6666
});
6767

68+
it("should throw an error if no no parser can be matched", async () => {
69+
try {
70+
await $RefParser.dereference(path.rel("specs/parsers/parsers.yaml"), {
71+
parse: {
72+
yaml: false,
73+
json: false,
74+
text: false,
75+
binary: false,
76+
},
77+
});
78+
}
79+
catch (err) {
80+
expect(err).to.be.an.instanceOf(SyntaxError);
81+
expect(err.message).to.contain("Unable to parse ");
82+
expect(err.message).to.contain("parsers/parsers.yaml");
83+
}
84+
});
85+
6886
it('should throw an error if "parse.text" and "parse.binary" are disabled', async () => {
6987
try {
7088
await $RefParser.dereference(path.rel("specs/parsers/parsers.yaml"), { parse: { text: false, binary: false }});
@@ -184,4 +202,27 @@ describe("References to non-JSON files", () => {
184202
expect(err.message).to.contain("arsers/parsers.yaml: Woops");
185203
}
186204
});
205+
206+
it("should let no parser to be matched if fastFail is false", async () => {
207+
const parser = new $RefParser();
208+
await parser.dereference(path.rel("specs/parsers/parsers.yaml"), {
209+
parse: {
210+
yaml: false,
211+
json: false,
212+
text: false,
213+
binary: false,
214+
},
215+
failFast: false,
216+
});
217+
218+
expect(parser.errors.length).to.equal(1);
219+
expect(parser.errors).to.containSubset([
220+
{
221+
name: UnmatchedParserError.name,
222+
message: expectedValue => expectedValue.startsWith("Could not find parser for"),
223+
path: [],
224+
source: expectedValue => expectedValue.endsWith("specs/parsers/parsers.yaml"),
225+
},
226+
]);
227+
});
187228
});

test/specs/resolvers/resolvers.spec.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"use strict";
22

3-
const { expect } = require("chai");
3+
const chai = require("chai");
4+
const chaiSubset = require("chai-subset");
5+
chai.use(chaiSubset);
6+
const { expect } = chai;
47
const $RefParser = require("../../..");
58
const helper = require("../../utils/helper");
69
const path = require("../../utils/path");
710
const parsedSchema = require("./parsed");
811
const dereferencedSchema = require("./dereferenced");
9-
const { ResolverError } = require("../../../lib/util/errors");
12+
const { ResolverError, UnmatchedResolverError } = require("../../../lib/util/errors");
1013

1114
describe("options.resolve", () => {
1215
it('should not resolve external links if "resolve.external" is disabled', async () => {
@@ -133,4 +136,25 @@ describe("options.resolve", () => {
133136
expect(err.message).to.contain("Error opening file");
134137
}
135138
});
139+
140+
it("should let no resolver to be matched if fastFail is false", async () => {
141+
const parser = new $RefParser();
142+
await parser.dereference(path.abs("specs/resolvers/resolvers.yaml"), {
143+
resolve: {
144+
file: false,
145+
http: false,
146+
},
147+
failFast: false,
148+
});
149+
150+
expect(parser.errors.length).to.equal(1);
151+
expect(parser.errors).to.containSubset([
152+
{
153+
name: UnmatchedResolverError.name,
154+
message: expectedValue => expectedValue.startsWith("Could not find resolver for"),
155+
path: [],
156+
source: expectedValue => expectedValue.endsWith("specs/resolvers/resolvers.yaml"),
157+
},
158+
]);
159+
});
136160
});

0 commit comments

Comments
 (0)