Skip to content

Commit d8ffda1

Browse files
committedSep 1, 2020
feat: remove ajv
1 parent 1c6b900 commit d8ffda1

19 files changed

+4451
-2807
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Change Log
22

3+
## swagger2openapi v7.0 and oas-validator v5.0
4+
5+
* remove use of `ajv` for fallback schema validation
6+
37
## swagger2openapi v6.1.0 and oas-resolver v2.4.0
48

59
New properties on the `options` object:

‎docs/options.md

-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
Parameter|Type|Input/Output|Description
44
|---|---|---|---|
55
agent|Object|Input|Optional http(s).Agent to be used when fetching resources
6-
ajv|Object|Input|Used to pass the instance of the `ajv` JSON Schema validator from the validator to the linter
76
allScopes|Object|Internal|Cache of scopes by securityScheme for validation
87
anchors|Boolean|Input|Allow use of YAML anchors/aliases. May break things
98
cache|Object|Input|Optional cache of external resources
@@ -26,7 +25,6 @@ handlers|Object|Input|Map of additional [protocol/scheme handlers](handlers.md),
2625
help|Boolean|Reserved|Command-line flag to display help
2726
indent|String|Input|Command-line flag to control JSON indenting
2827
isCallback|Boolean|Input|Hint to the linter that we are within a `callback` object
29-
jsonschema|String|Input|Path to alternative JSON schema (in JSON or YAML) for validation
3028
laxDefaults|Boolean|Input|Flag to validation step to ignore default/type mismatches
3129
laxRefs|Boolean|Input|**No longer has any effect as this is now the default**
3230
laxurls|Boolean|Input|Flag to validation step to ignore empty URLs and # ? in paths
@@ -47,7 +45,6 @@ output|Boolean|Input|Internal flag to testRunner to write output openapi.yaml fi
4745
patch|Boolean|Input|Flag to fix-up minor errors in the source definition during conversion
4846
patches|Integer|Output|Count of number of patches applied during conversion
4947
preserveMiro|Boolean|Input|Flag to resolver as to whether to preserve old value of `$ref` in `x-miro`, default: `false`
50-
prettify|Boolean|Input|Flag to validator to generate pretty (but potentially misleading) schema validation error reports
5148
prevalidate|Boolean|Input|Whether to validate each externally `$ref`d file separately
5249
promise|Object|Internal|Object containing resolve and reject functions for the converter
5350
quiet|Boolean|Input|Command-line flag used by `testRunner`
@@ -57,7 +54,6 @@ refSiblings|string|Input|Controls handling of `$ref` which has sibling propertie
5754
resolve|Boolean|Input|Flag to enable resolution of external `$ref`s
5855
resolveInternal|Boolean|Input|Flag to enable resolution of internal `$ref`s. Also disables deduplication of `requestBodies`
5956
resolver|Object|Internal|Used by the resolver to track outstanding resolutions
60-
schema|Object|Input|Temporarily holds JSON Schema during validation step
6157
skip|Boolean|Reserved|Used by tools such as Speccy to skip linter rules
6258
stop|Boolean|Input|Command-line flag used by `testRunner`
6359
source|String|Input|The source filename or url of the definition, used by the resolver
@@ -67,7 +63,6 @@ text|String|Both|If not already a truthy value, will be set to the input text of
6763
throws|Boolean|Input|Used by tests only to indicate the fixture should throw an exception
6864
url|String|Input|URL of the original definition, used when reading a file to create `x-origin` extension
6965
valid|Boolean|Output|The result of a validation step
70-
validateSchema|String|Input|Set to 'first', 'last' or 'never' to control ordering of validation strategies
7166
verbose|Boolean|Input|Increase verbosity, e.g. show HTTP GET requests
7267
version|Boolean|Input|Command-line flag to show version information
7368
warnings|Array|Output|Warnings generated by a validation step

‎packages/oas-linter/index.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const path = require('path');
55

66
const yaml = require('yaml');
77
const should = require('should/as-function');
8+
const { validator } = require('@exodus/schemasafe');
89

