validate sarif against schema before uploading
This commit is contained in:
parent
ff40939f66
commit
ddee374101
21 changed files with 5736 additions and 3 deletions
26
lib/upload-lib.js
generated
26
lib/upload-lib.js
generated
|
|
@ -15,6 +15,7 @@ const http = __importStar(require("@actions/http-client"));
|
|||
const auth = __importStar(require("@actions/http-client/auth"));
|
||||
const file_url_1 = __importDefault(require("file-url"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const jsonschema = __importStar(require("jsonschema"));
|
||||
const path = __importStar(require("path"));
|
||||
const zlib_1 = __importDefault(require("zlib"));
|
||||
const fingerprints = __importStar(require("./fingerprints"));
|
||||
|
|
@ -121,25 +122,48 @@ function countResultsInSarif(sarif) {
|
|||
return numResults;
|
||||
}
|
||||
exports.countResultsInSarif = countResultsInSarif;
|
||||
// Validates that the given file path refers to a valid SARIF file.
|
||||
// Returns a non-empty list of error message if the file is invalid,
|
||||
// otherwise returns the empty list if the file is valid.
|
||||
function validateSarifFileSchema(sarifFilePath) {
|
||||
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, 'utf8'));
|
||||
const schema = JSON.parse(fs.readFileSync(__dirname + '/../src/sarif_v2.1.0_schema.json', 'utf8'));
|
||||
const result = new jsonschema.Validator().validate(sarif, schema);
|
||||
if (result.valid) {
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
return result.errors.map(e => e.message);
|
||||
}
|
||||
}
|
||||
exports.validateSarifFileSchema = validateSarifFileSchema;
|
||||
// Uploads the given set of sarif files.
|
||||
// Returns true iff the upload occurred and succeeded
|
||||
async function uploadFiles(sarifFiles) {
|
||||
core.startGroup("Uploading results");
|
||||
let succeeded = false;
|
||||
try {
|
||||
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||
const sentinelEnvVar = "CODEQL_UPLOAD_SARIF";
|
||||
if (process.env[sentinelEnvVar]) {
|
||||
core.error("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job");
|
||||
return false;
|
||||
}
|
||||
core.exportVariable(sentinelEnvVar, sentinelEnvVar);
|
||||
// Validate that the files we were asked to upload are all valid SARIF files
|
||||
for (const file of sarifFiles) {
|
||||
const errors = validateSarifFileSchema(file);
|
||||
if (errors.length > 0) {
|
||||
core.setFailed("Unable to upload \"" + file + "\" as it is not valid SARIF:\n" + errors.join("\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
||||
const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID');
|
||||
const ref = util.getRef();
|
||||
const analysisKey = await util.getAnalysisKey();
|
||||
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
||||
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
||||
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||
let sarifPayload = combineSarifFiles(sarifFiles);
|
||||
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
||||
const zipped_sarif = zlib_1.default.gzipSync(sarifPayload).toString('base64');
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
25
lib/upload-lib.test.js
generated
Normal file
25
lib/upload-lib.test.js
generated
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
||||
result["default"] = mod;
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const ava_1 = __importDefault(require("ava"));
|
||||
const uploadLib = __importStar(require("./upload-lib"));
|
||||
ava_1.default('validateSarifFileSchema - valid', t => {
|
||||
const inputFile = __dirname + '/../src/testdata/valid-sarif.sarif';
|
||||
const errors = uploadLib.validateSarifFileSchema(inputFile);
|
||||
t.deepEqual(errors, []);
|
||||
});
|
||||
ava_1.default('validateSarifFileSchema - invalid', t => {
|
||||
const inputFile = __dirname + '/../src/testdata/invalid-sarif.sarif';
|
||||
const errors = uploadLib.validateSarifFileSchema(inputFile);
|
||||
t.notDeepEqual(errors, []);
|
||||
});
|
||||
//# sourceMappingURL=upload-lib.test.js.map
|
||||
1
lib/upload-lib.test.js.map
Normal file
1
lib/upload-lib.test.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"upload-lib.test.js","sourceRoot":"","sources":["../src/upload-lib.test.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8CAAuB;AAEvB,wDAA0C;AAE1C,aAAI,CAAC,iCAAiC,EAAE,CAAC,CAAC,EAAE;IAC1C,MAAM,SAAS,GAAG,SAAS,GAAG,oCAAoC,CAAC;IACnE,MAAM,MAAM,GAAG,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEH,aAAI,CAAC,mCAAmC,EAAE,CAAC,CAAC,EAAE;IAC5C,MAAM,SAAS,GAAG,SAAS,GAAG,sCAAsC,CAAC;IACrE,MAAM,MAAM,GAAG,SAAS,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
||||
10
node_modules/jsonschema/.editorconfig
generated
vendored
Normal file
10
node_modules/jsonschema/.editorconfig
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
21
node_modules/jsonschema/LICENSE
generated
vendored
Normal file
21
node_modules/jsonschema/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
jsonschema is licensed under MIT license.
|
||||
|
||||
Copyright (C) 2012-2015 Tom de Grunt <tom@degrunt.nl>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
261
node_modules/jsonschema/README.md
generated
vendored
Normal file
261
node_modules/jsonschema/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
[](http://travis-ci.org/tdegrunt/jsonschema)
|
||||
|
||||
# jsonschema
|
||||
[JSON schema](http://json-schema.org/) validator, which is designed to be fast and simple to use.
|
||||
The latest IETF published draft is v6, this library is mostly v4 compatible.
|
||||
|
||||
## Contributing & bugs
|
||||
Please fork the repository, make the changes in your fork and include tests. Once you're done making changes, send in a pull request.
|
||||
|
||||
### Bug reports
|
||||
Please include a test which shows why the code fails.
|
||||
|
||||
## Usage
|
||||
|
||||
### Simple
|
||||
Simple object validation using JSON schemas.
|
||||
|
||||
```javascript
|
||||
var Validator = require('jsonschema').Validator;
|
||||
var v = new Validator();
|
||||
var instance = 4;
|
||||
var schema = {"type": "number"};
|
||||
console.log(v.validate(instance, schema));
|
||||
```
|
||||
|
||||
### Even simpler
|
||||
|
||||
```javascript
|
||||
var validate = require('jsonschema').validate;
|
||||
console.log(validate(4, {"type": "number"}));
|
||||
```
|
||||
|
||||
### Complex example, with split schemas and references
|
||||
|
||||
```javascript
|
||||
var Validator = require('jsonschema').Validator;
|
||||
var v = new Validator();
|
||||
|
||||
// Address, to be embedded on Person
|
||||
var addressSchema = {
|
||||
"id": "/SimpleAddress",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lines": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"zip": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
"country": {"type": "string"}
|
||||
},
|
||||
"required": ["country"]
|
||||
};
|
||||
|
||||
// Person
|
||||
var schema = {
|
||||
"id": "/SimplePerson",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"address": {"$ref": "/SimpleAddress"},
|
||||
"votes": {"type": "integer", "minimum": 1}
|
||||
}
|
||||
};
|
||||
|
||||
var p = {
|
||||
"name": "Barack Obama",
|
||||
"address": {
|
||||
"lines": [ "1600 Pennsylvania Avenue Northwest" ],
|
||||
"zip": "DC 20500",
|
||||
"city": "Washington",
|
||||
"country": "USA"
|
||||
},
|
||||
"votes": "lots"
|
||||
};
|
||||
|
||||
v.addSchema(addressSchema, '/SimpleAddress');
|
||||
console.log(v.validate(p, schema));
|
||||
```
|
||||
### Example for Array schema
|
||||
```json
|
||||
var arraySchema = {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"lastname": { "type": "string" }
|
||||
},
|
||||
"required": ["name", "lastname"]
|
||||
}
|
||||
}
|
||||
```
|
||||
For a comprehensive, annotated example illustrating all possible validation options, see [examples/all.js](./examples/all.js)
|
||||
|
||||
## Features
|
||||
|
||||
### Definitions
|
||||
All schema definitions are supported, $schema is ignored.
|
||||
|
||||
### Types
|
||||
All types are supported
|
||||
|
||||
### Formats
|
||||
#### Disabling the format keyword.
|
||||
|
||||
You may disable format validation by providing `disableFormat: true` to the validator
|
||||
options.
|
||||
|
||||
#### String Formats
|
||||
All formats are supported, phone numbers are expected to follow the [E.123](http://en.wikipedia.org/wiki/E.123) standard.
|
||||
|
||||
#### Custom Formats
|
||||
You may add your own custom format functions. Format functions accept the input
|
||||
being validated and return a boolean value. If the returned value is `true`, then
|
||||
validation succeeds. If the returned value is `false`, then validation fails.
|
||||
|
||||
* Formats added to `Validator.prototype.customFormats` do not affect previously instantiated
|
||||
Validators. This is to prevent validator instances from being altered once created.
|
||||
It is conceivable that multiple validators may be created to handle multiple schemas
|
||||
with different formats in a program.
|
||||
* Formats added to `validator.customFormats` affect only that Validator instance.
|
||||
|
||||
Here is an example that uses custom formats:
|
||||
|
||||
```javascript
|
||||
Validator.prototype.customFormats.myFormat = function(input) {
|
||||
return input === 'myFormat';
|
||||
};
|
||||
|
||||
var validator = new Validator();
|
||||
validator.validate('myFormat', {type: 'string', format: 'myFormat'}).valid; // true
|
||||
validator.validate('foo', {type: 'string', format: 'myFormat'}).valid; // false
|
||||
```
|
||||
|
||||
### Results
|
||||
The first error found will be thrown as an `Error` object if `options.throwError` is `true`. Otherwise all results will be appended to the `result.errors` array which also contains the success flag `result.valid`.
|
||||
|
||||
When `oneOf` or `anyOf` validations fail, errors that caused any of the sub-schemas referenced therein to fail are not reported, unless `options.nestedErrors` is truthy. This option may be useful when troubleshooting validation errors in complex schemas.
|
||||
|
||||
### Custom properties
|
||||
Specify your own JSON Schema properties with the validator.attributes property:
|
||||
|
||||
```javascript
|
||||
validator.attributes.contains = function validateContains(instance, schema, options, ctx) {
|
||||
if(typeof instance!='string') return;
|
||||
if(typeof schema.contains!='string') throw new jsonschema.SchemaError('"contains" expects a string', schema);
|
||||
if(instance.indexOf(schema.contains)<0){
|
||||
return 'does not contain the string ' + JSON.stringify(schema.contains);
|
||||
}
|
||||
}
|
||||
var result = validator.validate("i am an instance", { type:"string", contains: "i am" });
|
||||
// result.valid === true;
|
||||
```
|
||||
|
||||
The instance passes validation if the function returns nothing. A single validation error is produced
|
||||
if the fuction returns a string. Any number of errors (maybe none at all) may be returned by passing a
|
||||
`ValidatorResult` object, which may be used like so:
|
||||
|
||||
```javascript
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
while(someErrorCondition()){
|
||||
result.addError('fails some validation test');
|
||||
}
|
||||
return result;
|
||||
```
|
||||
|
||||
### Dereferencing schemas
|
||||
Sometimes you may want to download schemas from remote sources, like a database, or over HTTP. When importing a schema,
|
||||
unknown references are inserted into the `validator.unresolvedRefs` Array. Asynchronously shift elements off this array and import
|
||||
them:
|
||||
|
||||
```javascript
|
||||
var Validator = require('jsonschema').Validator;
|
||||
var v = new Validator();
|
||||
v.addSchema(initialSchema);
|
||||
function importNextSchema(){
|
||||
var nextSchema = v.unresolvedRefs.shift();
|
||||
if(!nextSchema){ done(); return; }
|
||||
databaseGet(nextSchema, function(schema){
|
||||
v.addSchema(schema);
|
||||
importNextSchema();
|
||||
});
|
||||
}
|
||||
importNextSchema();
|
||||
```
|
||||
|
||||
### Pre-Property Validation Hook
|
||||
If some processing of properties is required prior to validation a function may be passed via the options parameter of the validate function. For example, say you needed to perform type coercion for some properties:
|
||||
|
||||
```javascript
|
||||
const coercionHook = function (instance, property, schema, options, ctx) {
|
||||
var value = instance[property];
|
||||
|
||||
// Skip nulls and undefineds
|
||||
if (value === null || typeof value == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the schema declares a type and the property fails type validation.
|
||||
if (schema.type && this.attributes.type.call(this, instance, schema, options, ctx.makeChild(schema, property))) {
|
||||
var types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
||||
var coerced = undefined;
|
||||
|
||||
// Go through the declared types until we find something that we can
|
||||
// coerce the value into.
|
||||
for (var i = 0; typeof coerced == 'undefined' && i < types.length; i++) {
|
||||
// If we support coercion to this type
|
||||
if (lib.coercions[types[i]]) {
|
||||
// ...attempt it.
|
||||
coerced = lib.coercions[types[i]](value);
|
||||
}
|
||||
}
|
||||
// If we got a successful coercion we modify the property of the instance.
|
||||
if (typeof coerced != 'undefined') {
|
||||
instance[property] = coerced;
|
||||
}
|
||||
}
|
||||
}.bind(validator)
|
||||
|
||||
// And now, to actually perform validation with the coercion hook!
|
||||
v.validate(instance, schema, { preValidateProperty: coercionHook });
|
||||
```
|
||||
|
||||
## Tests
|
||||
Uses [JSON Schema Test Suite](https://github.com/json-schema/JSON-Schema-Test-Suite) as well as our own tests.
|
||||
You'll need to update and init the git submodules:
|
||||
|
||||
git submodule update --init
|
||||
npm test
|
||||
|
||||
## Contributions
|
||||
|
||||
This library would not be possible without the valuable contributions by:
|
||||
|
||||
- Austin Wright
|
||||
|
||||
... and many others!
|
||||
|
||||
## License
|
||||
|
||||
jsonschema is licensed under MIT license.
|
||||
|
||||
Copyright (C) 2012-2019 Tom de Grunt <tom@degrunt.nl>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
820
node_modules/jsonschema/lib/attribute.js
generated
vendored
Normal file
820
node_modules/jsonschema/lib/attribute.js
generated
vendored
Normal file
|
|
@ -0,0 +1,820 @@
|
|||
'use strict';
|
||||
|
||||
var helpers = require('./helpers');
|
||||
|
||||
/** @type ValidatorResult */
|
||||
var ValidatorResult = helpers.ValidatorResult;
|
||||
/** @type SchemaError */
|
||||
var SchemaError = helpers.SchemaError;
|
||||
|
||||
var attribute = {};
|
||||
|
||||
attribute.ignoreProperties = {
|
||||
// informative properties
|
||||
'id': true,
|
||||
'default': true,
|
||||
'description': true,
|
||||
'title': true,
|
||||
// arguments to other properties
|
||||
'exclusiveMinimum': true,
|
||||
'exclusiveMaximum': true,
|
||||
'additionalItems': true,
|
||||
// special-handled properties
|
||||
'$schema': true,
|
||||
'$ref': true,
|
||||
'extends': true
|
||||
};
|
||||
|
||||
/**
|
||||
* @name validators
|
||||
*/
|
||||
var validators = attribute.validators = {};
|
||||
|
||||
/**
|
||||
* Validates whether the instance if of a certain type
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {ValidatorResult|null}
|
||||
*/
|
||||
validators.type = function validateType (instance, schema, options, ctx) {
|
||||
// Ignore undefined instances
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
||||
if (!types.some(this.testType.bind(this, instance, schema, options, ctx))) {
|
||||
var list = types.map(function (v) {
|
||||
return v.id && ('<' + v.id + '>') || (v+'');
|
||||
});
|
||||
result.addError({
|
||||
name: 'type',
|
||||
argument: list,
|
||||
message: "is not of a type(s) " + list,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
function testSchemaNoThrow(instance, options, ctx, callback, schema){
|
||||
var throwError = options.throwError;
|
||||
options.throwError = false;
|
||||
var res = this.validateSchema(instance, schema, options, ctx);
|
||||
options.throwError = throwError;
|
||||
|
||||
if (!res.valid && callback instanceof Function) {
|
||||
callback(res);
|
||||
}
|
||||
return res.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the instance matches some of the given schemas
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {ValidatorResult|null}
|
||||
*/
|
||||
validators.anyOf = function validateAnyOf (instance, schema, options, ctx) {
|
||||
// Ignore undefined instances
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var inner = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!Array.isArray(schema.anyOf)){
|
||||
throw new SchemaError("anyOf must be an array");
|
||||
}
|
||||
if (!schema.anyOf.some(
|
||||
testSchemaNoThrow.bind(
|
||||
this, instance, options, ctx, function(res){inner.importErrors(res);}
|
||||
))) {
|
||||
var list = schema.anyOf.map(function (v, i) {
|
||||
return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
||||
});
|
||||
if (options.nestedErrors) {
|
||||
result.importErrors(inner);
|
||||
}
|
||||
result.addError({
|
||||
name: 'anyOf',
|
||||
argument: list,
|
||||
message: "is not any of " + list.join(','),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance matches every given schema
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.allOf = function validateAllOf (instance, schema, options, ctx) {
|
||||
// Ignore undefined instances
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(schema.allOf)){
|
||||
throw new SchemaError("allOf must be an array");
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var self = this;
|
||||
schema.allOf.forEach(function(v, i){
|
||||
var valid = self.validateSchema(instance, v, options, ctx);
|
||||
if(!valid.valid){
|
||||
var msg = (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
||||
result.addError({
|
||||
name: 'allOf',
|
||||
argument: { id: msg, length: valid.errors.length, valid: valid },
|
||||
message: 'does not match allOf schema ' + msg + ' with ' + valid.errors.length + ' error[s]:',
|
||||
});
|
||||
result.importErrors(valid);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance matches exactly one of the given schemas
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.oneOf = function validateOneOf (instance, schema, options, ctx) {
|
||||
// Ignore undefined instances
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(schema.oneOf)){
|
||||
throw new SchemaError("oneOf must be an array");
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var inner = new ValidatorResult(instance, schema, options, ctx);
|
||||
var count = schema.oneOf.filter(
|
||||
testSchemaNoThrow.bind(
|
||||
this, instance, options, ctx, function(res) {inner.importErrors(res);}
|
||||
) ).length;
|
||||
var list = schema.oneOf.map(function (v, i) {
|
||||
return (v.id && ('<' + v.id + '>')) || (v.title && JSON.stringify(v.title)) || (v['$ref'] && ('<' + v['$ref'] + '>')) || '[subschema '+i+']';
|
||||
});
|
||||
if (count!==1) {
|
||||
if (options.nestedErrors) {
|
||||
result.importErrors(inner);
|
||||
}
|
||||
result.addError({
|
||||
name: 'oneOf',
|
||||
argument: list,
|
||||
message: "is not exactly one from " + list.join(','),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates properties
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null|ValidatorResult}
|
||||
*/
|
||||
validators.properties = function validateProperties (instance, schema, options, ctx) {
|
||||
if(!this.types.object(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var properties = schema.properties || {};
|
||||
for (var property in properties) {
|
||||
if (typeof options.preValidateProperty == 'function') {
|
||||
options.preValidateProperty(instance, property, properties[property], options, ctx);
|
||||
}
|
||||
|
||||
var prop = Object.hasOwnProperty.call(instance, property) ? instance[property] : undefined;
|
||||
var res = this.validateSchema(prop, properties[property], options, ctx.makeChild(properties[property], property));
|
||||
if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
|
||||
result.importErrors(res);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Test a specific property within in instance against the additionalProperties schema attribute
|
||||
* This ignores properties with definitions in the properties schema attribute, but no other attributes.
|
||||
* If too many more types of property-existance tests pop up they may need their own class of tests (like `type` has)
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
function testAdditionalProperty (instance, schema, options, ctx, property, result) {
|
||||
if(!this.types.object(instance)) return;
|
||||
if (schema.properties && schema.properties[property] !== undefined) {
|
||||
return;
|
||||
}
|
||||
if (schema.additionalProperties === false) {
|
||||
result.addError({
|
||||
name: 'additionalProperties',
|
||||
argument: property,
|
||||
message: "additionalProperty " + JSON.stringify(property) + " exists in instance when not allowed",
|
||||
});
|
||||
} else {
|
||||
var additionalProperties = schema.additionalProperties || {};
|
||||
|
||||
if (typeof options.preValidateProperty == 'function') {
|
||||
options.preValidateProperty(instance, property, additionalProperties, options, ctx);
|
||||
}
|
||||
|
||||
var res = this.validateSchema(instance[property], additionalProperties, options, ctx.makeChild(additionalProperties, property));
|
||||
if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
|
||||
result.importErrors(res);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates patternProperties
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null|ValidatorResult}
|
||||
*/
|
||||
validators.patternProperties = function validatePatternProperties (instance, schema, options, ctx) {
|
||||
if(!this.types.object(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var patternProperties = schema.patternProperties || {};
|
||||
|
||||
for (var property in instance) {
|
||||
var test = true;
|
||||
for (var pattern in patternProperties) {
|
||||
var expr = new RegExp(pattern);
|
||||
if (!expr.test(property)) {
|
||||
continue;
|
||||
}
|
||||
test = false;
|
||||
|
||||
if (typeof options.preValidateProperty == 'function') {
|
||||
options.preValidateProperty(instance, property, patternProperties[pattern], options, ctx);
|
||||
}
|
||||
|
||||
var res = this.validateSchema(instance[property], patternProperties[pattern], options, ctx.makeChild(patternProperties[pattern], property));
|
||||
if(res.instance !== result.instance[property]) result.instance[property] = res.instance;
|
||||
result.importErrors(res);
|
||||
}
|
||||
if (test) {
|
||||
testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates additionalProperties
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null|ValidatorResult}
|
||||
*/
|
||||
validators.additionalProperties = function validateAdditionalProperties (instance, schema, options, ctx) {
|
||||
if(!this.types.object(instance)) return;
|
||||
// if patternProperties is defined then we'll test when that one is called instead
|
||||
if (schema.patternProperties) {
|
||||
return null;
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
for (var property in instance) {
|
||||
testAdditionalProperty.call(this, instance, schema, options, ctx, property, result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is at least of a certain length, when the instance value is a string.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.minProperties = function validateMinProperties (instance, schema, options, ctx) {
|
||||
if (!this.types.object(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var keys = Object.keys(instance);
|
||||
if (!(keys.length >= schema.minProperties)) {
|
||||
result.addError({
|
||||
name: 'minProperties',
|
||||
argument: schema.minProperties,
|
||||
message: "does not meet minimum property length of " + schema.minProperties,
|
||||
})
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is at most of a certain length, when the instance value is a string.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.maxProperties = function validateMaxProperties (instance, schema, options, ctx) {
|
||||
if (!this.types.object(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var keys = Object.keys(instance);
|
||||
if (!(keys.length <= schema.maxProperties)) {
|
||||
result.addError({
|
||||
name: 'maxProperties',
|
||||
argument: schema.maxProperties,
|
||||
message: "does not meet maximum property length of " + schema.maxProperties,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates items when instance is an array
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null|ValidatorResult}
|
||||
*/
|
||||
validators.items = function validateItems (instance, schema, options, ctx) {
|
||||
var self = this;
|
||||
if (!this.types.array(instance)) return;
|
||||
if (!schema.items) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
instance.every(function (value, i) {
|
||||
var items = Array.isArray(schema.items) ? (schema.items[i] || schema.additionalItems) : schema.items;
|
||||
if (items === undefined) {
|
||||
return true;
|
||||
}
|
||||
if (items === false) {
|
||||
result.addError({
|
||||
name: 'items',
|
||||
message: "additionalItems not permitted",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
var res = self.validateSchema(value, items, options, ctx.makeChild(items, i));
|
||||
if(res.instance !== result.instance[i]) result.instance[i] = res.instance;
|
||||
result.importErrors(res);
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates minimum and exclusiveMinimum when the type of the instance value is a number.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.minimum = function validateMinimum (instance, schema, options, ctx) {
|
||||
if (!this.types.number(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var valid = true;
|
||||
if (schema.exclusiveMinimum && schema.exclusiveMinimum === true) {
|
||||
valid = instance > schema.minimum;
|
||||
} else {
|
||||
valid = instance >= schema.minimum;
|
||||
}
|
||||
if (!valid) {
|
||||
result.addError({
|
||||
name: 'minimum',
|
||||
argument: schema.minimum,
|
||||
message: "must have a minimum value of " + schema.minimum,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates maximum and exclusiveMaximum when the type of the instance value is a number.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.maximum = function validateMaximum (instance, schema, options, ctx) {
|
||||
if (!this.types.number(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var valid;
|
||||
if (schema.exclusiveMaximum && schema.exclusiveMaximum === true) {
|
||||
valid = instance < schema.maximum;
|
||||
} else {
|
||||
valid = instance <= schema.maximum;
|
||||
}
|
||||
if (!valid) {
|
||||
result.addError({
|
||||
name: 'maximum',
|
||||
argument: schema.maximum,
|
||||
message: "must have a maximum value of " + schema.maximum,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Perform validation for multipleOf and divisibleBy, which are essentially the same.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param validationType
|
||||
* @param errorMessage
|
||||
* @returns {String|null}
|
||||
*/
|
||||
var validateMultipleOfOrDivisbleBy = function validateMultipleOfOrDivisbleBy (instance, schema, options, ctx, validationType, errorMessage) {
|
||||
if (!this.types.number(instance)) return;
|
||||
|
||||
var validationArgument = schema[validationType];
|
||||
if (validationArgument == 0) {
|
||||
throw new SchemaError(validationType + " cannot be zero");
|
||||
}
|
||||
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
|
||||
var instanceDecimals = helpers.getDecimalPlaces(instance);
|
||||
var divisorDecimals = helpers.getDecimalPlaces(validationArgument);
|
||||
|
||||
var maxDecimals = Math.max(instanceDecimals , divisorDecimals);
|
||||
var multiplier = Math.pow(10, maxDecimals);
|
||||
|
||||
if (Math.round(instance * multiplier) % Math.round(validationArgument * multiplier) !== 0) {
|
||||
result.addError({
|
||||
name: validationType,
|
||||
argument: validationArgument,
|
||||
message: errorMessage + JSON.stringify(validationArgument)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates divisibleBy when the type of the instance value is a number.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.multipleOf = function validateMultipleOf (instance, schema, options, ctx) {
|
||||
return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "multipleOf", "is not a multiple of (divisible by) ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates multipleOf when the type of the instance value is a number.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.divisibleBy = function validateDivisibleBy (instance, schema, options, ctx) {
|
||||
return validateMultipleOfOrDivisbleBy.call(this, instance, schema, options, ctx, "divisibleBy", "is not divisible by (multiple of) ");
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is present.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.required = function validateRequired (instance, schema, options, ctx) {
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (instance === undefined && schema.required === true) {
|
||||
// A boolean form is implemented for reverse-compatability with schemas written against older drafts
|
||||
result.addError({
|
||||
name: 'required',
|
||||
message: "is required"
|
||||
});
|
||||
} else if (this.types.object(instance) && Array.isArray(schema.required)) {
|
||||
schema.required.forEach(function(n){
|
||||
if(instance[n]===undefined){
|
||||
result.addError({
|
||||
name: 'required',
|
||||
argument: n,
|
||||
message: "requires property " + JSON.stringify(n),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value matches the regular expression, when the instance value is a string.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.pattern = function validatePattern (instance, schema, options, ctx) {
|
||||
if (!this.types.string(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!instance.match(schema.pattern)) {
|
||||
result.addError({
|
||||
name: 'pattern',
|
||||
argument: schema.pattern,
|
||||
message: "does not match pattern " + JSON.stringify(schema.pattern.toString()),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is of a certain defined format or a custom
|
||||
* format.
|
||||
* The following formats are supported for string types:
|
||||
* - date-time
|
||||
* - date
|
||||
* - time
|
||||
* - ip-address
|
||||
* - ipv6
|
||||
* - uri
|
||||
* - color
|
||||
* - host-name
|
||||
* - alpha
|
||||
* - alpha-numeric
|
||||
* - utc-millisec
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param [options]
|
||||
* @param [ctx]
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.format = function validateFormat (instance, schema, options, ctx) {
|
||||
if (instance===undefined) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!result.disableFormat && !helpers.isFormat(instance, schema.format, this)) {
|
||||
result.addError({
|
||||
name: 'format',
|
||||
argument: schema.format,
|
||||
message: "does not conform to the " + JSON.stringify(schema.format) + " format",
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is at least of a certain length, when the instance value is a string.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.minLength = function validateMinLength (instance, schema, options, ctx) {
|
||||
if (!this.types.string(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var hsp = instance.match(/[\uDC00-\uDFFF]/g);
|
||||
var length = instance.length - (hsp ? hsp.length : 0);
|
||||
if (!(length >= schema.minLength)) {
|
||||
result.addError({
|
||||
name: 'minLength',
|
||||
argument: schema.minLength,
|
||||
message: "does not meet minimum length of " + schema.minLength,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is at most of a certain length, when the instance value is a string.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.maxLength = function validateMaxLength (instance, schema, options, ctx) {
|
||||
if (!this.types.string(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
// TODO if this was already computed in "minLength", use that value instead of re-computing
|
||||
var hsp = instance.match(/[\uDC00-\uDFFF]/g);
|
||||
var length = instance.length - (hsp ? hsp.length : 0);
|
||||
if (!(length <= schema.maxLength)) {
|
||||
result.addError({
|
||||
name: 'maxLength',
|
||||
argument: schema.maxLength,
|
||||
message: "does not meet maximum length of " + schema.maxLength,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether instance contains at least a minimum number of items, when the instance is an Array.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.minItems = function validateMinItems (instance, schema, options, ctx) {
|
||||
if (!this.types.array(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!(instance.length >= schema.minItems)) {
|
||||
result.addError({
|
||||
name: 'minItems',
|
||||
argument: schema.minItems,
|
||||
message: "does not meet minimum length of " + schema.minItems,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether instance contains no more than a maximum number of items, when the instance is an Array.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.maxItems = function validateMaxItems (instance, schema, options, ctx) {
|
||||
if (!this.types.array(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!(instance.length <= schema.maxItems)) {
|
||||
result.addError({
|
||||
name: 'maxItems',
|
||||
argument: schema.maxItems,
|
||||
message: "does not meet maximum length of " + schema.maxItems,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates that every item in an instance array is unique, when instance is an array
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {String|null|ValidatorResult}
|
||||
*/
|
||||
validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
|
||||
if (!this.types.array(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
function testArrays (v, i, a) {
|
||||
for (var j = i + 1; j < a.length; j++) if (helpers.deepCompareStrict(v, a[j])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!instance.every(testArrays)) {
|
||||
result.addError({
|
||||
name: 'uniqueItems',
|
||||
message: "contains duplicate item",
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deep compares arrays for duplicates
|
||||
* @param v
|
||||
* @param i
|
||||
* @param a
|
||||
* @private
|
||||
* @return {boolean}
|
||||
*/
|
||||
function testArrays (v, i, a) {
|
||||
var j, len = a.length;
|
||||
for (j = i + 1, len; j < len; j++) {
|
||||
if (helpers.deepCompareStrict(v, a[j])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether there are no duplicates, when the instance is an Array.
|
||||
* @param instance
|
||||
* @return {String|null}
|
||||
*/
|
||||
validators.uniqueItems = function validateUniqueItems (instance, schema, options, ctx) {
|
||||
if (!this.types.array(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!instance.every(testArrays)) {
|
||||
result.addError({
|
||||
name: 'uniqueItems',
|
||||
message: "contains duplicate item",
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate for the presence of dependency properties, if the instance is an object.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {null|ValidatorResult}
|
||||
*/
|
||||
validators.dependencies = function validateDependencies (instance, schema, options, ctx) {
|
||||
if (!this.types.object(instance)) return;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
for (var property in schema.dependencies) {
|
||||
if (instance[property] === undefined) {
|
||||
continue;
|
||||
}
|
||||
var dep = schema.dependencies[property];
|
||||
var childContext = ctx.makeChild(dep, property);
|
||||
if (typeof dep == 'string') {
|
||||
dep = [dep];
|
||||
}
|
||||
if (Array.isArray(dep)) {
|
||||
dep.forEach(function (prop) {
|
||||
if (instance[prop] === undefined) {
|
||||
result.addError({
|
||||
// FIXME there's two different "dependencies" errors here with slightly different outputs
|
||||
// Can we make these the same? Or should we create different error types?
|
||||
name: 'dependencies',
|
||||
argument: childContext.propertyPath,
|
||||
message: "property " + prop + " not found, required by " + childContext.propertyPath,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var res = this.validateSchema(instance, dep, options, childContext);
|
||||
if(result.instance !== res.instance) result.instance = res.instance;
|
||||
if (res && res.errors.length) {
|
||||
result.addError({
|
||||
name: 'dependencies',
|
||||
argument: childContext.propertyPath,
|
||||
message: "does not meet dependency required by " + childContext.propertyPath,
|
||||
});
|
||||
result.importErrors(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance value is one of the enumerated values.
|
||||
*
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {ValidatorResult|null}
|
||||
*/
|
||||
validators['enum'] = function validateEnum (instance, schema, options, ctx) {
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (!Array.isArray(schema['enum'])) {
|
||||
throw new SchemaError("enum expects an array", schema);
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!schema['enum'].some(helpers.deepCompareStrict.bind(null, instance))) {
|
||||
result.addError({
|
||||
name: 'enum',
|
||||
argument: schema['enum'],
|
||||
message: "is not one of enum values: " + schema['enum'].map(String).join(','),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance exactly matches a given value
|
||||
*
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @return {ValidatorResult|null}
|
||||
*/
|
||||
validators['const'] = function validateEnum (instance, schema, options, ctx) {
|
||||
if (instance === undefined) {
|
||||
return null;
|
||||
}
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
if (!helpers.deepCompareStrict(schema['const'], instance)) {
|
||||
result.addError({
|
||||
name: 'const',
|
||||
argument: schema['const'],
|
||||
message: "does not exactly match expected constant: " + schema['const'],
|
||||
});
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates whether the instance if of a prohibited type.
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @return {null|ValidatorResult}
|
||||
*/
|
||||
validators.not = validators.disallow = function validateNot (instance, schema, options, ctx) {
|
||||
var self = this;
|
||||
if(instance===undefined) return null;
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
var notTypes = schema.not || schema.disallow;
|
||||
if(!notTypes) return null;
|
||||
if(!Array.isArray(notTypes)) notTypes=[notTypes];
|
||||
notTypes.forEach(function (type) {
|
||||
if (self.testType(instance, schema, options, ctx, type)) {
|
||||
var schemaId = type && type.id && ('<' + type.id + '>') || type;
|
||||
result.addError({
|
||||
name: 'not',
|
||||
argument: schemaId,
|
||||
message: "is of prohibited type " + schemaId,
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = attribute;
|
||||
325
node_modules/jsonschema/lib/helpers.js
generated
vendored
Normal file
325
node_modules/jsonschema/lib/helpers.js
generated
vendored
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
'use strict';
|
||||
|
||||
var uri = require('url');
|
||||
|
||||
var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, propertyPath, name, argument) {
|
||||
if (propertyPath) {
|
||||
this.property = propertyPath;
|
||||
}
|
||||
if (message) {
|
||||
this.message = message;
|
||||
}
|
||||
if (schema) {
|
||||
if (schema.id) {
|
||||
this.schema = schema.id;
|
||||
} else {
|
||||
this.schema = schema;
|
||||
}
|
||||
}
|
||||
if (instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
this.name = name;
|
||||
this.argument = argument;
|
||||
this.stack = this.toString();
|
||||
};
|
||||
|
||||
ValidationError.prototype.toString = function toString() {
|
||||
return this.property + ' ' + this.message;
|
||||
};
|
||||
|
||||
var ValidatorResult = exports.ValidatorResult = function ValidatorResult(instance, schema, options, ctx) {
|
||||
this.instance = instance;
|
||||
this.schema = schema;
|
||||
this.propertyPath = ctx.propertyPath;
|
||||
this.errors = [];
|
||||
this.throwError = options && options.throwError;
|
||||
this.disableFormat = options && options.disableFormat === true;
|
||||
};
|
||||
|
||||
ValidatorResult.prototype.addError = function addError(detail) {
|
||||
var err;
|
||||
if (typeof detail == 'string') {
|
||||
err = new ValidationError(detail, this.instance, this.schema, this.propertyPath);
|
||||
} else {
|
||||
if (!detail) throw new Error('Missing error detail');
|
||||
if (!detail.message) throw new Error('Missing error message');
|
||||
if (!detail.name) throw new Error('Missing validator type');
|
||||
err = new ValidationError(detail.message, this.instance, this.schema, this.propertyPath, detail.name, detail.argument);
|
||||
}
|
||||
|
||||
if (this.throwError) {
|
||||
throw err;
|
||||
}
|
||||
this.errors.push(err);
|
||||
return err;
|
||||
};
|
||||
|
||||
ValidatorResult.prototype.importErrors = function importErrors(res) {
|
||||
if (typeof res == 'string' || (res && res.validatorType)) {
|
||||
this.addError(res);
|
||||
} else if (res && res.errors) {
|
||||
Array.prototype.push.apply(this.errors, res.errors);
|
||||
}
|
||||
};
|
||||
|
||||
function stringizer (v,i){
|
||||
return i+': '+v.toString()+'\n';
|
||||
}
|
||||
ValidatorResult.prototype.toString = function toString(res) {
|
||||
return this.errors.map(stringizer).join('');
|
||||
};
|
||||
|
||||
Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() {
|
||||
return !this.errors.length;
|
||||
} });
|
||||
|
||||
/**
|
||||
* Describes a problem with a Schema which prevents validation of an instance
|
||||
* @name SchemaError
|
||||
* @constructor
|
||||
*/
|
||||
var SchemaError = exports.SchemaError = function SchemaError (msg, schema) {
|
||||
this.message = msg;
|
||||
this.schema = schema;
|
||||
Error.call(this, msg);
|
||||
Error.captureStackTrace(this, SchemaError);
|
||||
};
|
||||
SchemaError.prototype = Object.create(Error.prototype,
|
||||
{ constructor: {value: SchemaError, enumerable: false}
|
||||
, name: {value: 'SchemaError', enumerable: false}
|
||||
});
|
||||
|
||||
var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, propertyPath, base, schemas) {
|
||||
this.schema = schema;
|
||||
this.options = options;
|
||||
this.propertyPath = propertyPath;
|
||||
this.base = base;
|
||||
this.schemas = schemas;
|
||||
};
|
||||
|
||||
SchemaContext.prototype.resolve = function resolve (target) {
|
||||
return uri.resolve(this.base, target);
|
||||
};
|
||||
|
||||
SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){
|
||||
var propertyPath = (propertyName===undefined) ? this.propertyPath : this.propertyPath+makeSuffix(propertyName);
|
||||
var base = uri.resolve(this.base, schema.id||'');
|
||||
var ctx = new SchemaContext(schema, this.options, propertyPath, base, Object.create(this.schemas));
|
||||
if(schema.id && !ctx.schemas[base]){
|
||||
ctx.schemas[base] = schema;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = {
|
||||
'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])[tT ](2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])(\.\d+)?([zZ]|[+-]([0-5][0-9]):(60|[0-5][0-9]))$/,
|
||||
'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])$/,
|
||||
'time': /^(2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])$/,
|
||||
|
||||
'email': /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/,
|
||||
'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
|
||||
'ipv6': /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
|
||||
'uri': /^[a-zA-Z][a-zA-Z0-9+-.]*:[^\s]*$/,
|
||||
|
||||
'color': /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/,
|
||||
|
||||
// hostname regex from: http://stackoverflow.com/a/1420225/5628
|
||||
'hostname': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
|
||||
'host-name': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
|
||||
|
||||
'alpha': /^[a-zA-Z]+$/,
|
||||
'alphanumeric': /^[a-zA-Z0-9]+$/,
|
||||
'utc-millisec': function (input) {
|
||||
return (typeof input === 'string') && parseFloat(input) === parseInt(input, 10) && !isNaN(input);
|
||||
},
|
||||
'regex': function (input) {
|
||||
var result = true;
|
||||
try {
|
||||
new RegExp(input);
|
||||
} catch (e) {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
'style': /\s*(.+?):\s*([^;]+);?/,
|
||||
'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/
|
||||
};
|
||||
|
||||
FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex;
|
||||
FORMAT_REGEXPS.pattern = FORMAT_REGEXPS.regex;
|
||||
FORMAT_REGEXPS.ipv4 = FORMAT_REGEXPS['ip-address'];
|
||||
|
||||
exports.isFormat = function isFormat (input, format, validator) {
|
||||
if (typeof input === 'string' && FORMAT_REGEXPS[format] !== undefined) {
|
||||
if (FORMAT_REGEXPS[format] instanceof RegExp) {
|
||||
return FORMAT_REGEXPS[format].test(input);
|
||||
}
|
||||
if (typeof FORMAT_REGEXPS[format] === 'function') {
|
||||
return FORMAT_REGEXPS[format](input);
|
||||
}
|
||||
} else if (validator && validator.customFormats &&
|
||||
typeof validator.customFormats[format] === 'function') {
|
||||
return validator.customFormats[format](input);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
var makeSuffix = exports.makeSuffix = function makeSuffix (key) {
|
||||
key = key.toString();
|
||||
// This function could be capable of outputting valid a ECMAScript string, but the
|
||||
// resulting code for testing which form to use would be tens of thousands of characters long
|
||||
// That means this will use the name form for some illegal forms
|
||||
if (!key.match(/[.\s\[\]]/) && !key.match(/^[\d]/)) {
|
||||
return '.' + key;
|
||||
}
|
||||
if (key.match(/^\d+$/)) {
|
||||
return '[' + key + ']';
|
||||
}
|
||||
return '[' + JSON.stringify(key) + ']';
|
||||
};
|
||||
|
||||
exports.deepCompareStrict = function deepCompareStrict (a, b) {
|
||||
if (typeof a !== typeof b) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
if (!Array.isArray(b)) {
|
||||
return false;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
return a.every(function (v, i) {
|
||||
return deepCompareStrict(a[i], b[i]);
|
||||
});
|
||||
}
|
||||
if (typeof a === 'object') {
|
||||
if (!a || !b) {
|
||||
return a === b;
|
||||
}
|
||||
var aKeys = Object.keys(a);
|
||||
var bKeys = Object.keys(b);
|
||||
if (aKeys.length !== bKeys.length) {
|
||||
return false;
|
||||
}
|
||||
return aKeys.every(function (v) {
|
||||
return deepCompareStrict(a[v], b[v]);
|
||||
});
|
||||
}
|
||||
return a === b;
|
||||
};
|
||||
|
||||
function deepMerger (target, dst, e, i) {
|
||||
if (typeof e === 'object') {
|
||||
dst[i] = deepMerge(target[i], e)
|
||||
} else {
|
||||
if (target.indexOf(e) === -1) {
|
||||
dst.push(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function copyist (src, dst, key) {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
|
||||
function copyistWithDeepMerge (target, src, dst, key) {
|
||||
if (typeof src[key] !== 'object' || !src[key]) {
|
||||
dst[key] = src[key];
|
||||
}
|
||||
else {
|
||||
if (!target[key]) {
|
||||
dst[key] = src[key];
|
||||
} else {
|
||||
dst[key] = deepMerge(target[key], src[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deepMerge (target, src) {
|
||||
var array = Array.isArray(src);
|
||||
var dst = array && [] || {};
|
||||
|
||||
if (array) {
|
||||
target = target || [];
|
||||
dst = dst.concat(target);
|
||||
src.forEach(deepMerger.bind(null, target, dst));
|
||||
} else {
|
||||
if (target && typeof target === 'object') {
|
||||
Object.keys(target).forEach(copyist.bind(null, target, dst));
|
||||
}
|
||||
Object.keys(src).forEach(copyistWithDeepMerge.bind(null, target, src, dst));
|
||||
}
|
||||
|
||||
return dst;
|
||||
};
|
||||
|
||||
module.exports.deepMerge = deepMerge;
|
||||
|
||||
/**
|
||||
* Validates instance against the provided schema
|
||||
* Implements URI+JSON Pointer encoding, e.g. "%7e"="~0"=>"~", "~1"="%2f"=>"/"
|
||||
* @param o
|
||||
* @param s The path to walk o along
|
||||
* @return any
|
||||
*/
|
||||
exports.objectGetPath = function objectGetPath(o, s) {
|
||||
var parts = s.split('/').slice(1);
|
||||
var k;
|
||||
while (typeof (k=parts.shift()) == 'string') {
|
||||
var n = decodeURIComponent(k.replace(/~0/,'~').replace(/~1/g,'/'));
|
||||
if (!(n in o)) return;
|
||||
o = o[n];
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
function pathEncoder (v) {
|
||||
return '/'+encodeURIComponent(v).replace(/~/g,'%7E');
|
||||
}
|
||||
/**
|
||||
* Accept an Array of property names and return a JSON Pointer URI fragment
|
||||
* @param Array a
|
||||
* @return {String}
|
||||
*/
|
||||
exports.encodePath = function encodePointer(a){
|
||||
// ~ must be encoded explicitly because hacks
|
||||
// the slash is encoded by encodeURIComponent
|
||||
return a.map(pathEncoder).join('');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the number of decimal places a number uses
|
||||
* We need this to get correct results out of multipleOf and divisibleBy
|
||||
* when either figure is has decimal places, due to IEEE-754 float issues.
|
||||
* @param number
|
||||
* @returns {number}
|
||||
*/
|
||||
exports.getDecimalPlaces = function getDecimalPlaces(number) {
|
||||
|
||||
var decimalPlaces = 0;
|
||||
if (isNaN(number)) return decimalPlaces;
|
||||
|
||||
if (typeof number !== 'number') {
|
||||
number = Number(number);
|
||||
}
|
||||
|
||||
var parts = number.toString().split('e');
|
||||
if (parts.length === 2) {
|
||||
if (parts[1][0] !== '-') {
|
||||
return decimalPlaces;
|
||||
} else {
|
||||
decimalPlaces = Number(parts[1].slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
var decimalParts = parts[0].split('.');
|
||||
if (decimalParts.length === 2) {
|
||||
decimalPlaces += decimalParts[1].length;
|
||||
}
|
||||
|
||||
return decimalPlaces;
|
||||
};
|
||||
|
||||
127
node_modules/jsonschema/lib/index.d.ts
generated
vendored
Normal file
127
node_modules/jsonschema/lib/index.d.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
This is type definition for typescript.
|
||||
This is for library users. Thus, properties and methods for internal use is omitted.
|
||||
*/
|
||||
export declare class Validator {
|
||||
constructor();
|
||||
customFormats: {[formatName: string]: CustomFormat};
|
||||
schemas: {[id: string]: Schema};
|
||||
unresolvedRefs: string[];
|
||||
|
||||
attributes: {[property: string]: CustomProperty};
|
||||
|
||||
addSchema(schema?: Schema, uri?: string): Schema|void;
|
||||
validate(instance: any, schema: Schema, options?: Options, ctx?: SchemaContext): ValidatorResult;
|
||||
}
|
||||
|
||||
export declare class ValidatorResult {
|
||||
constructor(instance: any, schema: Schema, options: Options, ctx: SchemaContext)
|
||||
instance: any;
|
||||
schema: Schema;
|
||||
propertyPath: string;
|
||||
errors: ValidationError[];
|
||||
throwError: boolean;
|
||||
disableFormat: boolean;
|
||||
valid: boolean;
|
||||
addError(detail: string|ErrorDetail): ValidationError;
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
export declare class ValidationError {
|
||||
constructor(message?: string, instance?: any, schema?: Schema, propertyPath?: any, name?: string, argument?: any);
|
||||
property: string;
|
||||
message: string;
|
||||
schema: string|Schema;
|
||||
instance: any;
|
||||
name: string;
|
||||
argument: any;
|
||||
toString(): string;
|
||||
stack: string;
|
||||
}
|
||||
|
||||
export declare class SchemaError extends Error{
|
||||
constructor(msg: string, schema: Schema);
|
||||
schema: Schema;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export declare function validate(instance: any, schema: any, options?: Options): ValidatorResult
|
||||
|
||||
export interface Schema {
|
||||
id?: string
|
||||
$schema?: string
|
||||
$ref?: string
|
||||
title?: string
|
||||
description?: string
|
||||
multipleOf?: number
|
||||
maximum?: number
|
||||
exclusiveMaximum?: boolean
|
||||
minimum?: number
|
||||
exclusiveMinimum?: boolean
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
pattern?: string | RegExp
|
||||
additionalItems?: boolean | Schema
|
||||
items?: Schema | Schema[]
|
||||
maxItems?: number
|
||||
minItems?: number
|
||||
uniqueItems?: boolean
|
||||
maxProperties?: number
|
||||
minProperties?: number
|
||||
required?: string[] | boolean
|
||||
additionalProperties?: boolean | Schema
|
||||
definitions?: {
|
||||
[name: string]: Schema
|
||||
}
|
||||
properties?: {
|
||||
[name: string]: Schema
|
||||
}
|
||||
patternProperties?: {
|
||||
[name: string]: Schema
|
||||
}
|
||||
dependencies?: {
|
||||
[name: string]: Schema | string[]
|
||||
}
|
||||
'enum'?: any[]
|
||||
type?: string | string[]
|
||||
format?: string
|
||||
allOf?: Schema[]
|
||||
anyOf?: Schema[]
|
||||
oneOf?: Schema[]
|
||||
not?: Schema
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
skipAttributes?: string[];
|
||||
allowUnknownAttributes?: boolean;
|
||||
rewrite?: RewriteFunction;
|
||||
propertyName?: string;
|
||||
base?: string;
|
||||
throwError?: boolean;
|
||||
}
|
||||
|
||||
export interface RewriteFunction {
|
||||
(instance: any, schema: Schema, options: Options, ctx: SchemaContext): any;
|
||||
}
|
||||
|
||||
export interface SchemaContext {
|
||||
schema: Schema;
|
||||
options: Options;
|
||||
propertyPath: string;
|
||||
base: string;
|
||||
schemas: {[base: string]: Schema};
|
||||
}
|
||||
|
||||
export interface CustomFormat {
|
||||
(input: any): boolean;
|
||||
}
|
||||
|
||||
export interface CustomProperty {
|
||||
(instance: any, schema: Schema, options: Options, ctx: SchemaContext): string|ValidatorResult;
|
||||
}
|
||||
|
||||
export interface ErrorDetail {
|
||||
message: string;
|
||||
name: string;
|
||||
argument: string;
|
||||
}
|
||||
14
node_modules/jsonschema/lib/index.js
generated
vendored
Normal file
14
node_modules/jsonschema/lib/index.js
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
var Validator = module.exports.Validator = require('./validator');
|
||||
|
||||
module.exports.ValidatorResult = require('./helpers').ValidatorResult;
|
||||
module.exports.ValidationError = require('./helpers').ValidationError;
|
||||
module.exports.SchemaError = require('./helpers').SchemaError;
|
||||
module.exports.SchemaScanResult = require('./scan').SchemaScanResult;
|
||||
module.exports.scan = require('./scan').scan;
|
||||
|
||||
module.exports.validate = function (instance, schema, options) {
|
||||
var v = new Validator();
|
||||
return v.validate(instance, schema, options);
|
||||
};
|
||||
74
node_modules/jsonschema/lib/scan.js
generated
vendored
Normal file
74
node_modules/jsonschema/lib/scan.js
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
var urilib = require('url');
|
||||
var helpers = require('./helpers');
|
||||
|
||||
module.exports.SchemaScanResult = SchemaScanResult;
|
||||
function SchemaScanResult(found, ref){
|
||||
this.id = found;
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a schema with a certain urn to the Validator instance.
|
||||
* @param string uri
|
||||
* @param object schema
|
||||
* @return {Object}
|
||||
*/
|
||||
module.exports.scan = function scan(base, schema){
|
||||
function scanSchema(baseuri, schema){
|
||||
if(!schema || typeof schema!='object') return;
|
||||
// Mark all referenced schemas so we can tell later which schemas are referred to, but never defined
|
||||
if(schema.$ref){
|
||||
var resolvedUri = urilib.resolve(baseuri, schema.$ref);
|
||||
ref[resolvedUri] = ref[resolvedUri] ? ref[resolvedUri]+1 : 0;
|
||||
return;
|
||||
}
|
||||
var ourBase = schema.id ? urilib.resolve(baseuri, schema.id) : baseuri;
|
||||
if (ourBase) {
|
||||
// If there's no fragment, append an empty one
|
||||
if(ourBase.indexOf('#')<0) ourBase += '#';
|
||||
if(found[ourBase]){
|
||||
if(!helpers.deepCompareStrict(found[ourBase], schema)){
|
||||
throw new Error('Schema <'+schema+'> already exists with different definition');
|
||||
}
|
||||
return found[ourBase];
|
||||
}
|
||||
found[ourBase] = schema;
|
||||
// strip trailing fragment
|
||||
if(ourBase[ourBase.length-1]=='#'){
|
||||
found[ourBase.substring(0, ourBase.length-1)] = schema;
|
||||
}
|
||||
}
|
||||
scanArray(ourBase+'/items', (Array.isArray(schema.items)?schema.items:[schema.items]));
|
||||
scanArray(ourBase+'/extends', (Array.isArray(schema.extends)?schema.extends:[schema.extends]));
|
||||
scanSchema(ourBase+'/additionalItems', schema.additionalItems);
|
||||
scanObject(ourBase+'/properties', schema.properties);
|
||||
scanSchema(ourBase+'/additionalProperties', schema.additionalProperties);
|
||||
scanObject(ourBase+'/definitions', schema.definitions);
|
||||
scanObject(ourBase+'/patternProperties', schema.patternProperties);
|
||||
scanObject(ourBase+'/dependencies', schema.dependencies);
|
||||
scanArray(ourBase+'/disallow', schema.disallow);
|
||||
scanArray(ourBase+'/allOf', schema.allOf);
|
||||
scanArray(ourBase+'/anyOf', schema.anyOf);
|
||||
scanArray(ourBase+'/oneOf', schema.oneOf);
|
||||
scanSchema(ourBase+'/not', schema.not);
|
||||
}
|
||||
function scanArray(baseuri, schemas){
|
||||
if(!Array.isArray(schemas)) return;
|
||||
for(var i=0; i<schemas.length; i++){
|
||||
scanSchema(baseuri+'/'+i, schemas[i]);
|
||||
}
|
||||
}
|
||||
function scanObject(baseuri, schemas){
|
||||
if(!schemas || typeof schemas!='object') return;
|
||||
for(var p in schemas){
|
||||
scanSchema(baseuri+'/'+p, schemas[p]);
|
||||
}
|
||||
}
|
||||
|
||||
var found = {};
|
||||
var ref = {};
|
||||
var schemaUri = base;
|
||||
scanSchema(base, schema);
|
||||
return new SchemaScanResult(found, ref);
|
||||
}
|
||||
320
node_modules/jsonschema/lib/validator.js
generated
vendored
Normal file
320
node_modules/jsonschema/lib/validator.js
generated
vendored
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
'use strict';
|
||||
|
||||
var urilib = require('url');
|
||||
|
||||
var attribute = require('./attribute');
|
||||
var helpers = require('./helpers');
|
||||
var scanSchema = require('./scan').scan;
|
||||
var ValidatorResult = helpers.ValidatorResult;
|
||||
var SchemaError = helpers.SchemaError;
|
||||
var SchemaContext = helpers.SchemaContext;
|
||||
//var anonymousBase = 'vnd.jsonschema:///';
|
||||
var anonymousBase = '/';
|
||||
|
||||
/**
|
||||
* Creates a new Validator object
|
||||
* @name Validator
|
||||
* @constructor
|
||||
*/
|
||||
var Validator = function Validator () {
|
||||
// Allow a validator instance to override global custom formats or to have their
|
||||
// own custom formats.
|
||||
this.customFormats = Object.create(Validator.prototype.customFormats);
|
||||
this.schemas = {};
|
||||
this.unresolvedRefs = [];
|
||||
|
||||
// Use Object.create to make this extensible without Validator instances stepping on each other's toes.
|
||||
this.types = Object.create(types);
|
||||
this.attributes = Object.create(attribute.validators);
|
||||
};
|
||||
|
||||
// Allow formats to be registered globally.
|
||||
Validator.prototype.customFormats = {};
|
||||
|
||||
// Hint at the presence of a property
|
||||
Validator.prototype.schemas = null;
|
||||
Validator.prototype.types = null;
|
||||
Validator.prototype.attributes = null;
|
||||
Validator.prototype.unresolvedRefs = null;
|
||||
|
||||
/**
|
||||
* Adds a schema with a certain urn to the Validator instance.
|
||||
* @param schema
|
||||
* @param urn
|
||||
* @return {Object}
|
||||
*/
|
||||
Validator.prototype.addSchema = function addSchema (schema, base) {
|
||||
var self = this;
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
var scan = scanSchema(base||anonymousBase, schema);
|
||||
var ourUri = base || schema.id;
|
||||
for(var uri in scan.id){
|
||||
this.schemas[uri] = scan.id[uri];
|
||||
}
|
||||
for(var uri in scan.ref){
|
||||
this.unresolvedRefs.push(uri);
|
||||
}
|
||||
this.unresolvedRefs = this.unresolvedRefs.filter(function(uri){
|
||||
return typeof self.schemas[uri]==='undefined';
|
||||
});
|
||||
return this.schemas[ourUri];
|
||||
};
|
||||
|
||||
Validator.prototype.addSubSchemaArray = function addSubSchemaArray(baseuri, schemas) {
|
||||
if(!Array.isArray(schemas)) return;
|
||||
for(var i=0; i<schemas.length; i++){
|
||||
this.addSubSchema(baseuri, schemas[i]);
|
||||
}
|
||||
};
|
||||
|
||||
Validator.prototype.addSubSchemaObject = function addSubSchemaArray(baseuri, schemas) {
|
||||
if(!schemas || typeof schemas!='object') return;
|
||||
for(var p in schemas){
|
||||
this.addSubSchema(baseuri, schemas[p]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets all the schemas of the Validator instance.
|
||||
* @param schemas
|
||||
*/
|
||||
Validator.prototype.setSchemas = function setSchemas (schemas) {
|
||||
this.schemas = schemas;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the schema of a certain urn
|
||||
* @param urn
|
||||
*/
|
||||
Validator.prototype.getSchema = function getSchema (urn) {
|
||||
return this.schemas[urn];
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates instance against the provided schema
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param [options]
|
||||
* @param [ctx]
|
||||
* @return {Array}
|
||||
*/
|
||||
Validator.prototype.validate = function validate (instance, schema, options, ctx) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
var propertyName = options.propertyName || 'instance';
|
||||
// This will work so long as the function at uri.resolve() will resolve a relative URI to a relative URI
|
||||
var base = urilib.resolve(options.base||anonymousBase, schema.id||'');
|
||||
if(!ctx){
|
||||
ctx = new SchemaContext(schema, options, propertyName, base, Object.create(this.schemas));
|
||||
if (!ctx.schemas[base]) {
|
||||
ctx.schemas[base] = schema;
|
||||
}
|
||||
var found = scanSchema(base, schema);
|
||||
for(var n in found.id){
|
||||
var sch = found.id[n];
|
||||
ctx.schemas[n] = sch;
|
||||
}
|
||||
}
|
||||
if (schema) {
|
||||
var result = this.validateSchema(instance, schema, options, ctx);
|
||||
if (!result) {
|
||||
throw new Error('Result undefined');
|
||||
}
|
||||
return result;
|
||||
}
|
||||
throw new SchemaError('no schema specified', schema);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param Object schema
|
||||
* @return mixed schema uri or false
|
||||
*/
|
||||
function shouldResolve(schema) {
|
||||
var ref = (typeof schema === 'string') ? schema : schema.$ref;
|
||||
if (typeof ref=='string') return ref;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an instance against the schema (the actual work horse)
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @private
|
||||
* @return {ValidatorResult}
|
||||
*/
|
||||
Validator.prototype.validateSchema = function validateSchema (instance, schema, options, ctx) {
|
||||
var result = new ValidatorResult(instance, schema, options, ctx);
|
||||
|
||||
// Support for the true/false schemas
|
||||
if(typeof schema==='boolean') {
|
||||
if(schema===true){
|
||||
// `true` is always valid
|
||||
schema = {};
|
||||
}else if(schema===false){
|
||||
// `false` is always invalid
|
||||
schema = {type: []};
|
||||
}
|
||||
}else if(!schema){
|
||||
// This might be a string
|
||||
throw new Error("schema is undefined");
|
||||
}
|
||||
|
||||
if (schema['extends']) {
|
||||
if (Array.isArray(schema['extends'])) {
|
||||
var schemaobj = {schema: schema, ctx: ctx};
|
||||
schema['extends'].forEach(this.schemaTraverser.bind(this, schemaobj));
|
||||
schema = schemaobj.schema;
|
||||
schemaobj.schema = null;
|
||||
schemaobj.ctx = null;
|
||||
schemaobj = null;
|
||||
} else {
|
||||
schema = helpers.deepMerge(schema, this.superResolve(schema['extends'], ctx));
|
||||
}
|
||||
}
|
||||
|
||||
// If passed a string argument, load that schema URI
|
||||
var switchSchema;
|
||||
if (switchSchema = shouldResolve(schema)) {
|
||||
var resolved = this.resolve(schema, switchSchema, ctx);
|
||||
var subctx = new SchemaContext(resolved.subschema, options, ctx.propertyPath, resolved.switchSchema, ctx.schemas);
|
||||
return this.validateSchema(instance, resolved.subschema, options, subctx);
|
||||
}
|
||||
|
||||
var skipAttributes = options && options.skipAttributes || [];
|
||||
// Validate each schema attribute against the instance
|
||||
for (var key in schema) {
|
||||
if (!attribute.ignoreProperties[key] && skipAttributes.indexOf(key) < 0) {
|
||||
var validatorErr = null;
|
||||
var validator = this.attributes[key];
|
||||
if (validator) {
|
||||
validatorErr = validator.call(this, instance, schema, options, ctx);
|
||||
} else if (options.allowUnknownAttributes === false) {
|
||||
// This represents an error with the schema itself, not an invalid instance
|
||||
throw new SchemaError("Unsupported attribute: " + key, schema);
|
||||
}
|
||||
if (validatorErr) {
|
||||
result.importErrors(validatorErr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.rewrite == 'function') {
|
||||
var value = options.rewrite.call(this, instance, schema, options, ctx);
|
||||
result.instance = value;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param Object schema
|
||||
* @param SchemaContext ctx
|
||||
* @returns Object schema or resolved schema
|
||||
*/
|
||||
Validator.prototype.schemaTraverser = function schemaTraverser (schemaobj, s) {
|
||||
schemaobj.schema = helpers.deepMerge(schemaobj.schema, this.superResolve(s, schemaobj.ctx));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param Object schema
|
||||
* @param SchemaContext ctx
|
||||
* @returns Object schema or resolved schema
|
||||
*/
|
||||
Validator.prototype.superResolve = function superResolve (schema, ctx) {
|
||||
var ref;
|
||||
if(ref = shouldResolve(schema)) {
|
||||
return this.resolve(schema, ref, ctx).subschema;
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param Object schema
|
||||
* @param Object switchSchema
|
||||
* @param SchemaContext ctx
|
||||
* @return Object resolved schemas {subschema:String, switchSchema: String}
|
||||
* @throws SchemaError
|
||||
*/
|
||||
Validator.prototype.resolve = function resolve (schema, switchSchema, ctx) {
|
||||
switchSchema = ctx.resolve(switchSchema);
|
||||
// First see if the schema exists under the provided URI
|
||||
if (ctx.schemas[switchSchema]) {
|
||||
return {subschema: ctx.schemas[switchSchema], switchSchema: switchSchema};
|
||||
}
|
||||
// Else try walking the property pointer
|
||||
var parsed = urilib.parse(switchSchema);
|
||||
var fragment = parsed && parsed.hash;
|
||||
var document = fragment && fragment.length && switchSchema.substr(0, switchSchema.length - fragment.length);
|
||||
if (!document || !ctx.schemas[document]) {
|
||||
throw new SchemaError("no such schema <" + switchSchema + ">", schema);
|
||||
}
|
||||
var subschema = helpers.objectGetPath(ctx.schemas[document], fragment.substr(1));
|
||||
if(subschema===undefined){
|
||||
throw new SchemaError("no such schema " + fragment + " located in <" + document + ">", schema);
|
||||
}
|
||||
return {subschema: subschema, switchSchema: switchSchema};
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests whether the instance if of a certain type.
|
||||
* @private
|
||||
* @param instance
|
||||
* @param schema
|
||||
* @param options
|
||||
* @param ctx
|
||||
* @param type
|
||||
* @return {boolean}
|
||||
*/
|
||||
Validator.prototype.testType = function validateType (instance, schema, options, ctx, type) {
|
||||
if (typeof this.types[type] == 'function') {
|
||||
return this.types[type].call(this, instance);
|
||||
}
|
||||
if (type && typeof type == 'object') {
|
||||
var res = this.validateSchema(instance, type, options, ctx);
|
||||
return res === undefined || !(res && res.errors.length);
|
||||
}
|
||||
// Undefined or properties not on the list are acceptable, same as not being defined
|
||||
return true;
|
||||
};
|
||||
|
||||
var types = Validator.prototype.types = {};
|
||||
types.string = function testString (instance) {
|
||||
return typeof instance == 'string';
|
||||
};
|
||||
types.number = function testNumber (instance) {
|
||||
// isFinite returns false for NaN, Infinity, and -Infinity
|
||||
return typeof instance == 'number' && isFinite(instance);
|
||||
};
|
||||
types.integer = function testInteger (instance) {
|
||||
return (typeof instance == 'number') && instance % 1 === 0;
|
||||
};
|
||||
types.boolean = function testBoolean (instance) {
|
||||
return typeof instance == 'boolean';
|
||||
};
|
||||
types.array = function testArray (instance) {
|
||||
return Array.isArray(instance);
|
||||
};
|
||||
types['null'] = function testNull (instance) {
|
||||
return instance === null;
|
||||
};
|
||||
types.date = function testDate (instance) {
|
||||
return instance instanceof Date;
|
||||
};
|
||||
types.any = function testAny (instance) {
|
||||
return true;
|
||||
};
|
||||
types.object = function testObject (instance) {
|
||||
// TODO: fix this - see #15
|
||||
return instance && (typeof instance === 'object') && !(Array.isArray(instance)) && !(instance instanceof Date);
|
||||
};
|
||||
|
||||
module.exports = Validator;
|
||||
38
node_modules/jsonschema/package.json
generated
vendored
Normal file
38
node_modules/jsonschema/package.json
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"author": "Tom de Grunt <tom@degrunt.nl>",
|
||||
"name": "jsonschema",
|
||||
"version": "1.2.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Austin Wright"
|
||||
}
|
||||
],
|
||||
"main": "./lib",
|
||||
"typings": "./lib/index.d.ts",
|
||||
"devDependencies": {
|
||||
"json-metaschema": "^1.2.0",
|
||||
"mocha": "~3",
|
||||
"chai": "~1.5.0"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"keywords": [
|
||||
"json",
|
||||
"schema",
|
||||
"jsonschema",
|
||||
"validator",
|
||||
"validation"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/tdegrunt/jsonschema.git"
|
||||
},
|
||||
"description": "A fast and easy to use JSON Schema validator",
|
||||
"scripts": {
|
||||
"test": "./node_modules/.bin/mocha -R spec"
|
||||
}
|
||||
}
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
|
|
@ -1658,6 +1658,11 @@
|
|||
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonschema": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.6.tgz",
|
||||
"integrity": "sha512-SqhURKZG07JyKKeo/ir24QnS4/BV7a6gQy93bUSe4lUdNp0QNpIz2c9elWJQ9dpc5cQYY6cvCzgRwy0MQCLyqA=="
|
||||
},
|
||||
"keyv": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
"file-url": "^3.0.0",
|
||||
"fs": "0.0.1-security",
|
||||
"js-yaml": "^3.13.1",
|
||||
"jsonschema": "1.2.6",
|
||||
"long": "^4.0.0",
|
||||
"md5": "^2.2.1",
|
||||
"path": "^0.12.7",
|
||||
|
|
|
|||
3370
src/sarif_v2.1.0_schema.json
Normal file
3370
src/sarif_v2.1.0_schema.json
Normal file
File diff suppressed because it is too large
Load diff
17
src/testdata/invalid-sarif.sarif
vendored
Normal file
17
src/testdata/invalid-sarif.sarif
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "LGTM.com",
|
||||
"organization": "Semmle",
|
||||
"version": "1.24.0-SNAPSHOT",
|
||||
"rules": []
|
||||
}
|
||||
},
|
||||
"results": 42
|
||||
}
|
||||
]
|
||||
}
|
||||
239
src/testdata/valid-sarif.sarif
vendored
Normal file
239
src/testdata/valid-sarif.sarif
vendored
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "LGTM.com",
|
||||
"organization": "Semmle",
|
||||
"version": "1.24.0-SNAPSHOT",
|
||||
"rules": [{
|
||||
"id": "js/unused-local-variable",
|
||||
"name": "js/unused-local-variable",
|
||||
"shortDescription": {
|
||||
"text": "Unused variable, import, function or class"
|
||||
},
|
||||
"fullDescription": {
|
||||
"text": "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully."
|
||||
},
|
||||
"defaultConfiguration": {
|
||||
"level": "note"
|
||||
},
|
||||
"properties": {
|
||||
"tags": ["maintainability"],
|
||||
"kind": "problem",
|
||||
"precision": "very-high",
|
||||
"name": "Unused variable, import, function or class",
|
||||
"description": "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.",
|
||||
"id": "js/unused-local-variable",
|
||||
"problem.severity": "recommendation"
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
"results": [{
|
||||
"ruleId": "js/unused-local-variable",
|
||||
"ruleIndex": 0,
|
||||
"message": {
|
||||
"text": "Unused variable foo."
|
||||
},
|
||||
"locations": [{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "main.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 0
|
||||
},
|
||||
"region": {
|
||||
"startLine": 2,
|
||||
"startColumn": 7,
|
||||
"endColumn": 10
|
||||
}
|
||||
}
|
||||
}],
|
||||
"partialFingerprints": {
|
||||
"primaryLocationLineHash": "39fa2ee980eb94b0:1",
|
||||
"primaryLocationStartColumnFingerprint": "4"
|
||||
}
|
||||
}],
|
||||
"columnKind": "utf16CodeUnits",
|
||||
"properties": {
|
||||
"semmle.formatSpecifier": "2.1.0",
|
||||
"semmle.sourceLanguage": "java"
|
||||
}
|
||||
},
|
||||
{
|
||||
"tool" : {
|
||||
"driver" : {
|
||||
"name" : "CodeQL command-line toolchain",
|
||||
"organization" : "GitHub",
|
||||
"semanticVersion" : "2.0.0",
|
||||
"rules" : [ {
|
||||
"id" : "js/unused-local-variable",
|
||||
"name" : "js/unused-local-variable",
|
||||
"shortDescription" : {
|
||||
"text" : "Unused variable, import, function or class"
|
||||
},
|
||||
"fullDescription" : {
|
||||
"text" : "Unused variables, imports, functions or classes may be a symptom of a bug and should be examined carefully."
|
||||
},
|
||||
"defaultConfiguration" : {
|
||||
"level": "note"
|
||||
},
|
||||
"properties" : {
|
||||
"tags" : [ "maintainability" ],
|
||||
"kind" : "problem",
|
||||
"precision" : "very-high",
|
||||
"name" : "Unused variable, import, function or class",
|
||||
"description" : "Unused variables, imports, functions or classes may be a symptom of a bug\n and should be examined carefully.",
|
||||
"id" : "js/unused-local-variable",
|
||||
"problem.severity" : "recommendation"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "js/inconsistent-use-of-new",
|
||||
"name": "js/inconsistent-use-of-new",
|
||||
"shortDescription": {
|
||||
"text": "Inconsistent use of 'new'"
|
||||
},
|
||||
"fullDescription": {
|
||||
"text": "If a function is intended to be a constructor, it should always be invoked with 'new'. Otherwise, it should always be invoked as a normal function, that is, without 'new'."
|
||||
},
|
||||
"defaultConfiguration": {
|
||||
"level": "note"
|
||||
},
|
||||
"properties": {
|
||||
"tags": [
|
||||
"reliability",
|
||||
"correctness",
|
||||
"language-features"
|
||||
],
|
||||
"kind": "problem",
|
||||
"precision": "very-high",
|
||||
"problem.severity": "warning"
|
||||
}
|
||||
} ]
|
||||
}
|
||||
},
|
||||
"artifacts" : [ {
|
||||
"location" : {
|
||||
"uri" : "main.js",
|
||||
"uriBaseId" : "%SRCROOT%",
|
||||
"index" : 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/promiseUtils.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/LiveQueryClient.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"uri": "src/ParseObject.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 3
|
||||
}
|
||||
} ],
|
||||
"results" : [ {
|
||||
"ruleId" : "js/unused-local-variable",
|
||||
"ruleIndex" : 0,
|
||||
"message" : {
|
||||
"text" : "Unused variable foo."
|
||||
},
|
||||
"locations" : [ {
|
||||
"physicalLocation" : {
|
||||
"artifactLocation" : {
|
||||
"uri" : "main.js",
|
||||
"uriBaseId" : "%SRCROOT%",
|
||||
"index" : 0
|
||||
},
|
||||
"region" : {
|
||||
"startLine" : 2,
|
||||
"startColumn" : 7,
|
||||
"endColumn" : 10
|
||||
}
|
||||
}
|
||||
} ],
|
||||
"partialFingerprints" : {
|
||||
"primaryLocationLineHash" : "39fa2ee980eb94b0:1",
|
||||
"primaryLocationStartColumnFingerprint" : "4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"ruleId": "js/inconsistent-use-of-new",
|
||||
"ruleIndex": 1,
|
||||
"message": {
|
||||
"text": "Function resolvingPromise is sometimes invoked as a constructor (for example [here](1)), and sometimes as a normal function (for example [here](2))."
|
||||
},
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/promiseUtils.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 1
|
||||
},
|
||||
"region": {
|
||||
"startLine": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"partialFingerprints": {
|
||||
"primaryLocationLineHash": "5061c3315a741b7d:1",
|
||||
"primaryLocationStartColumnFingerprint": "7"
|
||||
},
|
||||
"relatedLocations": [
|
||||
{
|
||||
"id": 1,
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/ParseObject.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 3
|
||||
},
|
||||
"region": {
|
||||
"startLine": 2281,
|
||||
"startColumn": 33,
|
||||
"endColumn": 55
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"text": "here"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": "src/LiveQueryClient.js",
|
||||
"uriBaseId": "%SRCROOT%",
|
||||
"index": 2
|
||||
},
|
||||
"region": {
|
||||
"startLine": 166
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"text": "here"
|
||||
}
|
||||
}
|
||||
]
|
||||
} ],
|
||||
"newlineSequences" : [ "\r\n", "\n", "
", "
" ],
|
||||
"columnKind" : "utf16CodeUnits",
|
||||
"properties" : {
|
||||
"semmle.formatSpecifier" : "sarif-latest"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
15
src/upload-lib.test.ts
Normal file
15
src/upload-lib.test.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import test from 'ava';
|
||||
|
||||
import * as uploadLib from './upload-lib';
|
||||
|
||||
test('validateSarifFileSchema - valid', t => {
|
||||
const inputFile = __dirname + '/../src/testdata/valid-sarif.sarif';
|
||||
const errors = uploadLib.validateSarifFileSchema(inputFile);
|
||||
t.deepEqual(errors, []);
|
||||
});
|
||||
|
||||
test('validateSarifFileSchema - invalid', t => {
|
||||
const inputFile = __dirname + '/../src/testdata/invalid-sarif.sarif';
|
||||
const errors = uploadLib.validateSarifFileSchema(inputFile);
|
||||
t.notDeepEqual(errors, []);
|
||||
});
|
||||
|
|
@ -3,6 +3,7 @@ import * as http from '@actions/http-client';
|
|||
import * as auth from '@actions/http-client/auth';
|
||||
import fileUrl from 'file-url';
|
||||
import * as fs from 'fs';
|
||||
import * as jsonschema from 'jsonschema';
|
||||
import * as path from 'path';
|
||||
import zlib from 'zlib';
|
||||
|
||||
|
|
@ -123,12 +124,29 @@ export function countResultsInSarif(sarif: string): number {
|
|||
return numResults;
|
||||
}
|
||||
|
||||
// Validates that the given file path refers to a valid SARIF file.
|
||||
// Returns a non-empty list of error message if the file is invalid,
|
||||
// otherwise returns the empty list if the file is valid.
|
||||
export function validateSarifFileSchema(sarifFilePath: string): string[] {
|
||||
const sarif = JSON.parse(fs.readFileSync(sarifFilePath, 'utf8'));
|
||||
const schema = JSON.parse(fs.readFileSync(__dirname + '/../src/sarif_v2.1.0_schema.json', 'utf8'));
|
||||
|
||||
const result = new jsonschema.Validator().validate(sarif, schema);
|
||||
if (result.valid) {
|
||||
return [];
|
||||
} else {
|
||||
return result.errors.map(e => e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Uploads the given set of sarif files.
|
||||
// Returns true iff the upload occurred and succeeded
|
||||
async function uploadFiles(sarifFiles: string[]): Promise<boolean> {
|
||||
core.startGroup("Uploading results");
|
||||
let succeeded = false;
|
||||
try {
|
||||
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||
|
||||
const sentinelEnvVar = "CODEQL_UPLOAD_SARIF";
|
||||
if (process.env[sentinelEnvVar]) {
|
||||
core.error("Aborting upload: only one run of the codeql/analyze or codeql/upload-sarif actions is allowed per job");
|
||||
|
|
@ -136,6 +154,15 @@ async function uploadFiles(sarifFiles: string[]): Promise<boolean> {
|
|||
}
|
||||
core.exportVariable(sentinelEnvVar, sentinelEnvVar);
|
||||
|
||||
// Validate that the files we were asked to upload are all valid SARIF files
|
||||
for (const file of sarifFiles) {
|
||||
const errors = validateSarifFileSchema(file);
|
||||
if (errors.length > 0) {
|
||||
core.setFailed("Unable to upload \"" + file + "\" as it is not valid SARIF:\n" + errors.join("\n"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const commitOid = util.getRequiredEnvParam('GITHUB_SHA');
|
||||
const workflowRunIDStr = util.getRequiredEnvParam('GITHUB_RUN_ID');
|
||||
const ref = util.getRef();
|
||||
|
|
@ -143,7 +170,6 @@ async function uploadFiles(sarifFiles: string[]): Promise<boolean> {
|
|||
const analysisName = util.getRequiredEnvParam('GITHUB_WORKFLOW');
|
||||
const startedAt = process.env[sharedEnv.CODEQL_ACTION_STARTED_AT];
|
||||
|
||||
core.info("Uploading sarif files: " + JSON.stringify(sarifFiles));
|
||||
let sarifPayload = combineSarifFiles(sarifFiles);
|
||||
sarifPayload = fingerprints.addFingerprints(sarifPayload);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue