From 08e890d9b389ab90b56019c0b8ec4b9a48c1ab7f Mon Sep 17 00:00:00 2001 From: svozza Date: Tue, 25 Aug 2026 20:15:46 +0000 Subject: [PATCH] AVRO-4350: Remove underscore dependency --- lang/js/etc/deprecated/Gruntfile.js | 57 --- lang/js/etc/deprecated/README | 20 - lang/js/etc/deprecated/test_validator.js | 525 ------------------- lang/js/etc/deprecated/validator.js | 163 +++--- lang/js/package-lock.json | 9 - lang/js/package.json | 4 +- lang/js/test/test_deprecated_validator.js | 589 ++++++++++++++++++++++ 7 files changed, 683 insertions(+), 684 deletions(-) delete mode 100644 lang/js/etc/deprecated/Gruntfile.js delete mode 100644 lang/js/etc/deprecated/README delete mode 100644 lang/js/etc/deprecated/test_validator.js create mode 100644 lang/js/test/test_deprecated_validator.js diff --git a/lang/js/etc/deprecated/Gruntfile.js b/lang/js/etc/deprecated/Gruntfile.js deleted file mode 100644 index dd7f8c1e7dc..00000000000 --- a/lang/js/etc/deprecated/Gruntfile.js +++ /dev/null @@ -1,57 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -module.exports = function(grunt) { - - // Project configuration. - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - nodeunit: { - files: ['test/**/*.js'] - }, - watch: { - files: ['<%= jshint.files %>'], - tasks: ['jshint', 'nodeunit'] - }, - jshint: { - files: ['Gruntfile.js', 'lib/**/*.js', 'test/**/*.js'], - options: { - curly: true, - eqeqeq: true, - immed: true, - latedef: true, - newcap: true, - noarg: true, - sub: true, - undef: true, - boss: true, - eqnull: true, - node: true - }, - globals: { - exports: true - } - } - }); - - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-nodeunit'); - grunt.loadNpmTasks('grunt-contrib-watch'); - - grunt.registerTask('default', ['jshint', 'nodeunit']); - grunt.registerTask('test', ['nodeunit']); - grunt.registerTask('lint', ['jshint']); - -}; diff --git a/lang/js/etc/deprecated/README b/lang/js/etc/deprecated/README deleted file mode 100644 index c853b60fa58..00000000000 --- a/lang/js/etc/deprecated/README +++ /dev/null @@ -1,20 +0,0 @@ -Avro Javascript -=============================================================================== - -Usage -------------------------------------------------------------------------------- - -* *With node.js*: Install this avro-js npm package and then - use ```require('avro-js')``` in your program. - -* *Outside of node.js (e.g., browser)*: Include the validator.js file and the - [underscore.js library](https://underscorejs.org/). - - -Running tests -------------------------------------------------------------------------------- - -To run the included test suite using node.js: - -1. Install the npm dependencies by running ```npm install``` in the "js" dir. -2. Run ```node_modules/grunt/bin/grunt test```. diff --git a/lang/js/etc/deprecated/test_validator.js b/lang/js/etc/deprecated/test_validator.js deleted file mode 100644 index 7e187ed960f..00000000000 --- a/lang/js/etc/deprecated/test_validator.js +++ /dev/null @@ -1,525 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one or more -// contributor license agreements. See the NOTICE file distributed with -// this work for additional information regarding copyright ownership. -// The ASF licenses this file to You under the Apache License, Version 2.0 -// (the "License"); you may not use this file except in compliance with -// the License. You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -var validator = require('./validator'); -var Validator = validator.Validator; -var ProtocolValidator = validator.ProtocolValidator; - -exports['test'] = { - setUp: function(done) { - done(); - }, - 'nonexistent/null/undefined': function(test) { - test.throws(function() { return new Validator(); }); - test.throws(function() { return new Validator(null); }); - test.throws(function() { return new Validator(undefined); }); - test.done(); - }, - 'unrecognized primitive type name': function(test) { - test.throws(function() { return new Validator('badtype'); }); - test.done(); - }, - 'invalid schema javascript type': function(test) { - test.throws(function() { return new Validator(123); }); - test.throws(function() { return new Validator(function() { }); }); - test.done(); - }, - - // Primitive types - 'null': function(test) { - test.ok(Validator.validate('null', null)); - test.ok(Validator.validate('null', undefined)); - test.throws(function() { Validator.validate('null', 1); }); - test.throws(function() { Validator.validate('null', 'a'); }); - test.done(); - }, - 'boolean': function(test) { - test.ok(Validator.validate('boolean', true)); - test.ok(Validator.validate('boolean', false)); - test.throws(function() { Validator.validate('boolean', null); }); - test.throws(function() { Validator.validate('boolean', 1); }); - test.throws(function() { Validator.validate('boolean', 'a'); }); - test.done(); - }, - 'int': function(test) { - test.ok(Validator.validate('int', 1)); - test.ok(Validator.validate('long', Math.pow(2, 31) - 1)); - test.throws(function() { Validator.validate('int', 1.5); }); - test.throws(function() { Validator.validate('int', Math.pow(2, 40)); }); - test.throws(function() { Validator.validate('int', null); }); - test.throws(function() { Validator.validate('int', 'a'); }); - test.done(); - }, - 'long': function(test) { - test.ok(Validator.validate('long', 1)); - test.ok(Validator.validate('long', Math.pow(2, 63) - 1)); - test.throws(function() { Validator.validate('long', 1.5); }); - test.throws(function() { Validator.validate('long', Math.pow(2, 70)); }); - test.throws(function() { Validator.validate('long', null); }); - test.throws(function() { Validator.validate('long', 'a'); }); - test.done(); - }, - 'float': function(test) { - test.ok(Validator.validate('float', 1)); - test.ok(Validator.validate('float', 1.5)); - test.throws(function() { Validator.validate('float', 'a'); }); - test.throws(function() { Validator.validate('float', null); }); - test.done(); - }, - 'double': function(test) { - test.ok(Validator.validate('double', 1)); - test.ok(Validator.validate('double', 1.5)); - test.throws(function() { Validator.validate('double', 'a'); }); - test.throws(function() { Validator.validate('double', null); }); - test.done(); - }, - 'bytes': function(test) { - // not implemented yet - test.throws(function() { Validator.validate('bytes', 1); }); - test.done(); - }, - 'string': function(test) { - test.ok(Validator.validate('string', 'a')); - test.throws(function() { Validator.validate('string', 1); }); - test.throws(function() { Validator.validate('string', null); }); - test.done(); - }, - - // Records - 'empty-record': function(test) { - var schema = {type: 'record', name: 'EmptyRecord', fields: []}; - test.ok(Validator.validate(schema, {})); - test.throws(function() { Validator.validate(schema, 1); }); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, 'a'); }); - test.done(); - }, - 'record-with-string': function(test) { - var schema = {type: 'record', name: 'EmptyRecord', fields: [{name: 'stringField', type: 'string'}]}; - test.ok(Validator.validate(schema, {stringField: 'a'})); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, {stringField: 1}); }); - test.throws(function() { Validator.validate(schema, {stringField: []}); }); - test.throws(function() { Validator.validate(schema, {stringField: {}}); }); - test.throws(function() { Validator.validate(schema, {stringField: null}); }); - test.throws(function() { Validator.validate(schema, {stringField: 'a', unexpectedField: 'a'}); }); - test.done(); - }, - 'record-with-string-and-number': function(test) { - var schema = {type: 'record', name: 'EmptyRecord', fields: [{name: 'stringField', type: 'string'}, {name: 'intField', type: 'int'}]}; - test.ok(Validator.validate(schema, {stringField: 'a', intField: 1})); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, {stringField: 'a'}); }); - test.throws(function() { Validator.validate(schema, {intField: 1}); }); - test.throws(function() { Validator.validate(schema, {stringField: 'a', intField: 1, unexpectedField: 'a'}); }); - test.done(); - }, - 'nested-record-with-namespace-relative': function(test) { - var schema = {type: 'record', namespace: 'x.y.z', name: 'RecordA', fields: [{name: 'recordBField1', type: ['null', {type: 'record', name: 'RecordB', fields: []}]}, {name: 'recordBField2', type: 'RecordB'}]}; - test.ok(Validator.validate(schema, {recordBField1: null, recordBField2: {}})); - test.ok(Validator.validate(schema, {recordBField1: {'x.y.z.RecordB': {}}, recordBField2: {}})); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, {recordBField1: null}); }); - test.throws(function() { Validator.validate(schema, {recordBField2: {}}); }); - test.throws(function() { Validator.validate(schema, {recordBField1: {'RecordB': {}}, recordBField2: {}}); }); - test.done(); - }, - 'nested-record-with-namespace-absolute': function(test) { - var schema = {type: 'record', namespace: 'x.y.z', name: 'RecordA', fields: [{name: 'recordBField1', type: ['null', {type: 'record', name: 'RecordB', fields: []}]}, {name: 'recordBField2', type: 'x.y.z.RecordB'}]}; - test.ok(Validator.validate(schema, {recordBField1: null, recordBField2: {}})); - test.ok(Validator.validate(schema, {recordBField1: {'x.y.z.RecordB': {}}, recordBField2: {}})); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, {recordBField1: null}); }); - test.throws(function() { Validator.validate(schema, {recordBField2: {}}); }); - test.throws(function() { Validator.validate(schema, {recordBField1: {'RecordB': {}}, recordBField2: {}}); }); - test.done(); - }, - - - // Enums - 'enum': function(test) { - var schema = {type: 'enum', name: 'Colors', symbols: ['Red', 'Blue']}; - test.ok(Validator.validate(schema, 'Red')); - test.ok(Validator.validate(schema, 'Blue')); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, undefined); }); - test.throws(function() { Validator.validate(schema, 'NotAColor'); }); - test.throws(function() { Validator.validate(schema, ''); }); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, []); }); - test.throws(function() { Validator.validate(schema, 1); }); - test.done(); - }, - - // Unions - 'union': function(test) { - var schema = ['string', 'int']; - test.ok(Validator.validate(schema, {string: 'a'})); - test.ok(Validator.validate(schema, {int: 1})); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, undefined); }); - test.throws(function() { Validator.validate(schema, 'a'); }); - test.throws(function() { Validator.validate(schema, 1); }); - test.throws(function() { Validator.validate(schema, {string: 'a', int: 1}); }); - test.throws(function() { Validator.validate(schema, []); }); - test.done(); - }, - - 'union with null': function(test) { - var schema = ['string', 'null']; - test.ok(Validator.validate(schema, {string: 'a'})); - test.ok(Validator.validate(schema, null)); - test.throws(function() { Validator.validate(schema, undefined); }); - test.done(); - }, - - 'nested union': function(test) { - var schema = ['string', {type: 'int'}]; - test.ok(Validator.validate(schema, {string: 'a'})); - test.ok(Validator.validate(schema, {int: 1})); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, undefined); }); - test.throws(function() { Validator.validate(schema, 'a'); }); - test.throws(function() { Validator.validate(schema, 1); }); - test.throws(function() { Validator.validate(schema, {string: 'a', int: 1}); }); - test.throws(function() { Validator.validate(schema, []); }); - test.done(); - }, - - // Arrays - 'array': function(test) { - var schema = {type: "array", items: "string"}; - test.ok(Validator.validate(schema, [])); - test.ok(Validator.validate(schema, ["a"])); - test.ok(Validator.validate(schema, ["a", "b", "a"])); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, undefined); }); - test.throws(function() { Validator.validate(schema, 'a'); }); - test.throws(function() { Validator.validate(schema, 1); }); - test.throws(function() { Validator.validate(schema, {}); }); - test.throws(function() { Validator.validate(schema, {"1": "a"}); }); - test.throws(function() { Validator.validate(schema, {1: "a"}); }); - test.throws(function() { Validator.validate(schema, {1: "a", "b": undefined}); }); - test.throws(function() { var a = {}; a[0] = "a"; Validator.validate(schema, a); }); - test.throws(function() { Validator.validate(schema, [1]); }); - test.throws(function() { Validator.validate(schema, [1, "a"]); }); - test.throws(function() { Validator.validate(schema, ["a", 1]); }); - test.throws(function() { Validator.validate(schema, [null, 1]); }); - test.done(); - }, - - // Maps - 'map': function(test) { - var schema = {type: "map", values: "string"}; - test.ok(Validator.validate(schema, {})); - test.ok(Validator.validate(schema, {"a": "b"})); - test.ok(Validator.validate(schema, {"a": "b", "c": "d"})); - test.throws(function() { Validator.validate(schema, null); }); - test.throws(function() { Validator.validate(schema, undefined); }); - test.throws(function() { Validator.validate(schema, 'a'); }); - test.throws(function() { Validator.validate(schema, 1); }); - test.throws(function() { Validator.validate(schema, [1]); }); - test.throws(function() { Validator.validate(schema, {"a": 1}); }); - test.throws(function() { Validator.validate(schema, {"a": "b", "c": 1}); }); - test.done(); - }, - - // Protocols - 'protocol': function(test) { - var protocol = {protocol: "Protocol1", namespace: "x.y.z", types: [ - {type: "record", name: "RecordA", fields: []}, - {type: "record", name: "RecordB", fields: [{name: "recordAField", type: "RecordA"}]} - ]}; - test.ok(ProtocolValidator.validate(protocol, 'RecordA', {})); - test.ok(ProtocolValidator.validate(protocol, 'x.y.z.RecordA', {})); - test.ok(ProtocolValidator.validate(protocol, 'RecordB', {recordAField: {}})); - test.ok(ProtocolValidator.validate(protocol, 'x.y.z.RecordB', {recordAField: {}})); - test.throws(function() { ProtocolValidator.validate(protocol, 'RecordDoesNotExist', {}); }); - test.throws(function() { ProtocolValidator.validate(protocol, 'RecordDoesNotExist', null); }); - test.throws(function() { ProtocolValidator.validate(protocol, 'RecordB', {}); }); - test.throws(function() { ProtocolValidator.validate(protocol, null, {}); }); - test.throws(function() { ProtocolValidator.validate(protocol, '', {}); }); - test.throws(function() { ProtocolValidator.validate(protocol, {}, {}); }); - test.done(); - }, - - // Samples - 'link': function(test) { - var schema = { - "type" : "record", - "name" : "Bundle", - "namespace" : "aa.bb.cc", - "fields" : [ { - "name" : "id", - "type" : "string" - }, { - "name" : "type", - "type" : "string" - }, { - "name" : "data_", - "type" : [ "null", { - "type" : "record", - "name" : "LinkData", - "fields" : [ { - "name" : "address", - "type" : "string" - }, { - "name" : "title", - "type" : [ "null", "string" ], - "default" : null - }, { - "name" : "excerpt", - "type" : [ "null", "string" ], - "default" : null - }, { - "name" : "image", - "type" : [ "null", { - "type" : "record", - "name" : "Image", - "fields" : [ { - "name" : "url", - "type" : "string" - }, { - "name" : "width", - "type" : "int" - }, { - "name" : "height", - "type" : "int" - } ] - } ], - "default" : null - }, { - "name" : "meta", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - } ] - } ], - "default" : null - }, { - "name" : "atoms_", - "type" : { - "type" : "map", - "values" : { - "type" : "map", - "values" : { - "type" : "record", - "name" : "Atom", - "fields" : [ { - "name" : "index_", - "type" : { - "type" : "record", - "name" : "AtomIndex", - "fields" : [ { - "name" : "type_", - "type" : "string" - }, { - "name" : "id", - "type" : "string" - } ] - } - }, { - "name" : "data_", - "type" : [ "LinkData" ] - } ] - } - } - }, - "default" : { - } - }, { - "name" : "meta_", - "type" : { - "type" : "record", - "name" : "BundleMetadata", - "fields" : [ { - "name" : "date", - "type" : "long", - "default" : 0 - }, { - "name" : "members", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - }, { - "name" : "tags", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - }, { - "name" : "meta", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - }, { - "name" : "votes", - "type" : { - "type" : "map", - "values" : { - "type" : "record", - "name" : "VoteData", - "fields" : [ { - "name" : "date", - "type" : "long" - }, { - "name" : "userName", - "type" : [ "null", "string" ], - "default" : null - }, { - "name" : "direction", - "type" : { - "type" : "enum", - "name" : "VoteDirection", - "symbols" : [ "Up", "Down", "None" ] - } - } ] - } - }, - "default" : { - } - }, { - "name" : "views", - "type" : { - "type" : "map", - "values" : { - "type" : "record", - "name" : "ViewData", - "fields" : [ { - "name" : "userName", - "type" : "string" - }, { - "name" : "count", - "type" : "int" - } ] - } - }, - "default" : { - } - }, { - "name" : "relevance", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - }, { - "name" : "clicks", - "type" : { - "type" : "map", - "values" : "string" - }, - "default" : { - } - } ] - } - } ] - }; - var okObj = { - "id": "https://github.com/sqs/akka-kryo-serialization/subscription", - "type": "link", - "data_": { - "aa.bb.cc.LinkData": { - "address": "https://github.com/sqs/akka-kryo-serialization/subscription", - "title": { - "string": "Sign in · GitHub" - }, - "excerpt": { - "string": "Signup and Pricing Explore GitHub Features Blog Sign in Sign in (Pricing and Signup) Username or Email Password (forgot password) GitHub Links GitHub About Blog Feat" - }, - "image": { - "aa.bb.cc.Image": { - "url": "https://a248.e.akamai.net/assets.github.com/images/modules/header/logov7@4x.png?1340659561", - "width": 280, - "height": 120 - } - }, - "meta": {} - } - }, - "atoms_": { - "link": { - "https://github.com/sqs/akka-kryo-serialization/subscription": { - "index_": { - "type_": "link", - "id": "https://github.com/sqs/akka-kryo-serialization/subscription" - }, - "data_": { - "aa.bb.cc.LinkData": { - "address": "https://github.com/sqs/akka-kryo-serialization/subscription", - "title": { - "string": "Sign in · GitHub" - }, - "excerpt": { - "string": "Signup and Pricing Explore GitHub Features Blog Sign in Sign in (Pricing and Signup) Username or Email Password (forgot password) GitHub Links GitHub About Blog Feat" - }, - "image": { - "aa.bb.cc.Image": { - "url": "https://a248.e.akamai.net/assets.github.com/images/modules/header/logov7@4x.png?1340659561", - "width": 280, - "height": 120 - } - }, - "meta": {} - } - } - } - } - }, - "meta_": { - "date": 1345537530000, - "members": { - "a@a.com": "1" - }, - "tags": { - "blue": "1" - }, - "meta": {}, - "votes": {}, - "views": { - "a@a.com": { - "userName": "John Smith", - "count": 100 - } - }, - "relevance": { - "a@a.com": "1", - "b@b.com": "2" - }, - "clicks": {} - } - }; - - test.ok(Validator.validate(schema, okObj)); - - var badObj = okObj; // no deep copy since we won't reuse okObj - badObj.meta_.clicks['a@a.com'] = 123; - test.throws(function() { Validator.validate(schema, badObj); }); - - test.done(); - } -}; diff --git a/lang/js/etc/deprecated/validator.js b/lang/js/etc/deprecated/validator.js index 25716561498..60c6c45e0ed 100644 --- a/lang/js/etc/deprecated/validator.js +++ b/lang/js/etc/deprecated/validator.js @@ -13,13 +13,34 @@ // See the License for the specific language governing permissions and // limitations under the License. -var _ = require("underscore"), - util = require('util'); +var util = require('util'); var WARNING = 'Validator API is deprecated. Please use the type API instead.'; Validator = util.deprecate(Validator, WARNING); ProtocolValidator = util.deprecate(ProtocolValidator, WARNING); +var objectToString = Object.prototype.toString; + + +function isObject(obj) { + var type = typeof obj; + return type === 'function' || (type === 'object' && !!obj); +} + +function isString(obj) { + return objectToString.call(obj) === '[object String]'; +} + +function isNumber(obj) { + return objectToString.call(obj) === '[object Number]'; +} + +function isBoolean(obj) { + return obj === true || + obj === false || + objectToString.call(obj) === '[object Boolean]'; +} + var AvroSpec = { PrimitiveTypes: ['null', 'boolean', 'int', 'long', 'float', 'double', 'bytes', 'string'], ComplexTypes: ['record', 'enum', 'array', 'map', 'union', 'fixed'] @@ -34,15 +55,15 @@ var ProtocolValidationError = function(msg) { return new Error('ProtocolValidati function Record(name, namespace, fields) { function validateArgs(name, namespace, fields) { - if (!_.isString(name)) { + if (!isString(name)) { throw new InvalidSchemaError('Record name must be string'); } - if (!_.isNull(namespace) && !_.isUndefined(namespace) && !_.isString(namespace)) { + if (namespace != null && !isString(namespace)) { throw new InvalidSchemaError('Record namespace must be string or null'); } - if (!_.isArray(fields)) { + if (!Array.isArray(fields)) { throw new InvalidSchemaError('Record name must be string'); } } @@ -56,30 +77,30 @@ function Record(name, namespace, fields) { function makeFullyQualifiedTypeName(schema, namespace) { var typeName = null; - if (_.isString(schema)) { + if (isString(schema)) { typeName = schema; - } else if (_.isObject(schema)) { - if (_.isString(schema.namespace)) { + } else if (isObject(schema)) { + if (isString(schema.namespace)) { namespace = schema.namespace; } - if (_.isString(schema.name)) { + if (isString(schema.name)) { typeName = schema.name; - } else if (_.isString(schema.type)) { + } else if (isString(schema.type)) { typeName = schema.type; } } else { throw new InvalidSchemaError('unable to determine fully qualified type name from schema ' + JSON.stringify(schema) + ' in namespace ' + namespace); } - if (!_.isString(typeName)) { + if (!isString(typeName)) { throw new InvalidSchemaError('unable to determine type name from schema ' + JSON.stringify(schema) + ' in namespace ' + namespace); } if (typeName.indexOf('.') !== -1) { return typeName; - } else if (_.contains(AvroSpec.PrimitiveTypes, typeName)) { + } else if (AvroSpec.PrimitiveTypes.includes(typeName)) { return typeName; - } else if (_.isString(namespace)) { + } else if (isString(namespace)) { return namespace + '.' + typeName; } else { return typeName; @@ -88,11 +109,11 @@ function makeFullyQualifiedTypeName(schema, namespace) { function Union(typeSchemas, namespace) { this.branchNames = function() { - return _.map(typeSchemas, function(typeSchema) { return makeFullyQualifiedTypeName(typeSchema, namespace); }); + return typeSchemas.map(function(typeSchema) { return makeFullyQualifiedTypeName(typeSchema, namespace); }); }; function validateArgs(typeSchemas) { - if (!_.isArray(typeSchemas) || _.isEmpty(typeSchemas)) { + if (!Array.isArray(typeSchemas) || typeSchemas.length === 0) { throw new InvalidSchemaError('Union must have at least 1 branch'); } } @@ -106,10 +127,10 @@ function Union(typeSchemas, namespace) { function Enum(symbols) { function validateArgs(symbols) { - if (!_.isArray(symbols)) { + if (!Array.isArray(symbols)) { throw new InvalidSchemaError('Enum must have array of symbols, got ' + JSON.stringify(symbols)); } - if (!_.all(symbols, function(symbol) { return _.isString(symbol); })) { + if (!symbols.every(function(symbol) { return isString(symbol); })) { throw new InvalidSchemaError('Enum symbols must be strings, got ' + JSON.stringify(symbols)); } } @@ -122,7 +143,7 @@ function Enum(symbols) { function AvroArray(itemSchema) { function validateArgs(itemSchema) { - if (_.isNull(itemSchema) || _.isUndefined(itemSchema)) { + if (itemSchema == null) { throw new InvalidSchemaError('Array "items" schema should not be null or undefined'); } } @@ -135,7 +156,7 @@ function AvroArray(itemSchema) { function Map(valueSchema) { function validateArgs(valueSchema) { - if (_.isNull(valueSchema) || _.isUndefined(valueSchema)) { + if (valueSchema == null) { throw new InvalidSchemaError('Map "values" schema should not be null or undefined'); } } @@ -147,7 +168,7 @@ function Map(valueSchema) { function Field(name, schema) { function validateArgs(name, schema) { - if (!_.isString(name)) { + if (!isString(name)) { throw new InvalidSchemaError('Field name must be string'); } } @@ -158,11 +179,11 @@ function Field(name, schema) { function Primitive(type) { function validateArgs(type) { - if (!_.isString(type)) { + if (!isString(type)) { throw new InvalidSchemaError('Primitive type name must be a string'); } - if (!_.contains(AvroSpec.PrimitiveTypes, type)) { + if (!AvroSpec.PrimitiveTypes.includes(type)) { throw new InvalidSchemaError('Primitive type must be one of: ' + JSON.stringify(AvroSpec.PrimitiveTypes) + '; got ' + type); } } @@ -196,40 +217,40 @@ function Validator(schema, namespace, namedTypes) { }; var _validateRecord = function(schema, obj) { - if (!_.isObject(obj) || _.isArray(obj)) { + if (!isObject(obj) || Array.isArray(obj)) { throw new ValidationError('Expected record Javascript type to be non-array object, got ' + JSON.stringify(obj)); } - var schemaFieldNames = _.pluck(schema.fields, 'name').sort(); - var objFieldNames = _.keys(obj).sort(); - if (!_.isEqual(schemaFieldNames, objFieldNames)) { + var schemaFieldNames = schema.fields.map(function(field) { return field.name; }).sort(); + var objFieldNames = Object.keys(obj).sort(); + if (!util.isDeepStrictEqual(schemaFieldNames, objFieldNames)) { throw new ValidationError('Expected record fields ' + JSON.stringify(schemaFieldNames) + '; got ' + JSON.stringify(objFieldNames)); } - return _.all(schema.fields, function(field) { + return schema.fields.every(function(field) { return _validate(field.schema, obj[field.name]); }); }; var _validateUnion = function(schema, obj) { - if (_.isObject(obj)) { - if (_.isArray(obj)) { + if (isObject(obj)) { + if (Array.isArray(obj)) { throw new ValidationError('Expected union Javascript type to be non-array object (or null), got ' + JSON.stringify(obj)); - } else if (_.size(obj) !== 1) { + } else if (Object.keys(obj).length !== 1) { throw new ValidationError('Expected union Javascript object to be object with exactly 1 key (or null), got ' + JSON.stringify(obj)); } else { - var unionBranch = _.keys(obj)[0]; + var unionBranch = Object.keys(obj)[0]; if (unionBranch === "") { throw new ValidationError('Expected union Javascript object to contain non-empty string branch, got ' + JSON.stringify(obj)); } - if (_.contains(schema.branchNames(), unionBranch)) { + if (schema.branchNames().includes(unionBranch)) { return true; } else { throw new ValidationError('Expected union branch to be one of ' + JSON.stringify(schema.branchNames()) + '; got ' + JSON.stringify(unionBranch)); } } - } else if (_.isNull(obj)) { - if (_.contains(schema.branchNames(), 'null')) { + } else if (obj === null) { + if (schema.branchNames().includes('null')) { return true; } else { throw new ValidationError('Expected union branch to be one of ' + JSON.stringify(schema.branchNames()) + '; got ' + JSON.stringify(obj)); @@ -240,8 +261,8 @@ function Validator(schema, namespace, namedTypes) { }; var _validateEnum = function(schema, obj) { - if (_.isString(obj)) { - if (_.contains(schema.symbols, obj)) { + if (isString(obj)) { + if (schema.symbols.includes(obj)) { return true; } else { throw new ValidationError('Expected enum value to be one of ' + JSON.stringify(schema.symbols) + '; got ' + JSON.stringify(obj)); @@ -252,17 +273,19 @@ function Validator(schema, namespace, namedTypes) { }; var _validateArray = function(schema, obj) { - if (_.isArray(obj)) { - return _.all(obj, function(member) { return _validate(schema.itemSchema, member); }); + if (Array.isArray(obj)) { + return obj.every(function(member) { return _validate(schema.itemSchema, member); }); } else { throw new ValidationError('Expected array Javascript object to be array, got ' + JSON.stringify(obj)); } }; var _validateMap = function(schema, obj) { - if (_.isObject(obj) && !_.isArray(obj)) { - return _.all(obj, function(value) { return _validate(schema.valueSchema, value); }); - } else if (_.isArray(obj)) { + if (isObject(obj) && !Array.isArray(obj)) { + return Object.keys(obj).every(function(key) { + return _validate(schema.valueSchema, obj[key]); + }); + } else if (Array.isArray(obj)) { throw new ValidationError('Expected map Javascript object to be non-array object, got array ' + JSON.stringify(obj)); } else { throw new ValidationError('Expected map Javascript object to be non-array object, got ' + JSON.stringify(obj)); @@ -272,42 +295,42 @@ function Validator(schema, namespace, namedTypes) { var _validatePrimitive = function(schema, obj) { switch (schema.type) { case 'null': - if (_.isNull(obj) || _.isUndefined(obj)) { + if (obj == null) { return true; } else { throw new ValidationError('Expected Javascript null or undefined for Avro null, got ' + JSON.stringify(obj)); } break; case 'boolean': - if (_.isBoolean(obj)) { + if (isBoolean(obj)) { return true; } else { throw new ValidationError('Expected Javascript boolean for Avro boolean, got ' + JSON.stringify(obj)); } break; case 'int': - if (_.isNumber(obj) && Math.floor(obj) === obj && Math.abs(obj) <= Math.pow(2, 31)) { + if (isNumber(obj) && Math.floor(obj) === obj && Math.abs(obj) <= Math.pow(2, 31)) { return true; } else { throw new ValidationError('Expected Javascript int32 number for Avro int, got ' + JSON.stringify(obj)); } break; case 'long': - if (_.isNumber(obj) && Math.floor(obj) === obj && Math.abs(obj) <= Math.pow(2, 63)) { + if (isNumber(obj) && Math.floor(obj) === obj && Math.abs(obj) <= Math.pow(2, 63)) { return true; } else { throw new ValidationError('Expected Javascript int64 number for Avro long, got ' + JSON.stringify(obj)); } break; case 'float': - if (_.isNumber(obj)) { // TODO: handle NaN? + if (isNumber(obj)) { // TODO: handle NaN? return true; } else { throw new ValidationError('Expected Javascript float number for Avro float, got ' + JSON.stringify(obj)); } break; case 'double': - if (_.isNumber(obj)) { // TODO: handle NaN? + if (isNumber(obj)) { // TODO: handle NaN? return true; } else { throw new ValidationError('Expected Javascript double number for Avro double, got ' + JSON.stringify(obj)); @@ -316,7 +339,7 @@ function Validator(schema, namespace, namedTypes) { case 'bytes': throw new InvalidSchemaError('not yet implemented: ' + schema.type); case 'string': - if (_.isString(obj)) { // TODO: handle NaN? + if (isString(obj)) { // TODO: handle NaN? return true; } else { throw new ValidationError('Expected Javascript string for Avro string, got ' + JSON.stringify(obj)); @@ -331,8 +354,8 @@ function Validator(schema, namespace, namedTypes) { // are probably buggy. var _namedTypes = namedTypes || {}; var _saveNamedType = function(fullyQualifiedTypeName, schema) { - if (_.has(_namedTypes, fullyQualifiedTypeName)) { - if (!_.isEqual(_namedTypes[fullyQualifiedTypeName], schema)) { + if (Object.hasOwn(_namedTypes, fullyQualifiedTypeName)) { + if (!util.isDeepStrictEqual(_namedTypes[fullyQualifiedTypeName], schema)) { throw new InvalidSchemaError('conflicting definitions for type ' + fullyQualifiedTypeName + ': ' + JSON.stringify(_namedTypes[fullyQualifiedTypeName]) + ' and ' + JSON.stringify(schema)); } } else { @@ -341,7 +364,7 @@ function Validator(schema, namespace, namedTypes) { }; var _lookupTypeByFullyQualifiedName = function(fullyQualifiedTypeName) { - if (_.has(_namedTypes, fullyQualifiedTypeName)) { + if (Object.hasOwn(_namedTypes, fullyQualifiedTypeName)) { return _namedTypes[fullyQualifiedTypeName]; } else { return null; @@ -349,29 +372,29 @@ function Validator(schema, namespace, namedTypes) { }; var _parseNamedType = function(schema, namespace) { - if (_.contains(AvroSpec.PrimitiveTypes, schema)) { + if (AvroSpec.PrimitiveTypes.includes(schema)) { return new Primitive(schema); - } else if (!_.isNull(_lookupTypeByFullyQualifiedName(makeFullyQualifiedTypeName(schema, namespace)))) { + } else if (_lookupTypeByFullyQualifiedName(makeFullyQualifiedTypeName(schema, namespace)) !== null) { return _lookupTypeByFullyQualifiedName(makeFullyQualifiedTypeName(schema, namespace)); } else { - throw new InvalidSchemaError('unknown type name: ' + JSON.stringify(schema) + '; known type names are ' + JSON.stringify(_.keys(_namedTypes))); + throw new InvalidSchemaError('unknown type name: ' + JSON.stringify(schema) + '; known type names are ' + JSON.stringify(Object.keys(_namedTypes))); } }; var _parseSchema = function(schema, parentSchema, namespace) { - if (_.isNull(schema) || _.isUndefined(schema)) { + if (schema == null) { throw new InvalidSchemaError('schema is null, in parentSchema: ' + JSON.stringify(parentSchema)); - } else if (_.isString(schema)) { + } else if (isString(schema)) { return _parseNamedType(schema, namespace); - } else if (_.isObject(schema) && !_.isArray(schema)) { + } else if (isObject(schema) && !Array.isArray(schema)) { if (schema.type === 'record') { - var newRecord = new Record(schema.name, schema.namespace, _.map(schema.fields, function(field) { + var newRecord = new Record(schema.name, schema.namespace, schema.fields.map(function(field) { return new Field(field.name, _parseSchema(field.type, schema, schema.namespace || namespace)); })); _saveNamedType(makeFullyQualifiedTypeName(schema, namespace), newRecord); return newRecord; } else if (schema.type === 'enum') { - if (_.has(schema, 'symbols')) { + if (Object.hasOwn(schema, 'symbols')) { var newEnum = new Enum(schema.symbols); _saveNamedType(makeFullyQualifiedTypeName(schema, namespace), newEnum); return newEnum; @@ -379,27 +402,27 @@ function Validator(schema, namespace, namedTypes) { throw new InvalidSchemaError('enum must specify symbols, got ' + JSON.stringify(schema)); } } else if (schema.type === 'array') { - if (_.has(schema, 'items')) { + if (Object.hasOwn(schema, 'items')) { return new AvroArray(_parseSchema(schema.items, schema, namespace)); } else { throw new InvalidSchemaError('array must specify "items" schema, got ' + JSON.stringify(schema)); } } else if (schema.type === 'map') { - if (_.has(schema, 'values')) { + if (Object.hasOwn(schema, 'values')) { return new Map(_parseSchema(schema.values, schema, namespace)); } else { throw new InvalidSchemaError('map must specify "values" schema, got ' + JSON.stringify(schema)); } - } else if (_.has(schema, 'type') && _.contains(AvroSpec.PrimitiveTypes, schema.type)) { + } else if (Object.hasOwn(schema, 'type') && AvroSpec.PrimitiveTypes.includes(schema.type)) { return _parseNamedType(schema.type, namespace); } else { throw new InvalidSchemaError('not yet implemented: ' + schema.type); } - } else if (_.isArray(schema)) { - if (_.isEmpty(schema)) { + } else if (Array.isArray(schema)) { + if (schema.length === 0) { throw new InvalidSchemaError('unions must have at least 1 branch'); } - var branchTypes = _.map(schema, function(branchType) { return _parseSchema(branchType, schema, namespace); }); + var branchTypes = schema.map(function(branchType) { return _parseSchema(branchType, schema, namespace); }); return new Union(branchTypes, namespace); } else { throw new InvalidSchemaError('unexpected Javascript type for schema: ' + (typeof schema)); @@ -417,8 +440,8 @@ Validator.validate = function(schema, obj) { function ProtocolValidator(protocol) { this.validate = function(typeName, obj) { var fullyQualifiedTypeName = makeFullyQualifiedTypeName(typeName, protocol.namespace); - if (!_.has(_typeSchemaValidators, fullyQualifiedTypeName)) { - throw new ProtocolValidationError('Protocol does not contain definition for type ' + JSON.stringify(fullyQualifiedTypeName) + ' (fully qualified from input "' + typeName + '"); known types are ' + JSON.stringify(_.keys(_typeSchemaValidators))); + if (!Object.hasOwn(_typeSchemaValidators, fullyQualifiedTypeName)) { + throw new ProtocolValidationError('Protocol does not contain definition for type ' + JSON.stringify(fullyQualifiedTypeName) + ' (fully qualified from input "' + typeName + '"); known types are ' + JSON.stringify(Object.keys(_typeSchemaValidators))); } return _typeSchemaValidators[fullyQualifiedTypeName].validate(obj); }; @@ -426,11 +449,11 @@ function ProtocolValidator(protocol) { var _typeSchemaValidators = {}; var _initSchemaValidators = function(protocol) { var namedTypes = {}; - if (!_.has(protocol, 'protocol') || !_.isString(protocol.protocol)) { + if (protocol == null || !Object.hasOwn(protocol, 'protocol') || !isString(protocol.protocol)) { throw new InvalidProtocolError('Protocol must contain a "protocol" attribute with a string value'); } - if (_.isArray(protocol.types)) { - _.each(protocol.types, function(typeSchema) { + if (Array.isArray(protocol.types)) { + protocol.types.forEach(function(typeSchema) { var schemaValidator = new Validator(typeSchema, protocol.namespace, namedTypes); var fullyQualifiedTypeName = makeFullyQualifiedTypeName(typeSchema, protocol.namespace); _typeSchemaValidators[fullyQualifiedTypeName] = schemaValidator; diff --git a/lang/js/package-lock.json b/lang/js/package-lock.json index 3e32efec013..0382d6e9832 100644 --- a/lang/js/package-lock.json +++ b/lang/js/package-lock.json @@ -8,9 +8,6 @@ "name": "avro-js", "version": "1.13.0-SNAPSHOT", "license": "Apache-2.0", - "dependencies": { - "underscore": "^1.13.2" - }, "devDependencies": { "jshint": "^2.13.4", "mocha": "11.7.6", @@ -2739,12 +2736,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/underscore": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", - "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", - "license": "MIT" - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/lang/js/package.json b/lang/js/package.json index 258a3e1a025..a28b71fc11f 100644 --- a/lang/js/package.json +++ b/lang/js/package.json @@ -41,9 +41,7 @@ "interop-data-generate": "node test/interop_data_generate.js", "interop-data-test": "mocha test/interop_data_test.js" }, - "dependencies": { - "underscore": "^1.13.2" - }, + "dependencies": {}, "devDependencies": { "nyc": "^18.0.0", "jshint": "^2.13.4", diff --git a/lang/js/test/test_deprecated_validator.js b/lang/js/test/test_deprecated_validator.js new file mode 100644 index 00000000000..d2fcb5d8000 --- /dev/null +++ b/lang/js/test/test_deprecated_validator.js @@ -0,0 +1,589 @@ +/* jshint node: true, mocha: true */ + +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +var validator = require('../etc/deprecated/validator'), + Validator = validator.Validator, + ProtocolValidator = validator.ProtocolValidator, + assert = require('assert'); + +describe('deprecated validator', function () { + it('nonexistent/null/undefined', function () { + assert.throws(function() { return new Validator(); }); + assert.throws(function() { return new Validator(null); }); + assert.throws(function() { return new Validator(undefined); }); + }); + it('unrecognized primitive type name', function () { + assert.throws(function() { return new Validator('badtype'); }); + }); + it('invalid schema javascript type', function () { + assert.throws(function() { return new Validator(123); }); + assert.throws(function() { return new Validator(function() { }); }); + }); + + // Primitive types + it('null', function () { + assert(Validator.validate('null', null)); + assert(Validator.validate('null', undefined)); + assert.throws(function() { Validator.validate('null', 1); }); + assert.throws(function() { Validator.validate('null', 'a'); }); + }); + it('boolean', function () { + assert(Validator.validate('boolean', true)); + assert(Validator.validate('boolean', false)); + assert.throws(function() { Validator.validate('boolean', null); }); + assert.throws(function() { Validator.validate('boolean', 1); }); + assert.throws(function() { Validator.validate('boolean', 'a'); }); + }); + it('int', function () { + assert(Validator.validate('int', 1)); + assert(Validator.validate('long', Math.pow(2, 31) - 1)); + assert.throws(function() { Validator.validate('int', 1.5); }); + assert.throws(function() { Validator.validate('int', Math.pow(2, 40)); }); + assert.throws(function() { Validator.validate('int', null); }); + assert.throws(function() { Validator.validate('int', 'a'); }); + }); + it('long', function () { + assert(Validator.validate('long', 1)); + assert(Validator.validate('long', Math.pow(2, 63) - 1)); + assert.throws(function() { Validator.validate('long', 1.5); }); + assert.throws(function() { Validator.validate('long', Math.pow(2, 70)); }); + assert.throws(function() { Validator.validate('long', null); }); + assert.throws(function() { Validator.validate('long', 'a'); }); + }); + it('float', function () { + assert(Validator.validate('float', 1)); + assert(Validator.validate('float', 1.5)); + assert.throws(function() { Validator.validate('float', 'a'); }); + assert.throws(function() { Validator.validate('float', null); }); + }); + it('double', function () { + assert(Validator.validate('double', 1)); + assert(Validator.validate('double', 1.5)); + assert.throws(function() { Validator.validate('double', 'a'); }); + assert.throws(function() { Validator.validate('double', null); }); + }); + it('bytes', function () { + // not implemented yet + assert.throws(function() { Validator.validate('bytes', 1); }); + }); + it('string', function () { + assert(Validator.validate('string', 'a')); + assert.throws(function() { Validator.validate('string', 1); }); + assert.throws(function() { Validator.validate('string', null); }); + }); + it('boxed primitive values', function () { + assert(Validator.validate('string', Object('a'))); + assert(Validator.validate('boolean', Object(true))); + assert(Validator.validate('float', Object(1))); + assert.throws(function() { Validator.validate('int', Object(1)); }); + }); + it('special number values', function () { + assert(Validator.validate('float', NaN)); + assert.throws(function() { Validator.validate('int', NaN); }); + }); + + // Records + it('empty-record', function () { + var schema = {type: 'record', name: 'EmptyRecord', fields: []}; + assert(Validator.validate(schema, {})); + assert(Validator.validate(schema, function () {})); + assert.throws(function() { Validator.validate(schema, 1); }); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, 'a'); }); + }); + it('record-with-string', function () { + var schema = {type: 'record', name: 'EmptyRecord', fields: [{name: 'stringField', type: 'string'}]}; + assert(Validator.validate(schema, {stringField: 'a'})); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, {stringField: 1}); }); + assert.throws(function() { Validator.validate(schema, {stringField: []}); }); + assert.throws(function() { Validator.validate(schema, {stringField: {}}); }); + assert.throws(function() { Validator.validate(schema, {stringField: null}); }); + assert.throws(function() { Validator.validate(schema, {stringField: 'a', unexpectedField: 'a'}); }); + }); + it('record-with-string-and-number', function () { + var schema = {type: 'record', name: 'EmptyRecord', fields: [{name: 'stringField', type: 'string'}, {name: 'intField', type: 'int'}]}; + assert(Validator.validate(schema, {stringField: 'a', intField: 1})); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, {stringField: 'a'}); }); + assert.throws(function() { Validator.validate(schema, {intField: 1}); }); + assert.throws(function() { Validator.validate(schema, {stringField: 'a', intField: 1, unexpectedField: 'a'}); }); + }); + it('nested-record-with-namespace-relative', function () { + var schema = {type: 'record', namespace: 'x.y.z', name: 'RecordA', fields: [{name: 'recordBField1', type: ['null', {type: 'record', name: 'RecordB', fields: []}]}, {name: 'recordBField2', type: 'RecordB'}]}; + assert(Validator.validate(schema, {recordBField1: null, recordBField2: {}})); + assert(Validator.validate(schema, {recordBField1: {'x.y.z.RecordB': {}}, recordBField2: {}})); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, {recordBField1: null}); }); + assert.throws(function() { Validator.validate(schema, {recordBField2: {}}); }); + assert.throws(function() { Validator.validate(schema, {recordBField1: {'RecordB': {}}, recordBField2: {}}); }); + }); + it('repeated named types', function () { + var schema = { + type: 'record', + name: 'Outer', + fields: [ + {name: 'first', type: {type: 'record', name: 'Inner', fields: []}}, + {name: 'second', type: {type: 'record', name: 'Inner', fields: []}} + ] + }; + assert(Validator.validate(schema, {first: {}, second: {}})); + + schema.fields[1].type.fields.push({name: 'value', type: 'string'}); + assert.throws(function() { + Validator.validate(schema, {first: {}, second: {value: 'a'}}); + }); + }); + it('deeply nested repeated named types', function () { + var nestedType = { + type: 'record', + name: 'Nested', + fields: [{ + name: 'leaf', + type: { + type: 'record', + name: 'Leaf', + fields: [{name: 'value', type: 'string'}] + } + }] + }; + var schema = { + type: 'record', + name: 'Outer', + fields: [ + {name: 'first', type: nestedType}, + {name: 'second', type: JSON.parse(JSON.stringify(nestedType))} + ] + }; + var obj = { + first: {leaf: {value: 'a'}}, + second: {leaf: {value: 'b'}} + }; + assert(Validator.validate(schema, obj)); + + schema.fields[1].type.fields[0].type.fields[0].type = 'int'; + assert.throws(function() { + Validator.validate(schema, obj); + }); + }); + it('repeated named enums', function () { + var enumType = { + type: 'enum', + name: 'Choice', + symbols: ['A', 'B'] + }; + var schema = { + type: 'record', + name: 'Outer', + fields: [ + {name: 'first', type: enumType}, + {name: 'second', type: JSON.parse(JSON.stringify(enumType))} + ] + }; + assert(Validator.validate(schema, {first: 'A', second: 'B'})); + + schema.fields[1].type.symbols.push('C'); + assert.throws(function() { + Validator.validate(schema, {first: 'A', second: 'C'}); + }); + }); + it('nested-record-with-namespace-absolute', function () { + var schema = {type: 'record', namespace: 'x.y.z', name: 'RecordA', fields: [{name: 'recordBField1', type: ['null', {type: 'record', name: 'RecordB', fields: []}]}, {name: 'recordBField2', type: 'x.y.z.RecordB'}]}; + assert(Validator.validate(schema, {recordBField1: null, recordBField2: {}})); + assert(Validator.validate(schema, {recordBField1: {'x.y.z.RecordB': {}}, recordBField2: {}})); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, {recordBField1: null}); }); + assert.throws(function() { Validator.validate(schema, {recordBField2: {}}); }); + assert.throws(function() { Validator.validate(schema, {recordBField1: {'RecordB': {}}, recordBField2: {}}); }); + }); + + + // Enums + it('enum', function () { + var schema = {type: 'enum', name: 'Colors', symbols: ['Red', 'Blue']}; + assert(Validator.validate(schema, 'Red')); + assert(Validator.validate(schema, 'Blue')); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, undefined); }); + assert.throws(function() { Validator.validate(schema, 'NotAColor'); }); + assert.throws(function() { Validator.validate(schema, ''); }); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, []); }); + assert.throws(function() { Validator.validate(schema, 1); }); + }); + + // Unions + it('union', function () { + var schema = ['string', 'int']; + assert(Validator.validate(schema, {string: 'a'})); + assert(Validator.validate(schema, {int: 1})); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, undefined); }); + assert.throws(function() { Validator.validate(schema, 'a'); }); + assert.throws(function() { Validator.validate(schema, 1); }); + assert.throws(function() { Validator.validate(schema, {string: 'a', int: 1}); }); + assert.throws(function() { Validator.validate(schema, []); }); + }); + + it('union with null', function () { + var schema = ['string', 'null']; + assert(Validator.validate(schema, {string: 'a'})); + assert(Validator.validate(schema, null)); + assert.throws(function() { Validator.validate(schema, undefined); }); + }); + + it('nested union', function () { + var schema = ['string', {type: 'int'}]; + assert(Validator.validate(schema, {string: 'a'})); + assert(Validator.validate(schema, {int: 1})); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, undefined); }); + assert.throws(function() { Validator.validate(schema, 'a'); }); + assert.throws(function() { Validator.validate(schema, 1); }); + assert.throws(function() { Validator.validate(schema, {string: 'a', int: 1}); }); + assert.throws(function() { Validator.validate(schema, []); }); + }); + + // Arrays + it('array', function () { + var schema = {type: "array", items: "string"}; + assert(Validator.validate(schema, [])); + assert(Validator.validate(schema, ["a"])); + assert(Validator.validate(schema, ["a", "b", "a"])); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, undefined); }); + assert.throws(function() { Validator.validate(schema, 'a'); }); + assert.throws(function() { Validator.validate(schema, 1); }); + assert.throws(function() { Validator.validate(schema, {}); }); + assert.throws(function() { Validator.validate(schema, {"1": "a"}); }); + assert.throws(function() { Validator.validate(schema, {1: "a"}); }); + assert.throws(function() { Validator.validate(schema, {1: "a", "b": undefined}); }); + assert.throws(function() { var a = {}; a[0] = "a"; Validator.validate(schema, a); }); + assert.throws(function() { Validator.validate(schema, [1]); }); + assert.throws(function() { Validator.validate(schema, [1, "a"]); }); + assert.throws(function() { Validator.validate(schema, ["a", 1]); }); + assert.throws(function() { Validator.validate(schema, [null, 1]); }); + }); + + // Maps + it('map', function () { + var schema = {type: "map", values: "string"}; + assert(Validator.validate(schema, {})); + assert(Validator.validate(schema, function () {})); + assert(Validator.validate(schema, {"a": "b"})); + assert(Validator.validate(schema, {"a": "b", "c": "d"})); + assert.throws(function() { Validator.validate(schema, null); }); + assert.throws(function() { Validator.validate(schema, undefined); }); + assert.throws(function() { Validator.validate(schema, 'a'); }); + assert.throws(function() { Validator.validate(schema, 1); }); + assert.throws(function() { Validator.validate(schema, [1]); }); + assert.throws(function() { Validator.validate(schema, {"a": 1}); }); + assert.throws(function() { Validator.validate(schema, {"a": "b", "c": 1}); }); + }); + + // Protocols + it('protocol', function () { + var protocol = {protocol: "Protocol1", namespace: "x.y.z", types: [ + {type: "record", name: "RecordA", fields: []}, + {type: "record", name: "RecordB", fields: [{name: "recordAField", type: "RecordA"}]} + ]}; + assert(ProtocolValidator.validate(protocol, 'RecordA', {})); + assert(ProtocolValidator.validate(protocol, 'x.y.z.RecordA', {})); + assert(ProtocolValidator.validate(protocol, 'RecordB', {recordAField: {}})); + assert(ProtocolValidator.validate(protocol, 'x.y.z.RecordB', {recordAField: {}})); + assert.throws(function() { ProtocolValidator.validate(protocol, 'RecordDoesNotExist', {}); }); + assert.throws(function() { ProtocolValidator.validate(protocol, 'RecordDoesNotExist', null); }); + assert.throws(function() { ProtocolValidator.validate(protocol, 'RecordB', {}); }); + assert.throws(function() { ProtocolValidator.validate(protocol, null, {}); }); + assert.throws(function() { ProtocolValidator.validate(protocol, '', {}); }); + assert.throws(function() { ProtocolValidator.validate(protocol, {}, {}); }); + }); + it('invalid protocol object', function () { + assert.throws(function() { return new ProtocolValidator(); }); + assert.throws(function() { return new ProtocolValidator(null); }); + }); + + // Samples + it('link', function () { + var schema = { + "type" : "record", + "name" : "Bundle", + "namespace" : "aa.bb.cc", + "fields" : [ { + "name" : "id", + "type" : "string" + }, { + "name" : "type", + "type" : "string" + }, { + "name" : "data_", + "type" : [ "null", { + "type" : "record", + "name" : "LinkData", + "fields" : [ { + "name" : "address", + "type" : "string" + }, { + "name" : "title", + "type" : [ "null", "string" ], + "default" : null + }, { + "name" : "excerpt", + "type" : [ "null", "string" ], + "default" : null + }, { + "name" : "image", + "type" : [ "null", { + "type" : "record", + "name" : "Image", + "fields" : [ { + "name" : "url", + "type" : "string" + }, { + "name" : "width", + "type" : "int" + }, { + "name" : "height", + "type" : "int" + } ] + } ], + "default" : null + }, { + "name" : "meta", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + } ] + } ], + "default" : null + }, { + "name" : "atoms_", + "type" : { + "type" : "map", + "values" : { + "type" : "map", + "values" : { + "type" : "record", + "name" : "Atom", + "fields" : [ { + "name" : "index_", + "type" : { + "type" : "record", + "name" : "AtomIndex", + "fields" : [ { + "name" : "type_", + "type" : "string" + }, { + "name" : "id", + "type" : "string" + } ] + } + }, { + "name" : "data_", + "type" : [ "LinkData" ] + } ] + } + } + }, + "default" : { + } + }, { + "name" : "meta_", + "type" : { + "type" : "record", + "name" : "BundleMetadata", + "fields" : [ { + "name" : "date", + "type" : "long", + "default" : 0 + }, { + "name" : "members", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + }, { + "name" : "tags", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + }, { + "name" : "meta", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + }, { + "name" : "votes", + "type" : { + "type" : "map", + "values" : { + "type" : "record", + "name" : "VoteData", + "fields" : [ { + "name" : "date", + "type" : "long" + }, { + "name" : "userName", + "type" : [ "null", "string" ], + "default" : null + }, { + "name" : "direction", + "type" : { + "type" : "enum", + "name" : "VoteDirection", + "symbols" : [ "Up", "Down", "None" ] + } + } ] + } + }, + "default" : { + } + }, { + "name" : "views", + "type" : { + "type" : "map", + "values" : { + "type" : "record", + "name" : "ViewData", + "fields" : [ { + "name" : "userName", + "type" : "string" + }, { + "name" : "count", + "type" : "int" + } ] + } + }, + "default" : { + } + }, { + "name" : "relevance", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + }, { + "name" : "clicks", + "type" : { + "type" : "map", + "values" : "string" + }, + "default" : { + } + } ] + } + } ] + }; + var okObj = { + "id": "https://github.com/sqs/akka-kryo-serialization/subscription", + "type": "link", + "data_": { + "aa.bb.cc.LinkData": { + "address": "https://github.com/sqs/akka-kryo-serialization/subscription", + "title": { + "string": "Sign in · GitHub" + }, + "excerpt": { + "string": "Signup and Pricing Explore GitHub Features Blog Sign in Sign in (Pricing and Signup) Username or Email Password (forgot password) GitHub Links GitHub About Blog Feat" + }, + "image": { + "aa.bb.cc.Image": { + "url": "https://a248.e.akamai.net/assets.github.com/images/modules/header/logov7@4x.png?1340659561", + "width": 280, + "height": 120 + } + }, + "meta": {} + } + }, + "atoms_": { + "link": { + "https://github.com/sqs/akka-kryo-serialization/subscription": { + "index_": { + "type_": "link", + "id": "https://github.com/sqs/akka-kryo-serialization/subscription" + }, + "data_": { + "aa.bb.cc.LinkData": { + "address": "https://github.com/sqs/akka-kryo-serialization/subscription", + "title": { + "string": "Sign in · GitHub" + }, + "excerpt": { + "string": "Signup and Pricing Explore GitHub Features Blog Sign in Sign in (Pricing and Signup) Username or Email Password (forgot password) GitHub Links GitHub About Blog Feat" + }, + "image": { + "aa.bb.cc.Image": { + "url": "https://a248.e.akamai.net/assets.github.com/images/modules/header/logov7@4x.png?1340659561", + "width": 280, + "height": 120 + } + }, + "meta": {} + } + } + } + } + }, + "meta_": { + "date": 1345537530000, + "members": { + "a@a.com": "1" + }, + "tags": { + "blue": "1" + }, + "meta": {}, + "votes": {}, + "views": { + "a@a.com": { + "userName": "John Smith", + "count": 100 + } + }, + "relevance": { + "a@a.com": "1", + "b@b.com": "2" + }, + "clicks": {} + } + }; + + assert(Validator.validate(schema, okObj)); + + var badObj = okObj; // no deep copy since we won't reuse okObj + badObj.meta_.clicks['a@a.com'] = 123; + assert.throws(function() { Validator.validate(schema, badObj); }); + + }); + +});