correct unicode string lengths with "unicode" option

master
Evgeny Poberezkin 2015-06-04 23:08:45 +01:00
parent 62e0d23a00
commit b9b9affcf4
9 changed files with 168 additions and 147 deletions

View File

@ -38,8 +38,8 @@ ajv compiles schemas to functions and caches them in both cases (using stringifi
## Options
- _allErrors_: if true, jv will continue validating all rules collecting all errors (false by default)
- _allErrors_: if true, ajv checks all rules collecting all errors, otherwise it will return after the first error (false by default)
- _verbose_: include the reference to the validated data in the errors (false by default)
- _format_: if false, the formats won't be validated (true by default)
- _uniqueItems_: if false, `uniqueItems` keyword will be ignored (true by default)
- _unicode_: if true, the lengths of strings with unicode pairs will be correct (false by default) - not implemented yet.
- _uniqueItems_: if false, `uniqueItems` keyword will be ignored (true by default, i.e. uniqueItems is checked)
- _unicode_: if true, the lengths of strings with unicode pairs will be correct and each pair will be counted as one character (false by default, as it is slower).

View File

@ -1,14 +1,14 @@
'use strict';
module.exports = {
date: date,
'date-time': date_time,
uri: uri,
email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/,
hostname: hostname,
ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-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-f]{1,4}:){5}(?:(?:(?::[0-9a-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-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-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-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-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-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-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-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-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-f]{1,4}){1,7})|(?:(?::[0-9a-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*$/i,
regex: regex
date: date,
'date-time': date_time,
uri: uri,
email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/,
hostname: hostname,
ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,
ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-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-f]{1,4}:){5}(?:(?:(?::[0-9a-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-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-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-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-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-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-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-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-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-f]{1,4}){1,7})|(?:(?::[0-9a-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*$/i,
regex: regex
};
var DATE = /^\d\d\d\d-(\d\d)-(\d\d)$/;
@ -19,48 +19,48 @@ var URI = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9
function date(str) {
// full-date from http://tools.ietf.org/html/rfc3339#section-5.6
var matches = str.match(DATE);
if (!matches) return false;
// full-date from http://tools.ietf.org/html/rfc3339#section-5.6
var matches = str.match(DATE);
if (!matches) return false;
var month = +matches[1];
var day = +matches[2];
return month >= 1 && month <= 12 && day >= 1 && day <= DAYS[month];
var month = +matches[1];
var day = +matches[2];
return month >= 1 && month <= 12 && day >= 1 && day <= DAYS[month];
}
function date_time(str) {
// http://tools.ietf.org/html/rfc3339#section-5.6
var dateTime = str.toLowerCase().split('t');
if (!date(dateTime[0])) return false;
// http://tools.ietf.org/html/rfc3339#section-5.6
var dateTime = str.toLowerCase().split('t');
if (!date(dateTime[0])) return false;
var matches = dateTime[1].match(TIME);
if (!matches) return false;
var matches = dateTime[1].match(TIME);
if (!matches) return false;
var hour = matches[1];
var minute = matches[2];
var second = matches[3];
return hour <= 23 && minute <= 59 && second <= 59;
var hour = matches[1];
var minute = matches[2];
var second = matches[3];
return hour <= 23 && minute <= 59 && second <= 59;
}
function hostname(str) {
// http://tools.ietf.org/html/rfc1034#section-3.5
return str.length <= 255 && HOSTNAME.test(str);
// http://tools.ietf.org/html/rfc1034#section-3.5
return str.length <= 255 && HOSTNAME.test(str);
}
function uri(str) {
// http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
return str.indexOf('.') >= 0 && URI.test(str);
// http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
return str.indexOf('.') >= 0 && URI.test(str);
}
function regex(str) {
try {
new RegExp(str);
return true;
} catch(e) {
return false;
}
try {
new RegExp(str);
return true;
} catch(e) {
return false;
}
}

View File

@ -1,54 +1,54 @@
'use strict';
var doT = require('dot')
, fs = require('fs')
, stableStringify = require('json-stable-stringify')
, formats = require('./formats')
, resolve = require('./resolve')
, equal = require('./equal');
, fs = require('fs')
, stableStringify = require('json-stable-stringify')
, formats = require('./formats')
, resolve = require('./resolve')
, equal = require('./equal');
var RULES = require('./rules')
, validateTemplate = fs.readFileSync(__dirname + '/validate.dot.js')
, validateGenerator = doT.compile(validateTemplate);
, validateTemplate = fs.readFileSync(__dirname + '/validate.dot.js')
, validateGenerator = doT.compile(validateTemplate);
module.exports = compile;
function compile(schema) {
var self = this;
var validateCode = validateGenerator({
isRoot: true,
schema: schema,
schemaPath: '',
RULES: RULES,
validate: validateGenerator,
copy: copy,
toHash: toHash,
resolveRef: resolveRef,
checkDataType: checkDataType,
checkDataTypes: checkDataTypes,
escapeQuotes: escapeQuotes,
stableStringify: stableStringify,
opts: this.opts
});
// console.log('\n\n\n *** \n', validateCode);
var validate;
eval('validate = ' + validateCode);
var self = this;
var validateCode = validateGenerator({
isRoot: true,
schema: schema,
schemaPath: '',
RULES: RULES,
validate: validateGenerator,
copy: copy,
toHash: toHash,
resolveRef: resolveRef,
checkDataType: checkDataType,
checkDataTypes: checkDataTypes,
escapeQuotes: escapeQuotes,
stableStringify: stableStringify,
opts: this.opts
});
// console.log('\n\n\n *** \n', validateCode);
var validate;
eval('validate = ' + validateCode);
validate.schema = schema;
validate.errors = [];
validate.schema = schema;
validate.errors = [];
return validate;
return validate;
function resolveRef(ref) {
return resolve.call(self, compile, schema, ref);
}
function resolveRef(ref) {
return resolve.call(self, compile, schema, ref);
}
function validateRef(ref, data) {
var v = ref == '#' ? validate : self._schemas[ref];
var valid = v(data);
return { valid: valid, errors: v.errors };
}
function validateRef(ref, data) {
var v = ref == '#' ? validate : self._schemas[ref];
var valid = v(data);
return { valid: valid, errors: v.errors };
}
}
@ -58,58 +58,78 @@ function compile(schema) {
function copy(o, to) {
to = to || {};
for (var key in o) to[key] = o[key];
return to;
to = to || {};
for (var key in o) to[key] = o[key];
return to;
}
function checkDataType(dataType, lvl) {
var data = 'data' + lvl;
switch (dataType) {
case 'null': return data + ' === null';
case 'array': return 'Array.isArray(' + data + ')';
case 'object': return '(' + data + ' && typeof ' + data + ' == "object" && !Array.isArray(' + data + '))';
case 'integer': return '(typeof ' + data + ' == "number" && !(' + data + ' % 1))'
default: return 'typeof ' + data + ' == "' + dataType + '"';
}
var data = 'data' + lvl;
switch (dataType) {
case 'null': return data + ' === null';
case 'array': return 'Array.isArray(' + data + ')';
case 'object': return '(' + data + ' && typeof ' + data + ' == "object" && !Array.isArray(' + data + '))';
case 'integer': return '(typeof ' + data + ' == "number" && !(' + data + ' % 1))'
default: return 'typeof ' + data + ' == "' + dataType + '"';
}
}
function checkDataTypes(dataTypes, lvl) {
var data = 'data' + lvl;
switch (dataTypes.length) {
case 0: return 'true';
case 1: return checkDataType(dataTypes[0], lvl);
default:
var code = ''
var types = toHash(dataTypes);
if (types.array && types.object) {
code = types.null ? '(': '(' + data + ' && '
code += 'typeof ' + data + ' == "object")';
delete types.null;
delete types.array;
delete types.object;
}
if (types.number) delete types.integer;
for (var t in types)
code += (code ? '||' : '' ) + checkDataType(t, lvl);
var data = 'data' + lvl;
switch (dataTypes.length) {
case 0: return 'true';
case 1: return checkDataType(dataTypes[0], lvl);
default:
var code = ''
var types = toHash(dataTypes);
if (types.array && types.object) {
code = types.null ? '(': '(' + data + ' && '
code += 'typeof ' + data + ' == "object")';
delete types.null;
delete types.array;
delete types.object;
}
if (types.number) delete types.integer;
for (var t in types)
code += (code ? '||' : '' ) + checkDataType(t, lvl);
return code;
return code;
}
}
// https://mathiasbynens.be/notes/javascript-encoding
// https://github.com/bestiejs/punycode.js - punycode.ucs2.decode
function ucs2length(str) {
var length = 0
, len = str.length
, pos = 0
, value;
while (pos < len) {
length++;
value = str.charCodeAt(pos++);
if (value >= 0xD800 && value <= 0xDBFF && pos < len) {
// high surrogate, and there is a next character
value = str.charCodeAt(pos);
if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate
}
}
return length;
}
function toHash(arr, func) {
var hash = {};
arr.forEach(function (item) {
if (func) item = func(item);
hash[item] = true;
});
return hash;
var hash = {};
arr.forEach(function (item) {
if (func) item = func(item);
hash[item] = true;
});
return hash;
}
function escapeQuotes(str) {
return str.replace(/"/g, '\\"');
return str.replace(/"/g, '\\"');
}

View File

@ -2,30 +2,30 @@
module.exports = function resolve(compile, rootSchema, ref) {
var schema = rootSchema;
if (ref[0] != '#')
schema = undefined;
else if (ref != '#') {
if (this._schemas[ref])
schema = this._schemas[ref];
else {
var parts = ref.split('/');
for (var i = 1; i < parts.length; i++) {
if (!schema) break;
var part = unescape(parts[i]);
schema = schema[part];
if (schema.$ref)
schema = resolve.call(this, compile, rootSchema, schema.$ref);
}
if (schema) this._schemas[ref] = compile.call(this, schema);
}
var schema = rootSchema;
if (ref[0] != '#')
schema = undefined;
else if (ref != '#') {
if (this._schemas[ref])
schema = this._schemas[ref];
else {
var parts = ref.split('/');
for (var i = 1; i < parts.length; i++) {
if (!schema) break;
var part = unescape(parts[i]);
schema = schema[part];
if (schema.$ref)
schema = resolve.call(this, compile, rootSchema, schema.$ref);
}
if (schema) this._schemas[ref] = compile.call(this, schema);
}
return schema;
}
return schema;
};
function unescape(str) {
return decodeURIComponent(str)
.replace(/~1/g, '/')
.replace(/~0/g, '~');
return decodeURIComponent(str)
.replace(/~1/g, '/')
.replace(/~0/g, '~');
}

View File

@ -34,6 +34,15 @@
#}}
{{## def.strLength:
{{? it.opts.unicode }}
ucs2length(data{{=$dataLvl}})
{{??}}
data{{=$dataLvl}}.length
{{?}}
#}}
{{## def.cleanUp:
{{ out = out.replace(/if \(valid[0-9]*\) \{\s*\}/g, ''); }}
#}}

View File

@ -1,6 +1,6 @@
{{# def.definitions }}
{{# def.setup:'maxLength' }}
var valid{{=$lvl}} = data{{=$dataLvl}}.length <= {{=$schema}};
var valid{{=$lvl}} = {{# def.strLength }} <= {{=$schema}};
{{# def.checkError:'maxLength' }}

View File

@ -1,6 +1,6 @@
{{# def.definitions }}
{{# def.setup:'minLength' }}
var valid{{=$lvl}} = data{{=$dataLvl}}.length >= {{=$schema}};
var valid{{=$lvl}} = {{# def.strLength }} >= {{=$schema}};
{{# def.checkError:'minLength' }}

View File

@ -1,6 +1,6 @@
{
"name": "ajv",
"version": "0.2.0",
"version": "0.2.1",
"description": "Another JSON schema Validator",
"main": "lib/ajv.js",
"scripts": {

View File

@ -6,18 +6,10 @@ var glob = require('glob')
var ONLY_RULES, SKIP_RULES;
// ONLY_RULES = [
// 'type',
// 'not',
// 'allOf',
// 'anyOf',
// 'oneOf',
// 'enum',
// 'maximum', 'minimum',
// 'multipleOf',
// 'maxLength', 'minLength', 'pattern',
// 'type', 'not', 'allOf', 'anyOf', 'oneOf', 'enum',
// 'maximum', 'minimum', 'multipleOf', 'maxLength', 'minLength', 'pattern',
// 'properties', 'patternProperties', 'additionalProperties',
// 'dependencies',
// 'required',
// 'dependencies', 'required',
// 'maxProperties', 'minProperties', 'maxItems', 'minItems',
// 'items', 'additionalItems', 'uniqueItems',
// 'optional/format', 'optional/bignum',
@ -31,8 +23,8 @@ SKIP_RULES = [
var Ajv = require('../lib/ajv')
, ajv = Ajv()
, fullAjv = Ajv({ allErrors: true, verbose: true });
, ajv = Ajv({ unicode: true })
, fullAjv = Ajv({ unicode: true, allErrors: true, verbose: true });
var remoteRefs = {
'http://localhost:1234/integer.json': require('./JSON-Schema-Test-Suite/remotes/integer.json'),
@ -67,7 +59,7 @@ describe('JSON-Schema tests', function () {
var fullValidate = fullAjv.compile(testSet.schema);
testSet.tests.forEach(function (test) {
// if (test.description != 'valid') return;
// if (test.description != 'one supplementary Unicode code point is not long enough') return;
// console.log(testSet.schema, '\n\n***\n\n', validate.toString());
it(test.description, function() {
var valid = validate(test.data);