910
let rules = [];
1011
let results = [];
@@ -242,13 +243,15 @@ function lint(objectName,object,key,options) {
242243
}
243244
if (rule.schema) {
244245
matched = true;
245-
const validate = options.ajv.compile(rule.schema);
246-
const valid = validate(object);
246+
if (!rule.$schema) {
247+
rule.$schema = validator(rule.schema, { includeErrors: true, allErrors: true, mode: 'default' });
248+
}
249+
const valid = rule.$schema(object);
247250
if (!valid) {
248-
const pointer = (options.context && options.context.length > 0 ? options.context[options.context.length-1] : null);
249-
for (let error of validate.errors) {
250-
results.push({ pointer, rule, ruleName: rule.name, error, dataPath: pointer, keyword: 'lint', message: error.dataPath + ' ' + error.message, url: rules.url });
251-
}
251+
const pointer = (options.context && options.context.length > 0 ? options.context[options.context.length-1] : null);
252+
for (let error of rule.$schema.errors) {
253+
results.push({ pointer, rule, ruleName: rule.name, error, dataPath: pointer, keyword: 'lint', message: error.instanceLocation, url: rules.url });
254+
}
252255
}
253256
}
254257
if (!matched && options.verbose) console.warn('Linter rule did not match any known rule-types',rule.name);

