Comparing version 0.1.1 to 0.1.2
{ | ||
"name": "grunt-tv4", | ||
"description": "Use tv4 to validate files against json-schema draft v04", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"homepage": "https://github.com/Bartvds/grunt-tv4", | ||
@@ -37,4 +37,5 @@ "author": { | ||
"dependencies": { | ||
"tv4": "~1.0.3", | ||
"jsonpointer.js": "~0.3.0" | ||
"tv4": "~1.0.5", | ||
"jsonpointer.js": "~0.3.0", | ||
"request": "~2.25.0" | ||
}, | ||
@@ -45,3 +46,4 @@ "devDependencies": { | ||
"grunt-cli": "~0.1", | ||
"grunt": "~0.4.1" | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-connect": "~0.3.0" | ||
}, | ||
@@ -48,0 +50,0 @@ "peerDependencies": { |
@@ -13,3 +13,3 @@ # grunt-tv4 | ||
```shell | ||
npm install grunt-tv4 --save-dev | ||
$ npm install grunt-tv4 --save-dev | ||
``` | ||
@@ -28,4 +28,3 @@ | ||
* Uses [Tiny Validator tv4 ](https://github.com/geraintluff/tv4) so schemas must conform to [json-schema draft v04](http://json-schema.org/documentation.html), | ||
* Currently only supports the synchronous validation mode. | ||
* Asynchronous support should will be added as soon as I got time or a use-case. | ||
* As of version 0.1.2 there is support for automatically loading remote references by URI. See the [JSON Schema](http://json-schema.org/) documentation on how to use `$ref`. | ||
@@ -37,5 +36,8 @@ ### Default Options | ||
tv4: { | ||
//specify single schema's and multiple data to validate | ||
files: { | ||
'schema/format.json': ['data/alpha.json', 'data/beta.json'] | ||
myTarget: { | ||
//specify single schemas and multiple data to validate | ||
files: { | ||
'schema/format.json': ['data/alpha.json', 'data/beta.json'], | ||
'http://example.com/schema/v1': ['data/alpha.json', 'data/beta.json'] | ||
} | ||
} | ||
@@ -46,3 +48,3 @@ } | ||
### Custom Options | ||
### Advanced Options | ||
@@ -52,5 +54,8 @@ ```js | ||
tv4: { | ||
//use global options | ||
options: { | ||
//show multiple errors per file | ||
multi: true | ||
//show multiple errors per file (off by default) | ||
multi: true, | ||
//create a new tv4 instance for every target (off by default) | ||
fresh: true, | ||
//pre register extra schemas by URI | ||
@@ -61,8 +66,10 @@ schemas: { | ||
} | ||
}, | ||
myTarget: { | ||
files: { | ||
//use glob and other standard selector options | ||
'schema/map.json': ['data/map_*.json'], | ||
'http://example.com/schema/v1': ['**/lib_*.json'] | ||
} | ||
} | ||
files: { | ||
//use glob and other standard selector options | ||
'schema/map.json': ['data/map_*.json'], | ||
'schema/library.json': ['**/lib_*.json'] | ||
} | ||
} | ||
@@ -74,2 +81,3 @@ }) | ||
* 0.1.2 - Support for loading remote references (JSON Schema's `$ref`). | ||
* 0.1.1 - Bugfixes and improved reporting | ||
@@ -81,4 +89,1 @@ * 0.1.0 - First release with synchronous validation | ||
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). | ||
## Release History | ||
_(Nothing yet)_ |
320
tasks/tv4.js
@@ -43,79 +43,82 @@ /* | ||
}; | ||
grunt.registerMultiTask('tv4', 'Your task description goes here.', function () { | ||
var options = this.options({ | ||
schemas: {}, | ||
multi:false | ||
}); | ||
var tv4 = require('tv4').tv4; | ||
var jsonpointer = require('jsonpointer.js'); | ||
var pluralise = function (str, num) { | ||
if (num === 1) { | ||
return str; | ||
} | ||
return str + 's'; | ||
}; | ||
var cache = {}; | ||
var fileCount = 0; | ||
var fail = []; | ||
var pass = []; | ||
//TODO decide to tv4 outside task for inter-target caching? | ||
var taskTv4 = require('tv4').tv4.freshApi(); | ||
var jsonpointer = require('jsonpointer.js'); | ||
var request = require('request'); | ||
grunt.util._.each(options.schemas, function (schema, url) { | ||
grunt.log.writeln('schema add: ' + url); | ||
tv4.addSchema(url, schema); | ||
//load a single schema by uri | ||
var loadSchemaFile = function (context, uri, callback) { | ||
if (!/^https?:/.test(uri)) { | ||
callback('not a http uri: ' + uri); | ||
} | ||
grunt.log.writeln('load: ' + uri); | ||
request.get({url: uri, timeout: context.options.timeout}, function (err, res) { | ||
//grunt.log.writeln('done: ' + uri); | ||
if (err) { | ||
return callback('http schema at: ' + uri); | ||
} | ||
if (!res || !res.body) { | ||
return callback('no rsponse at: '.red + uri); | ||
} | ||
if (res.statusCode < 200 || res.statusCode >= 400) { | ||
return callback('http bad response code: ' + res.statusCode + ' on '.red + uri); | ||
} | ||
var schema; | ||
try { | ||
schema = JSON.parse(res.body); | ||
} | ||
catch (e) { | ||
return callback('invalid json at: '.red + uri + ':\n' + e + '\n' + res.body); | ||
} | ||
callback(null, schema); | ||
}); | ||
}; | ||
grunt.util._.each(this.files, function (f) { | ||
grunt.util._.each(f.src, function (filepath) { | ||
fileCount++; | ||
if (!grunt.file.exists(filepath)) { | ||
grunt.log.warn('data not found: '.red + filepath); | ||
return; | ||
} | ||
if (!grunt.file.isFile(filepath)) { | ||
grunt.log.fail('data not a file: '.red + filepath); | ||
return; | ||
} | ||
var schema; | ||
if (cache.hasOwnProperty(f.dest)) { | ||
//grunt.log.writeln('schema cache: ' + f.dest); | ||
schema = cache[f.dest]; | ||
} | ||
else { | ||
if (!grunt.file.exists(f.dest)) { | ||
grunt.log.fail('schema not found: '.red + f.dest); | ||
return; | ||
} | ||
if (!grunt.file.isFile(f.dest)) { | ||
grunt.log.fail('schema not a file: '.red + f.dest); | ||
return; | ||
} | ||
schema = grunt.file.readJSON(f.dest); | ||
schema.path = f.dest; | ||
cache[f.dest] = schema; | ||
//grunt.log.writeln('schema load: ' + f.dest); | ||
} | ||
//load and add batch of schema by uri, repeat until all missing are solved | ||
var loadSchemaList = function (context, uris, callback) { | ||
var data = grunt.file.readJSON(filepath); | ||
var result; | ||
if (options.multi){ | ||
result = tv4.validateMultiple(data, schema); | ||
var step = function () { | ||
if (uris.length === 0) { | ||
return callback(); | ||
} | ||
var uri = uris[0]; | ||
loadSchemaFile(context, uri, function (err, schema) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
else { | ||
result = tv4.validateResult(data, schema); | ||
} | ||
context.tv4.addSchema(uri, schema); | ||
uris = context.tv4.getMissingUris(); | ||
step(); | ||
}); | ||
}; | ||
step(); | ||
}; | ||
result.data = data; | ||
result.path = filepath; | ||
result.schema = schema; | ||
//print all results and finish task | ||
var finaliseTask = function (err, context) { | ||
if (err) { | ||
grunt.log.writeln(); | ||
grunt.log.warn('error: '.red + err); | ||
grunt.log.writeln(); | ||
context.done(false); | ||
return; | ||
} | ||
if (!result.valid || result.missing.length > 0) { | ||
fail.push(result); | ||
} | ||
else { | ||
pass.push(result); | ||
grunt.log.writeln('pass: '.green + filepath); | ||
} | ||
}); | ||
}); | ||
//subroutine | ||
var printError = function (error, data, schema, indent) { | ||
//grunt.log.writeln(util.inspect(error, false, 4)); | ||
var value = jsonpointer.get(data, error.dataPath); | ||
var schemaValue = jsonpointer.get(schema, error.schemaPath); | ||
grunt.log.writeln(indent + error.message); | ||
grunt.log.writeln(indent + error.message.yellow); | ||
grunt.log.writeln(indent + indent + error.dataPath); | ||
@@ -127,20 +130,25 @@ grunt.log.writeln(indent + indent + '-> value: ' + valueType(value) + ' -> ' + valueStrim(value)); | ||
/*grunt.util._.each(error.subErrors, function (error) { | ||
printError(error, data, schema, indent + indent + indent + indent); | ||
});*/ | ||
printError(error, data, schema, indent + indent + indent + indent); | ||
});*/ | ||
}; | ||
if (fail.length > 0) { | ||
grunt.util._.each(fail, function (result) { | ||
grunt.log.writeln('fail: '.red + result.path + ' != '.red + result.schema.path); | ||
if (result.errors) { | ||
grunt.util._.each(result.errors, function (error) { | ||
printError(error, result.data, result.schema, ' '); | ||
//got some failures: print log and fail the task | ||
if (context.fail.length > 0) { | ||
grunt.log.writeln(); | ||
grunt.log.writeln('-> tv4 reporting ' + (context.fail.length + ' ' + pluralise('invalid file', context.fail.length) + ':').red); | ||
grunt.log.writeln(); | ||
grunt.util._.each(context.fail, function (file) { | ||
grunt.log.writeln('fail: '.red + file.path + ' !== '.red + (file.schemaSource)); | ||
if (file.result.errors) { | ||
grunt.util._.each(file.result.errors, function (error) { | ||
printError(error, file.data, file.schema, ' '); | ||
}); | ||
} | ||
else if (result.error) { | ||
printError(result.error, result.data, result.schema, ' '); | ||
else if (file.result.error) { | ||
printError(file.result.error, file.data, file.schema, ' '); | ||
} | ||
if (result.missing.length > 0) { | ||
grunt.log.writeln('missing schemas:'.yellow); | ||
grunt.util._.each(result.missing, function (missing) { | ||
if (file.result.missing.length > 0) { | ||
grunt.log.writeln('missing schemas: '.yellow); | ||
grunt.util._.each(file.result.missing, function (missing) { | ||
grunt.log.writeln(missing); | ||
@@ -151,8 +159,158 @@ }); | ||
grunt.log.fail('-> '.white + 'tv4 ' + ('validated ' + pass.length).green + ', ' + ('failed ' + fail.length).red); | ||
return false; | ||
grunt.log.writeln(); | ||
grunt.log.warn('tv4 ' + ('validated ' + context.pass.length).green + ', ' + ('failed ' + context.fail.length).red + ' of ' + context.fileCount + ' ' + pluralise('file', context.fileCount) + '\n'); | ||
context.done(false); | ||
} | ||
grunt.log.ok('tv4 ' + ('validated ' + pass.length).green); | ||
else { | ||
grunt.log.writeln(); | ||
grunt.log.ok('tv4 ' + ('validated ' + context.pass.length).green + ' of ' + context.fileCount + ' ' + pluralise('file', context.fileCount) + '\n'); | ||
context.done(); | ||
} | ||
}; | ||
//supports automatic lazy loading | ||
var recursiveTest = function (context, file, callback) { | ||
grunt.log.writeln('test: ' + file.path); | ||
if (context.options.multi) { | ||
file.result = context.tv4.validateMultiple(file.data, file.schema); | ||
} | ||
else { | ||
file.result = context.tv4.validateResult(file.data, file.schema); | ||
} | ||
if (!file.result.valid) { | ||
context.fail.push(file); | ||
grunt.log.writeln('fail: '.red + file.path); | ||
return callback(); | ||
} | ||
if (file.result.missing.length === 0) { | ||
context.pass.push(file); | ||
grunt.log.writeln('pass: '.green + file.path); | ||
return callback(); | ||
} | ||
loadSchemaList(context, file.result.missing, function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
//check again | ||
recursiveTest(context, file, callback); | ||
}); | ||
}; | ||
//validate single file | ||
var validateFile = function (context, file, callback) { | ||
grunt.log.writeln('-> ' + file.path.cyan); | ||
file.data = grunt.file.readJSON(file.path); | ||
if (/^https?:/.test(file.schemaSource)) { | ||
grunt.log.writeln('http: ' + file.schemaSource); | ||
//known from previous sessions? | ||
var schema = context.tv4.getSchema(file.schemaSource); | ||
if (schema) { | ||
grunt.log.writeln('have: ' + file.schemaSource); | ||
file.schema = schema; | ||
recursiveTest(context, file, callback); | ||
} | ||
else { | ||
loadSchemaFile(context, file.schemaSource, function (err, schema) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
file.schema = schema; | ||
context.tv4.addSchema(file.schemaSource, schema); | ||
//pre fetch (saves a validation round) | ||
loadSchemaList(context, context.tv4.getMissingUris(), function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
recursiveTest(context, file, callback); | ||
}); | ||
}); | ||
} | ||
} | ||
else { | ||
grunt.log.writeln('file: ' + file.schemaSource); | ||
if (!grunt.file.exists(file.schemaSource)) { | ||
return callback('not found: '.red + file.schemaSource); | ||
} | ||
if (!grunt.file.isFile(file.schemaSource)) { | ||
return callback('not a file: '.red + file.schemaSource); | ||
} | ||
file.schema = grunt.file.readJSON(file.schemaSource); | ||
context.tv4.addSchema(file.schema.id || "", file.schema); | ||
//pre fetch (saves a validation round) | ||
loadSchemaList(context, context.tv4.getMissingUris(), function (err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
recursiveTest(context, file, callback); | ||
}); | ||
} | ||
}; | ||
grunt.registerMultiTask('tv4', 'Your task description goes here.', function () { | ||
var context = {}; | ||
context.done = this.async(); | ||
context.fileCount = 0; | ||
context.fail = []; | ||
context.pass = []; | ||
context.files = []; | ||
context.tv4 = taskTv4; | ||
//import options | ||
context.options = this.options({ | ||
schemas: {}, | ||
timeout: 5000, | ||
fresh: false, | ||
multi: false | ||
}); | ||
if (context.options.fresh) { | ||
context.tv4 = taskTv4.freshApi(); | ||
} | ||
grunt.util._.each(context.options.schemas, function (schema, uri) { | ||
context.tv4.addSchema(uri, schema); | ||
}); | ||
//flatten list for sanity | ||
grunt.util._.each(this.files, function (f) { | ||
grunt.util._.each(f.src, function (filePath) { | ||
context.fileCount++; | ||
context.files.push({path: filePath, data: null, schema: null, schemaSource: f.dest}); | ||
}); | ||
}); | ||
if (context.fileCount === 0) { | ||
grunt.log.warn('zero input files selected'); | ||
context.done(false); | ||
return; | ||
} | ||
//start the flow | ||
loadSchemaList(context, context.tv4.getMissingUris(), function (err) { | ||
if (err) { | ||
return finaliseTask(err, context); | ||
} | ||
grunt.util.async.forEachSeries(context.files, function (file, callback) { | ||
validateFile(context, file, callback); | ||
}, function(err) { | ||
finaliseTask(err, context); | ||
}); | ||
}); | ||
}); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
13856
270
82
4
5
1
+ Addedrequest@~2.25.0
+ Addedasn1@0.1.11(transitive)
+ Addedassert-plus@0.1.5(transitive)
+ Addedasync@0.9.2(transitive)
+ Addedaws-sign@0.3.0(transitive)
+ Addedboom@0.4.2(transitive)
+ Addedcombined-stream@0.0.7(transitive)
+ Addedcookie-jar@0.3.0(transitive)
+ Addedcryptiles@0.2.2(transitive)
+ Addedctype@0.5.3(transitive)
+ Addeddelayed-stream@0.0.5(transitive)
+ Addedforever-agent@0.5.2(transitive)
+ Addedform-data@0.1.4(transitive)
+ Addedhawk@1.0.0(transitive)
+ Addedhoek@0.9.1(transitive)
+ Addedhttp-signature@0.10.1(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedmime@1.2.11(transitive)
+ Addednode-uuid@1.4.8(transitive)
+ Addedoauth-sign@0.3.0(transitive)
+ Addedqs@0.6.6(transitive)
+ Addedrequest@2.25.0(transitive)
+ Addedsntp@0.2.4(transitive)
+ Addedtunnel-agent@0.3.0(transitive)
Updatedtv4@~1.0.5