Skip to content
This repository has been archived by the owner on Jun 5, 2020. It is now read-only.

Commit

Permalink
Merge pull request #1 from teppeis/formats
Browse files Browse the repository at this point in the history
custom formats
  • Loading branch information
teppeis committed Apr 7, 2017
2 parents 7b71dde + e5c930d commit 0a3ef2d
Show file tree
Hide file tree
Showing 14 changed files with 203 additions and 122 deletions.
14 changes: 13 additions & 1 deletion index.js
Expand Up @@ -2,16 +2,28 @@

const Ajv = require('ajv');
const jsonSchema = require('./manifest-schema.json');
const validateUrl = require('./src/validate-https-url');

/**
* @param {Object} json
* @param {Object=} options
* @return {{valid: boolean, errors: Array<!Object>}} errors is null if valid
*/
module.exports = function(json) {
module.exports = function(json, options) {
options = options || {};
let relativePath = str => true;
if (options.relativePath) {
relativePath = options.relativePath;
}
const ajv = new Ajv({
allErrors: true,
unknownFormats: true,
errorDataPath: 'property',
formats: {
url: str => validateUrl(str, true),
'https-url': str => validateUrl(str),
'relative-path': relativePath,
},
});
const validate = ajv.compile(jsonSchema);
const valid = validate(json);
Expand Down
73 changes: 27 additions & 46 deletions manifest-schema.json
Expand Up @@ -65,65 +65,44 @@
"type": "string",
"description": "internal only",
"minLength": 1,
"format": "uri"
"format": "relative-path"
},
"homepage_url": {
"type": "object",
"properties": {
"ja": {
"type": "string",
"minLength": 1,
"format": "uri"
"format": "url"
},
"en": {
"type": "string",
"minLength": 1,
"format": "uri"
"format": "url"
},
"zh": {
"type": "string",
"minLength": 1,
"format": "uri"
"format": "url"
}
}
},
"desktop": {
"type": "object",
"properties": {
"js": {
"type": "array",
"uniqueItems": true,
"items": {
"description": "internal or external",
"type": "string",
"format": "uri",
"maxItems": 30
}
"$ref": "#resources"
},
"css": {
"type": "array",
"uniqueItems": true,
"items": {
"description": "internal or external",
"type": "string",
"format": "uri",
"maxItems": 30
}
"$ref": "#resources"
}
}
},
"mobile": {
"type": "object",
"properties": {
"js": {
"type": "array",
"uniqueItems": true,
"items": {
"description": "internal or external",
"type": "string",
"format": "uri",
"maxItems": 30
}
"$ref": "#resources"
}
}
},
Expand All @@ -133,28 +112,14 @@
"html": {
"description": "internal only",
"type": "string",
"format": "uri",
"format": "relative-path",
"minLength": 1
},
"js": {
"type": "array",
"uniqueItems": true,
"items": {
"description": "internal or external",
"type": "string",
"format": "uri",
"maxItems": 30
}
"$ref": "#resources"
},
"css": {
"type": "array",
"uniqueItems": true,
"items": {
"description": "internal or external",
"type": "string",
"format": "uri",
"maxItems": 30
}
"$ref": "#resources"
},
"required_params": {
"type": "array",
Expand All @@ -173,5 +138,21 @@
"type",
"name",
"icon"
]
],
"definitions": {
"resources": {
"id": "#resources",
"type": "array",
"uniqueItems": true,
"items": {
"type": "string",
"anyOf": [{
"format": "https-url"
}, {
"format": "relative-path"
}],
"maxItems": 30
}
}
}
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -14,7 +14,8 @@
"test": "run-s lint mocha"
},
"dependencies": {
"ajv": "^4.11.5"
"ajv": "^4.11.5",
"isomorphic-url": "^1.0.0-alpha12"
},
"devDependencies": {
"eslint": "^3.19.0",
Expand Down
20 changes: 20 additions & 0 deletions src/validate-https-url.js
@@ -0,0 +1,20 @@
'use strict';

const URL = require('isomorphic-url').URL;

/**
* @param {string} str
* @param {boolean=} opt_allowHttp
* @return {boolean}
*/
function validateHttpsUrl(str, opt_allowHttp) {
const allowHttp = !!opt_allowHttp;
try {
const url = new URL(str);
return url.protocol === 'https:' || (allowHttp && url.protocol === 'http:');
} catch (e) {
return false;
}
}

module.exports = validateHttpsUrl;
9 changes: 0 additions & 9 deletions test/fixtures/2-errors.json

This file was deleted.

9 changes: 0 additions & 9 deletions test/fixtures/minimal.json

This file was deleted.

12 changes: 0 additions & 12 deletions test/fixtures/no-english-description.json

This file was deleted.

8 changes: 0 additions & 8 deletions test/fixtures/no-version.json

This file was deleted.

9 changes: 0 additions & 9 deletions test/fixtures/type-is-not-app.json

This file was deleted.

9 changes: 0 additions & 9 deletions test/fixtures/version-is-string.json

This file was deleted.

9 changes: 0 additions & 9 deletions test/fixtures/version-is-zero.json

This file was deleted.

63 changes: 54 additions & 9 deletions test/index.js
@@ -1,7 +1,5 @@
'use strict';

/* eslint-disable global-require */

const assert = require('assert');
const validator = require('../');

Expand All @@ -11,11 +9,13 @@ describe('validator', () => {
});

it('minimal valid JSON', () => {
assert.deepEqual(validator(require('./fixtures/minimal.json')), {valid: true, errors: null});
assert.deepEqual(validator(json()), {valid: true, errors: null});
});

it('missing property', () => {
assert.deepEqual(validator(require('./fixtures/no-version.json')), {
const manifestJson = json();
delete manifestJson.version;
assert.deepEqual(validator(manifestJson), {
valid: false,
errors: [{
dataPath: '.version',
Expand All @@ -30,7 +30,7 @@ describe('validator', () => {
});

it('invalid type', () => {
assert.deepEqual(validator(require('./fixtures/version-is-string.json')), {
assert.deepEqual(validator(json({version: '1'})), {
valid: false,
errors: [{
dataPath: '.version',
Expand All @@ -45,7 +45,7 @@ describe('validator', () => {
});

it('integer is out of range', () => {
assert.deepEqual(validator(require('./fixtures/version-is-zero.json')), {
assert.deepEqual(validator(json({version: 0})), {
valid: false,
errors: [{
dataPath: '.version',
Expand All @@ -62,7 +62,7 @@ describe('validator', () => {
});

it('invalid enum value', () => {
assert.deepEqual(validator(require('./fixtures/type-is-not-app.json')), {
assert.deepEqual(validator(json({type: 'FOO'})), {
valid: false,
errors: [{
dataPath: '.type',
Expand All @@ -79,7 +79,7 @@ describe('validator', () => {
});

it('no English description', () => {
assert.deepEqual(validator(require('./fixtures/no-english-description.json')), {
assert.deepEqual(validator(json({description: {}})), {
valid: false,
errors: [{
dataPath: '.description.en',
Expand All @@ -94,8 +94,53 @@ describe('validator', () => {
});

it('2 errors', () => {
const actual = validator(require('./fixtures/2-errors.json'));
const actual = validator(json({
manifest_version: 'a',
version: 0,
}));
assert(actual.valid === false);
assert(actual.errors.length === 2);
});

it('relative path is invalid for `url`', () => {
const actual = validator(json({homepage_url: {en: 'foo/bar.html'}}));
assert(actual.valid === false);
assert(actual.errors.length === 1);
assert(actual.errors[0].params.format === 'url');
});

it('"http:" is invalid for `https-url`', () => {
const actual = validator(json({
desktop: {
js: [
'http://example.com/icon.png'
]
}
}), {
relativePath: str => !/^https?:/.test(str),
});
assert(actual.valid === false);
assert(actual.errors.length === 3);
assert(actual.errors[0].keyword === 'format');
assert(actual.errors[1].keyword === 'format');
assert(actual.errors[2].keyword === 'anyOf');
});
});

/**
* Generate minimum valid manifest.json and overwrite with source
*
* @param {Object=} source
* @return {!Object}
*/
function json(source) {
return Object.assign({
manifest_version: 1,
version: 1,
type: 'APP',
name: {
en: 'sample plugin',
},
icon: 'image/icon.png',
}, source);
}

0 comments on commit 0a3ef2d

Please sign in to comment.