‎packages/oas-linter/package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/oas-linter/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
"author": "Mike Ralphson",
1717
"license": "BSD-3-Clause",
1818
"dependencies": {
19+
"@exodus/schemasafe": "^1.0.0-rc.2",
1920
"should": "^13.2.1",
20-
"yaml": "^1.8.3"
21+
"yaml": "^1.10.0"
2122
},
2223
"repository": {
2324
"type": "git",

‎packages/oas-resolver/package-lock.json

-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/oas-resolver/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"node-fetch-h2": "^2.3.0",
2727
"oas-kit-common": "^1.0.8",
2828
"reftools": "^1.1.5",
29-
"yaml": "^1.8.3",
29+
"yaml": "^1.10.0",
3030
"yargs": "^15.3.1"
3131
},
3232
"repository": {

‎packages/oas-validator/index.js

+9-89
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,6 @@ const url = require('url');
77
const yaml = require('yaml');
88
const should = require('should/as-function');
99
const maybe = require('call-me-maybe');
10-
let ajv = require('ajv')({
11-
$data: true,
12-
allErrors: true,
13-
verbose: true,
14-
jsonPointers: true,
15-
patternGroups: true,
16-
extendRefs: true // optional, current default is to 'fail', spec behaviour is to 'ignore'
17-
});
18-
//meta: false, // optional, to prevent adding draft-06 meta-schema
19-
20-
let ajvFormats = require('ajv/lib/compile/formats.js');
21-
ajv.addFormat('uriref', ajvFormats.full['uri-reference']);
22-
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
23-
ajv._refs['http://json-schema.org/schema'] = 'http://json-schema.org/draft-04/schema'; // optional, using unversioned URI is out of spec
24-
let metaSchema = require('ajv/lib/refs/json-schema-v5.json');
25-
ajv.addMetaSchema(metaSchema);
26-
ajv._opts.defaultMeta = metaSchema.id;
27-
28-
const bae = require('better-ajv-errors');
29-
30-
class JSONSchemaError extends Error {
31-
constructor(message) {
32-
super(message);
33-
this.name = 'JSONSchemaError';
34-
}
35-
}
36-
37-
class CLIError extends Error {
38-
constructor(message) {
39-
super(message);
40-
this.name = 'CLIError';
41-
}
42-
}
4310

4411
const common = require('oas-kit-common');
4512
const jptr = require('reftools/lib/jptr.js');
@@ -51,11 +18,6 @@ const sw = require('oas-schema-walker');
5118
const linter = require('oas-linter');
5219
const resolver = require('oas-resolver');
5320

54-
const jsonSchema = require('./schemas/json_v5.json');
55-
const validateMetaSchema = ajv.compile(jsonSchema);
56-
let openapi3Schema = require('./schemas/openapi-3.0.json');
57-
let validateOpenAPI3 = ajv.compile(openapi3Schema);
58-
5921
const dummySchema = { anyOf: {} };
6022
const emptySchema = {};
6123
let refSeen = {};
@@ -95,20 +57,6 @@ function validateHeaderName(name) {
9557
return /^[A-Za-z0-9!#\-\$%&'\*\+\\\.\^_`\|~]+$/.test(name);
9658
}
9759

98-
function validateSchema(schema, openapi, options) {
99-
validateMetaSchema(schema);
100-
let errors = validateSchema.errors;
101-
if (errors && errors.length) {
102-
if (options.prettify) {
103-
const errorStr = bae(schema, openapi, errors);
104-
throw (new CLIError(errorStr));
105-
}
106-
throw (new JSONSchemaError('Schema invalid:\n'+ yaml.stringify(errors)));
107-
}
108-
options.schema = schema;
109-
return !(errors && errors.length);
110-
}
111-
11260
function checkSubSchema(schema, parent, state) {
11361
let prop = state.property;
11462
if (prop) contextAppend(state.options, prop);
@@ -334,7 +282,6 @@ function checkSubSchema(schema, parent, state) {
334282
if (state.options.lint) state.options.linter('externalDocs',schema.externalDocs,'externalDocs',state.options);
335283
}
336284
if (prop) state.options.context.pop();
337-
if (!prop || prop === 'schema') validateSchema(schema, state.openapi, state.options); // top level only
338285
}
339286

340287
function checkSchema(schema,parent,prop,openapi,options) {
@@ -497,7 +444,7 @@ function checkLink(link, openapi, options) {
497444
if (typeof link.$ref !== 'undefined') {
498445
let ref = link.$ref;
499446
should(ref).be.type('string');
500-
if (refSeen[ref]) return true;
447+
if (refSeen[ref]) return true; // bail out
501448
refSeen[ref] = true;
502449
if (options.lint) options.linter('reference',link,'$ref',options);
503450
link = resolveInternal(openapi, ref);
@@ -536,7 +483,7 @@ function checkHeader(header, contextServers, openapi, options) {
536483
if (typeof header.$ref !== 'undefined') {
537484
let ref = header.$ref;
538485
should(ref).be.type('string');
539-
if (refSeen[ref]) return true;
486+
if (refSeen[ref]) return true; // bail out
540487
refSeen[ref] = true;
541488
if (options.lint) options.linter('reference',header,'$ref',options);
542489
header = resolveInternal(openapi, ref);
@@ -582,7 +529,7 @@ function checkResponse(response, key, contextServers, openapi, options) {
582529
if (typeof response.$ref !== 'undefined') {
583530
let ref = response.$ref;
584531
should(ref).be.type('string');
585-
if (refSeen[ref]) return true;
532+
if (refSeen[ref]) return true; // bail out
586533
refSeen[ref] = true;
587534
if (options.lint) options.linter('reference',response,'$ref',options);
588535
response = resolveInternal(openapi, ref);
@@ -620,13 +567,16 @@ function checkResponse(response, key, contextServers, openapi, options) {
620567
}
621568

622569
function checkParam(param, index, path, contextServers, openapi, options) {
623-
contextAppend(options, index);
624570
const ref = param.$ref;
571+
contextAppend(options, index);
625572
if (typeof param.$ref !== 'undefined') {
626573
should(ref).be.type('string');
627-
if (refSeen[ref]) return refSeen[ref];
628574
if (options.lint) options.linter('reference',param,'$ref',options);
629575
param = resolveInternal(openapi, ref);
576+
if (refSeen[ref] && (param.in !== 'path')) {
577+
options.context.pop();
578+
return param; // bail out
579+
}
630580
should(param).not.be.exactly(false, 'Cannot resolve reference: ' + ref);
631581
}
632582
should(param).have.property('name');
@@ -954,16 +904,6 @@ function validateInner(openapi, options, callback) {
954904
setupOptions(options,openapi);
955905
let contextServers = [];
956906

957-
if (options.jsonschema) {
958-
let schemaStr = fs.readFileSync(options.jsonschema, 'utf8');
959-
openapi3Schema = yaml.parse(schemaStr, { schema:'core' });
960-
validateOpenAPI3 = ajv.compile(openapi3Schema);
961-
}
962-
963-
if (options.validateSchema === 'first') {
964-
schemaValidate(openapi, options);
965-
}
966-
967907
should(openapi).be.an.Object();
968908
should(openapi).not.have.key('swagger');
969909
should(openapi).have.key('openapi');
@@ -1383,10 +1323,6 @@ function validateInner(openapi, options, callback) {
13831323
options.context.pop();
13841324
}
13851325

1386-
if (!options.validateSchema || (options.validateSchema === 'last')) {
1387-
schemaValidate(openapi, options);
1388-
}
1389-
13901326
options.valid = !options.expectFailure;
13911327
if (options.lint) options.linter('openapi',openapi,'',options);
13921328

@@ -1418,18 +1354,6 @@ function validateInner(openapi, options, callback) {
14181354
}));
14191355
}
14201356

1421-
function schemaValidate(openapi, options) {
1422-
validateOpenAPI3(openapi);
1423-
let errors = validateOpenAPI3.errors;
1424-
if (errors && errors.length) {
1425-
if (options.prettify) {
1426-
const errorStr = bae(options.schema, openapi, errors, { indent: 2 });
1427-
throw (new CLIError(errorStr));
1428-
}
1429-
throw (new JSONSchemaError('Failed OpenAPI3 schema validation: ' + JSON.stringify(errors, null, 2)));
1430-
}
1431-
}
1432-
14331357
function setupOptions(options,openapi) {
14341358
refSeen = {};
14351359
options.valid = false;
@@ -1446,10 +1370,8 @@ function setupOptions(options,openapi) {
14461370
options.linterResults = linter.getResults;
14471371
}
14481372
if (!options.cache) options.cache = {};
1449-
options.schema = openapi3Schema;
14501373
options.metadata = { lines: -1, count: {} };
14511374
if ((options.text) && (typeof options.text === 'string')) options.metadata.lines = options.text.split('\n').length;
1452-
options.ajv = ajv;
14531375
}
14541376

14551377
function validate(openapi, options, callback) {
@@ -1494,7 +1416,5 @@ module.exports = {
14941416
validateInner: validateInner,
14951417
validate: validate,
14961418
microValidate: microValidate,
1497-
optionallyValidate: optionallyValidate,
1498-
JSONSchemaError: JSONSchemaError,
1499-
CLIError: CLIError
1419+
optionallyValidate: optionallyValidate
15001420
}

‎packages/oas-validator/package-lock.json

+20-228
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/oas-validator/package.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,14 @@
1717
"author": "Mike Ralphson",
1818
"license": "BSD-3-Clause",
1919
"dependencies": {
20-
"ajv": "^5.5.2",
21-
"better-ajv-errors": "^0.6.7",
2220
"call-me-maybe": "^1.0.1",
2321
"oas-kit-common": "^1.0.8",
2422
"oas-linter": "^3.1.3",
2523
"oas-resolver": "^2.4.3",
2624
"oas-schema-walker": "^1.1.5",
2725
"reftools": "^1.1.5",
2826
"should": "^13.2.1",
29-
"yaml": "^1.8.3"
27+
"yaml": "^1.10.0"
3028
},
3129
"repository": {
3230
"type": "git",

‎packages/oas-validator/schemas/json_v5.json

-221
This file was deleted.

‎packages/oas-validator/schemas/openapi-3.0.json

-1,654
This file was deleted.

‎packages/reftools/package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/reftools/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
"mocha": "^8.0.1",
4242
"nyc": "^15.0.0",
4343
"should": "^13.2.1",
44-
"yaml": "^1.8.3"
44+
"yaml": "^1.10.0"
4545
}
4646
}

‎packages/swagger2openapi/boast.js

+3-14
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
const fs = require('fs');
88

99
const yaml = require('yaml');
10-
const fetch = require('node-fetch-h2');
11-
const bae = require('better-ajv-errors');
10+
const fetch = require('node-fetch');
1211

1312
const swagger2openapi = require('./index.js');
1413
const validator = require('oas-validator');
@@ -21,9 +20,6 @@ let argv = require('yargs')
2120
.boolean('all')
2221
.alias('a','all')
2322
.describe('all','show all lint warnings')
24-
.boolean('bae')
25-
.alias('b','bae')
26-
.describe('bae','enable better-ajv-errors')
2723
.string('encoding')
2824
.alias('e','encoding')
2925
.default('encoding','utf8')
@@ -71,11 +67,8 @@ function main(){
7167
argv.fatal = true;
7268
argv.laxurls = true;
7369
argv.laxDefaults = true;
70+
argv.fetch = fetch;
7471
if (argv.all) argv.lintLimit = Number.MAX_SAFE_INTEGER;
75-
if (argv.bae) {
76-
argv.validateSchema = 'first';
77-
argv.prettify = true;
78-
}
7972
if (argv.internal) {
8073
argv.resolveInternal = true;
8174
}
@@ -114,11 +107,7 @@ function main(){
114107
jsonOutput.warnings = [];
115108
if (options.warnings) {
116109
for (let warning of options.warnings) {
117-
if (argv.bae) {
118-
const display = bae(options.schema,options.openapi,[warning]);
119-
console.warn(display);
120-
}
121-
else if (options.json) {
110+
if (options.json) {
122111
jsonOutput.warnings.push({ message:warning.message, pointer:warning.pointer, ruleName:warning.ruleName, ruleUrl:warning.rule.url });
123112
}
124113
else {

‎packages/swagger2openapi/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ function fixUpSubSchema(schema,parent,options) {
142142
if (schema.xml && typeof schema.xml.namespace === 'string') {
143143
if (!schema.xml.namespace) delete schema.xml.namespace;
144144
}
145+
if (typeof schema.allowEmptyValue !== 'undefined') {
146+
options.patches++;
147+
delete schema.allowEmptyValue;
148+
}
145149
}
146150

147151
function fixUpSubSchemaExtensions(schema,parent) {

‎packages/swagger2openapi/oas-validate.js

+18-35
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ let argv = yargs
4949
.string('output')
5050
.alias('o', 'output')
5151
.describe('output', 'output conversion result')
52-
.boolean('prettify')
53-
.alias('p','prettify')
54-
.describe('prettify','pretty schema validation errors')
5552
.boolean('quiet')
5653
.alias('q', 'quiet')
5754
.describe('quiet', 'do not show test passes on console, for CI')
@@ -88,8 +85,6 @@ let fail = 0;
8885
let failures = [];
8986
let warnings = [];
9087

91-
let genStack = [];
92-
9388
let options = argv;
9489
options.patch = !argv.nopatch;
9590
options.fatal = true;
@@ -148,7 +143,6 @@ function finalise(err, options) {
148143
if (options.file != 'unknown') failures.push(options.file);
149144
if (argv.stop) process.exit(1);
150145
}
151-
genStackNext();
152146
}
153147

154148
function handleResult(err, options) {
@@ -203,14 +197,7 @@ function handleResult(err, options) {
203197
}
204198
}
205199

206-
function genStackNext() {
207-
if (!genStack.length) return false;
208-
let gen = genStack.shift();
209-
gen.next();
210-
return true;
211-
}
212-
213-
function* check(file, force, expectFailure) {
200+
async function check(file, force, expectFailure) {
214201
let result = false;
215202
options.context = [];
216203
options.expectFailure = expectFailure;
@@ -242,7 +229,6 @@ function* check(file, force, expectFailure) {
242229
}
243230

244231
if (!src || ((!src.swagger && !src.openapi))) {
245-
genStackNext();
246232
return true;
247233
}
248234
}
@@ -278,7 +264,6 @@ function* check(file, force, expectFailure) {
278264
failures.push('Converter failed ' + options.source);
279265
fail++;
280266
}
281-
genStackNext();
282267
result = false;
283268
});
284269
}
@@ -297,65 +282,63 @@ function* check(file, force, expectFailure) {
297282
failures.push('Converter failed ' + options.source);
298283
fail++;
299284
}
300-
genStackNext();
301285
result = false;
302286
});
303287
}
304288
}
305289
else {
306-
genStackNext();
307290
result = true;
308291
}
309292
return result;
310293
}
311294

312-
function processPathSpec(pathspec, expectFailure) {
295+
async function processPathSpec(pathspec, expectFailure) {
313296
globalExpectFailure = expectFailure;
314297
if (pathspec.startsWith('@')) {
315298
pathspec = pathspec.substr(1, pathspec.length - 1);
316299
let list = fs.readFileSync(pathspec, 'utf8').split('\r').join('').split('\n');
317300
for (let file of list) {
318-
genStack.push(check(file, false, expectFailure));
301+
await check(file, false, expectFailure);
319302
}
320-
genStackNext();
321303
}
322304
else if (pathspec.startsWith('http')) {
323-
genStack.push(check(pathspec, true, expectFailure));
324-
genStackNext();
305+
await check(pathspec, true, expectFailure);
325306
}
326307
else if (fs.statSync(path.resolve(pathspec)).isFile()) {
327-
genStack.push(check(pathspec, true, expectFailure));
328-
genStackNext();
308+
await check(pathspec, true, expectFailure);
329309
}
330310
else {
331311
readfiles(pathspec, { readContents: false, filenameFormat: readfiles.FULL_PATH }, function (err) {
332312
if (err) console.warn(yaml.stringify(err));
333313
})
334-
.then(files => {
314+
.then(async function(files) {
335315
files = files.sort();
336316
for (let file of files) {
337-
genStack.push(check(file, false, expectFailure));
317+
await check(file, false, expectFailure);
338318
}
339-
genStackNext();
340319
})
341320
.catch(err => {
342321
handleResult(err,options);
343322
});
344323
}
345324
}
346325

347-
process.exitCode = 1;
348-
console.warn('Gathering...');
349-
for (let pathspec of argv._) {
350-
processPathSpec(pathspec, false);
351-
}
352-
if (argv.fail) {
326+
async function main() {
327+
process.exitCode = 1;
328+
console.warn('Gathering...');
329+
for (let pathspec of argv._) {
330+
await processPathSpec(pathspec, false);
331+
}
332+
if (argv.fail) {
353333
if (!Array.isArray(argv.fail)) argv.fail = [argv.fail];
354334
for (let pathspec of argv.fail) {
355-
processPathSpec(pathspec, true);
335+
await processPathSpec(pathspec, true);
356336
}
337+
}
357338
}
358339

340+
main();
341+
359342
process.on('unhandledRejection', r => console.warn('UPR',r));
360343

361344
process.on('exit', function () {

‎packages/swagger2openapi/package-lock.json

+4,371-540
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/swagger2openapi/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"author": "Mike Ralphson <mike.ralphson@gmail.com>",
3535
"license": "BSD-3-Clause",
3636
"dependencies": {
37-
"better-ajv-errors": "^0.6.1",
3837
"call-me-maybe": "^1.0.1",
3938
"node-fetch-h2": "^2.3.0",
4039
"node-readfiles": "^0.2.0",
@@ -43,7 +42,7 @@
4342
"oas-schema-walker": "^1.1.5",
4443
"oas-validator": "^4.0.8",
4544
"reftools": "^1.1.5",
46-
"yaml": "^1.8.3",
45+
"yaml": "^1.10.0",
4746
"yargs": "^15.3.1"
4847
},
4948
"keywords": [

0 commit comments

Comments
 (0)
Please sign in to comment.