Socket
Socket
Sign inDemoInstall

grunt-tv4

Package Overview
Dependencies
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grunt-tv4 - npm Package Compare versions

Comparing version 0.1.1 to 0.1.2

10

package.json
{
"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": {

39

README.md

@@ -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)_

@@ -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);
});
});
});
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc