├── .gitignore
├── tsconfig.json
├── typings
└── tsd.d.ts
├── tsd.json
├── package.json
├── index.ts
├── README.md
├── index.js
└── test
├── test.ts
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | typings
3 | !typings/tsd.d.ts
4 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "noImplicitAny": false,
6 | "sourceMap": false
7 | }
8 | }
--------------------------------------------------------------------------------
/typings/tsd.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 | ///
5 | ///
6 |
--------------------------------------------------------------------------------
/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "borisyankov/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "typings",
6 | "bundle": "typings/tsd.d.ts",
7 | "installed": {
8 | "node/node.d.ts": {
9 | "commit": "7dc3b09e3ad2a8de14330d6c84efe3e6d76fd79b"
10 | },
11 | "chai/chai.d.ts": {
12 | "commit": "7dc3b09e3ad2a8de14330d6c84efe3e6d76fd79b"
13 | },
14 | "mocha/mocha.d.ts": {
15 | "commit": "7dc3b09e3ad2a8de14330d6c84efe3e6d76fd79b"
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mongoose-ajv-plugin",
3 | "version": "2.0.0",
4 | "description": "AJV plugin for Mongoose",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "mocha"
8 | },
9 | "author": "Jason Thorpe",
10 | "license": "ISC",
11 | "dependencies": {
12 | "ajv": "^5.2.0",
13 | "mongoose": "^4.10.8",
14 | "semver": "^5.3.0"
15 | },
16 | "devDependencies": {
17 | "bluebird": "^3.4.6",
18 | "chai": "^3.5.0",
19 | "chai-as-promised": "^6.0.0",
20 | "mocha": "^3.1.2"
21 | },
22 | "directories": {
23 | "test": "test"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/jdthorpe/mongoose-ajv-plugin.git"
28 | },
29 | "keywords": [
30 | "mongoose",
31 | "ajv",
32 | "json-schema",
33 | "json",
34 | "schema",
35 | "JSONschema"
36 | ],
37 | "bugs": {
38 | "url": "https://github.com/jdthorpe/mongoose-ajv-plugin/issues"
39 | },
40 | "homepage": "https://github.com/jdthorpe/mongoose-ajv-plugin#readme"
41 | }
42 |
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------
2 | // Programmer: Jason Thorpe
3 | // Language: typescript
4 | // Purpose:
5 | // Comments:
6 | // --------------------------------------------------------------------------------
7 |
8 | ///
9 |
10 | var semver = require('semver');
11 | var mongoose = require('mongoose');
12 | var AJV = require('ajv');
13 | var ValidationError = mongoose.Error.ValidationError;
14 | var ValidatorError = mongoose.Error.ValidatorError;
15 |
16 | export = function (schema, options) {
17 |
18 | // GET THE AJV INSTANCE
19 | var ajv = (options && options.ajv) || new AJV();
20 | if(options && options.plugins){
21 | for(let key in options.plugins){
22 | if(!options.plugins.hasOwnProperty(key))
23 | continue;
24 | if(options.plugins[key].version){
25 | var version = require(key+"/package.json").version;
26 | if(!semver.satisfies(version, options.plugins[key].version))
27 | throw new Error(`Installed version "${version}" of plugin "${key}" does not satisify "${options.plugins[key].version}"`);
28 | }
29 | if(options.plugins[key].options){
30 | require(key)(ajv,options.plugins[key].options)
31 | }else{
32 | require(key)(ajv)
33 | }
34 | }
35 | }
36 |
37 | // COMPILE THE ATTRIBUTE SCHEMA
38 | var schemata = {},SCHEMA;
39 | // COMPILE THE OVERALL DOCUMENT SCHEMA
40 | if(schema.path("ajv-schema")){
41 | try{
42 | var data = schema.paths["ajv-schema"].options;
43 | SCHEMA = ajv.compile(data)
44 | schema.remove("ajv-schema");
45 | }catch(err){
46 | throw new Error(`Failed to compile document schema with error message: ${err.message} `);
47 | }
48 | }
49 |
50 | schema.eachPath((key) => {
51 | if(! schema.path(key).options["ajv-schema"]){
52 | return ;
53 | }
54 | try{
55 | console.log(`creating schema for path: "${key}" ... `)
56 | var $schema = ajv.compile(schema.path(key).options["ajv-schema"])
57 | console.log(`SUCCESS!\n`)
58 | }catch(err){
59 | console.log(`FAILED.\n`)
60 | throw new Error(`Failed to compile schema for path "${key}" with error message: ${err.message} `);
61 | }
62 | schemata[key] = $schema;
63 | });
64 |
65 | schema.post('validate', function (data,next) {
66 | // APPLY THE OVERALL DOCUMENT SCHEMA
67 | if(SCHEMA && !SCHEMA(data)){
68 | var error = new ValidationError(data);
69 | error.message += "; ";
70 | console.log("SCHEMA.errors.length: ",SCHEMA.errors.length)
71 | error.message += JSON.stringify(SCHEMA.errors.map((x) => `'${x.schemaPath}' ${x.message}`));
72 | error.errors.record = new ValidatorError('record', 'Overall object does not match JSON-schema', 'notvalid', data);
73 | error.errors.record.errors = SCHEMA.errors;
74 | return next(error);
75 | }
76 | try{
77 | // APPLY THE ATTRIBUTE SCHEMA
78 | var $schema;
79 | for(var key in schemata){
80 | if(data[key] === undefined){
81 | // use the Mongoose `required` validator for validating the presence of the attribute
82 | continue
83 | }
84 | console.log("validating schema path", key);
85 | $schema = schemata[key];
86 | if(!$schema(data[key])){
87 | var error = new ValidationError(data);
88 | error.message += `; '${key}' attribute does not match it's JSON-schema: `;
89 | error.message += JSON.stringify($schema.errors.map((x) => `'${x.schemaPath}' ${x.message}`));
90 | error.errors[key] = new ValidatorError(key, key+' does not match JSON-schema', 'notvalid', data);
91 | error.errors[key].errors = schemata[key].errors;
92 | return next(error);
93 | }
94 | }
95 | return next();
96 | }catch(err){
97 | return next(err);
98 | }
99 | })
100 |
101 | }
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## The Problem
3 |
4 | You love Mongoose for all it's convenience methods and
5 | valiate-before-saving logic, but but you store complex objects using
6 | `Schema.Types.Mixed` which lacks validation in Mongoose, or you just wish
7 | you could validate objects, strings, etc. using a richer
8 | [JSON-schema](http://json-schema.org/) vocabulary than is included with
9 | Mongoose.
10 |
11 | ## The Solution
12 |
13 | The `mongoose-ajv-plugin` lets you use the awesome [AJV JSON-Schema
14 | validation library][ajv], to validate individual attributes or entire
15 | documents, giving you access to it's rich extensible schema vocabulary and convenience
16 | formats like [email][formats], [Date][formats], [hostname][formats], ect.
17 |
18 | [ajv]: https://github.com/epoberezkin/ajv "AJV"
19 | [formats]: https://github.com/epoberezkin/ajv#formats "String Formats"
20 | [validate]: http://mongoosejs.com/docs/api.html#schematype_SchemaType-validate "Validation"
21 |
22 | ## Getting Started
23 |
24 | Import mongoose and add in the `mongoose-ajv-plugin`:
25 |
26 | ```JavaScript
27 | var mongoose = require("mongoose");
28 | mongoose.plugin(require("mongoose-ajv-plugin"))
29 | ```
30 |
31 | Now use your favorite AJV Schema, such as the `ajv_contact_schema` defined
32 | below, to validate entire documents using the `"ajv-schema"` keyword, like
33 | so:
34 |
35 | ```JavaScript
36 | var Contact_schema = new mongoose.Schema({
37 | "name": String ,
38 | "email": String,
39 | "birthday": String,
40 | // let AJV validate this entire document
41 | "ajv-schema": ajv_contact_schema
42 | });
43 | ```
44 |
45 | Or use AJV to validate one or more attributes of a document using the `"ajv-schema"` option:
46 |
47 | ```JavaScript
48 | // use AJV to validate fields within a document
49 | var Player_schema = new Schema({
50 | "user_name": String,
51 | "rank": Number,
52 | "ip_address": {
53 | "type": String,
54 | // let AJV validate this string attribute
55 | "ajv-schema": {
56 | "type": 'string',
57 | "format": 'ipv4' /
58 | }
59 | },
60 | "contact-info": {
61 | "type": Schema.Types.Mixed ,
62 | // let AJV validate this nested object
63 | "ajv-schema": contact_json_schema
64 | },
65 | });
66 | ```
67 |
68 | ## Using AJV Extensions
69 |
70 | If you wish to extend the Ajv instance used for validation with additional
71 | [schemata](https://github.com/epoberezkin/ajv#addschemaarrayobjectobject-schema--string-key),
72 | [formats](https://github.com/epoberezkin/ajv#addformatstring-name-stringregexpfunctionobject-format),
73 | or [keywords](https://github.com/epoberezkin/ajv#api-addkeyword), you can
74 | pass your own (extended) ajv instance to the plugin, like so:
75 |
76 | ```JavaScript
77 | // create an Ajv instance
78 | var Ajv = require("ajv");
79 | var ajv = new Ajv();
80 |
81 | // add custom schema, keywords, or formats
82 | ajv.addSchema(...);
83 | // or
84 | ajv.addKewword(...)
85 | // or
86 | ajv.addFormat(...)
87 | // or
88 | require("my-ajv-plugin")(ajv)
89 |
90 | // use this ajv instance to compile every new validator
91 | mongoose.plugin(require("mongoose-ajv-plugin",{"ajv":ajv})
92 |
93 | // or use this ajv instance to compile validators for an individual
94 | // mongoose schema
95 | var my_schema = new mongoose.Schema({...});
96 | my_schema.plugin(require("mongoose-ajv-plugin",{"ajv":ajv})
97 | ```
98 |
99 | ## Contact JSON schema
100 |
101 | And finally, here's the definition of `ajv_contact_schema` used in the
102 | above examples:
103 |
104 | ```JavaScript
105 | var ajv_contact_schema = {
106 | "type":"object",
107 | "properties":{
108 | "name": {
109 | "type":"string"
110 | },
111 | "email": {
112 | "type":"string",
113 | "fomrat":"email"
114 | },
115 | "birthday": {
116 | "oneOf":[
117 | {"$ref":"#/definitions/date"},
118 | {"$ref":"#/definitions/date-time"}
119 | ]
120 | }
121 | },
122 | "required":[
123 | "name",
124 | "email"
125 | ],
126 | "definitions":{
127 | "date":{
128 | "type":"string",
129 | "format":"date"
130 | },
131 | "date-time":{
132 | "type":"string",
133 | "format":"date-time"
134 | }
135 | }
136 | };
137 | ```
138 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // --------------------------------------------------------------------------------
3 | // Programmer: Jason Thorpe
4 | // Language: typescript
5 | // Purpose:
6 | // Comments:
7 | // --------------------------------------------------------------------------------
8 | ///
9 | var semver = require('semver');
10 | var mongoose = require('mongoose');
11 | var AJV = require('ajv');
12 | var ValidationError = mongoose.Error.ValidationError;
13 | var ValidatorError = mongoose.Error.ValidatorError;
14 | module.exports = function (schema, options) {
15 | // GET THE AJV INSTANCE
16 | var ajv = (options && options.ajv) || new AJV();
17 | if (options && options.plugins) {
18 | for (var key in options.plugins) {
19 | if (!options.plugins.hasOwnProperty(key))
20 | continue;
21 | if (options.plugins[key].version) {
22 | var version = require(key + "/package.json").version;
23 | if (!semver.satisfies(version, options.plugins[key].version))
24 | throw new Error("Installed version \"" + version + "\" of plugin \"" + key + "\" does not satisify \"" + options.plugins[key].version + "\"");
25 | }
26 | if (options.plugins[key].options) {
27 | require(key)(ajv, options.plugins[key].options);
28 | }
29 | else {
30 | require(key)(ajv);
31 | }
32 | }
33 | }
34 | // COMPILE THE ATTRIBUTE SCHEMA
35 | var schemata = {}, SCHEMA;
36 | // COMPILE THE OVERALL DOCUMENT SCHEMA
37 | if (schema.path("ajv-schema")) {
38 | try {
39 | var data = schema.paths["ajv-schema"].options;
40 | SCHEMA = ajv.compile(data);
41 | schema.remove("ajv-schema");
42 | }
43 | catch (err) {
44 | throw new Error("Failed to compile document schema with error message: " + err.message + " ");
45 | }
46 | }
47 | schema.eachPath(function (key) {
48 | if (!schema.path(key).options["ajv-schema"]) {
49 | return;
50 | }
51 | try {
52 | console.log("creating schema for path: \"" + key + "\" ... ");
53 | var $schema = ajv.compile(schema.path(key).options["ajv-schema"]);
54 | console.log("SUCCESS!\n");
55 | }
56 | catch (err) {
57 | console.log("FAILED.\n");
58 | throw new Error("Failed to compile schema for path \"" + key + "\" with error message: " + err.message + " ");
59 | }
60 | schemata[key] = $schema;
61 | });
62 | schema.post('validate', function (data, next) {
63 | // APPLY THE OVERALL DOCUMENT SCHEMA
64 | if (SCHEMA && !SCHEMA(data)) {
65 | var error = new ValidationError(data);
66 | error.message += "; ";
67 | console.log("SCHEMA.errors.length: ", SCHEMA.errors.length);
68 | error.message += JSON.stringify(SCHEMA.errors.map(function (x) { return "'" + x.schemaPath + "' " + x.message; }));
69 | error.errors.record = new ValidatorError('record', 'Overall object does not match JSON-schema', 'notvalid', data);
70 | error.errors.record.errors = SCHEMA.errors;
71 | return next(error);
72 | }
73 | try {
74 | // APPLY THE ATTRIBUTE SCHEMA
75 | var $schema;
76 | for (var key in schemata) {
77 | if (data[key] === undefined) {
78 | // use the Mongoose `required` validator for validating the presence of the attribute
79 | continue;
80 | }
81 | console.log("validating schema path", key);
82 | $schema = schemata[key];
83 | if (!$schema(data[key])) {
84 | var error = new ValidationError(data);
85 | error.message += "; '" + key + "' attribute does not match it's JSON-schema: ";
86 | error.message += JSON.stringify($schema.errors.map(function (x) { return "'" + x.schemaPath + "' " + x.message; }));
87 | error.errors[key] = new ValidatorError(key, key + ' does not match JSON-schema', 'notvalid', data);
88 | error.errors[key].errors = schemata[key].errors;
89 | return next(error);
90 | }
91 | }
92 | return next();
93 | }
94 | catch (err) {
95 | return next(err);
96 | }
97 | });
98 | };
99 |
--------------------------------------------------------------------------------
/test/test.ts:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------
2 | // Programmer: Jason Thorpe
3 | // Language: typescript
4 | // Purpose: mocha testing
5 | // Comments:
6 | // --------------------------------------------------------------------------------
7 |
8 | ///
9 | ///
10 | ///
11 |
12 | import chai = require('chai');
13 | var expect = chai.expect;
14 |
15 | var mongoose = require('mongoose');
16 | mongoose.Promise = require('bluebird');
17 | var Schema = mongoose.Schema;
18 |
19 | var ajv_plugin = require('../index')
20 | mongoose.plugin(ajv_plugin)
21 |
22 | var AJV = require('ajv'),
23 | ajv = new AJV();
24 |
25 | ajv.validate({"type":"string","format":"ipv4"},"123.4.5.789")
26 |
27 | //------------------------------
28 | // define an AJV JSONschema:
29 | //------------------------------
30 |
31 | var contact_json_schema = {
32 | "type":"object",
33 | "properties":{
34 | "name": {
35 | "type":"string"
36 | },
37 | "email": {
38 | "type":"string",
39 | "fomrat":"email"
40 | },
41 | "birthday": {
42 | "oneOf":[
43 | {"$ref":"#/definitions/date"},
44 | {"$ref":"#/definitions/date-time"}
45 | ]
46 | }
47 | },
48 | "required":[
49 | "name",
50 | "email"
51 | ],
52 | "definitions":{
53 | "date":{
54 | "type":"string",
55 | "format":"date"
56 | },
57 | "date-time":{
58 | "type":"string",
59 | "format":"date-time"
60 | }
61 | }
62 | };
63 |
64 | var team_json_schema = {
65 | "type":"object",
66 | "properties": {
67 | "team_name": {
68 | "type": "string",
69 | // team names must be between 5 and 30 characters
70 | "minLength": 5,
71 | "maxLength": 30
72 | },
73 | "players": {
74 | "type": "array",
75 | "minItems": 2,
76 | "maxItems": 10,
77 | "items": {
78 | "type": "string"
79 | }
80 | }
81 | }
82 | };
83 |
84 | describe("Schemas",function(){
85 | it("should be valid",function(){
86 | expect(ajv.validateSchema(contact_json_schema)).to.be.true;
87 | expect(ajv.validateSchema(team_json_schema)).to.be.true;
88 | })
89 | })
90 |
91 | // ----------------------------------------
92 | // build the models
93 | // ----------------------------------------
94 |
95 | // use AJV to validate fields within a document
96 | var Player_schema = new Schema({
97 | "user_name": String,
98 | "rank": Number,
99 | "ip_address": {
100 | "type": String, // the mongoose type
101 | "ajv-schema": { // use AJV to validtae this string
102 | "type": 'string', // the JSON schema Type
103 | "format": 'ipv4' // AJV convenience String Format
104 | }
105 | },
106 | "contact": {
107 | "type": Schema.Types.Mixed ,
108 | "ajv-schema": contact_json_schema // use AJV to validate this object
109 | },
110 | });
111 |
112 | // add the AJV plugin to the schema
113 | //-- Player_schema.plugin(ajv_plugin);
114 | // Create a model from the schema
115 | var Player = mongoose.model('Player', Player_schema);
116 |
117 | var Team_schema = new Schema({
118 | "team_name": String,
119 | "players": [String],
120 | "ajv-schema":team_json_schema,
121 | });
122 | //-- Team_schema.plugin(ajv_plugin);
123 | var Team = mongoose.model('Team', Team_schema);
124 |
125 | // ----------------------------------------
126 | // build the model instances
127 | // ----------------------------------------
128 |
129 | var valid_attrs = new Player({
130 |
131 | "user_name": "Felix",
132 | "rank": 5,
133 | "ip_address": "123.45.67.89",
134 | "contact": {
135 | "name":"Jack" ,
136 | "email":"plaza626@email.com",
137 | "birthday": "1925-02-08"
138 | }
139 |
140 | });
141 |
142 | var invalid_string_attribute = new Player({
143 | "user_name": "Oscar",
144 | "rank": 7,
145 | "ip_address": "123.4.5.678", // invalid ip_address
146 | "contact": {
147 | "name":"Walter" ,
148 | "email":"RedWingsFan@poker.com",
149 | "birthday": "1920-10-01" // invalid date format format
150 | },
151 | })
152 |
153 | var invalid_object_attribute = new Player({
154 | "user_name": "Oscar",
155 | "rank": 7,
156 | "ip_address": "123.45.67.89",
157 | "contact": {
158 | "name":"Walter" ,
159 | "email":"RedWingsFan@poker.com",
160 | "birthday": "October 1, 1920" // invalid date format format
161 | },
162 | })
163 |
164 | var invalid_doc = new Team({
165 | "team_name": "Just Me",
166 | "players": ["Bridget"] // too few players
167 | })
168 |
169 | var valid_doc = new Team({
170 | "team_name": "ThursdayNightPoker",
171 | "players": ["Oscar","Felix","Speed","Vinnie","Roy","Murray"]
172 | })
173 |
174 | // ----------------------------------------
175 | // testing
176 | // ----------------------------------------
177 |
178 | describe("Invalid nested object",function(){
179 | it("should not validate",function(done){
180 | invalid_object_attribute.validate(function(err,doc){
181 | if(err){
182 | done();
183 | }else{
184 | done(new Error("Failed to throw an error"));
185 | }
186 | })
187 | })
188 | })
189 |
190 | describe("Invalid string attribute",function(){
191 | it("should not validate",function(done){
192 | invalid_string_attribute.validate(function(err,doc){
193 | if(err){
194 | done();
195 | }else{
196 | done(new Error("Failed to throw an error"));
197 | }
198 | })
199 | })
200 | })
201 |
202 | describe("Valid attributes",function(){
203 | it("should not throw",function(done){
204 | valid_attrs.validate(function(err,doc){
205 | if(err){
206 | done(err);
207 | }else{
208 | done();
209 | }
210 | })
211 | })
212 | })
213 |
214 | describe("Invalid document",function(){
215 | it("should not validate",function(done){
216 | invalid_doc.validate(function(err,doc){
217 | if(err){
218 | done();
219 | }else{
220 | done(new Error("Failed to throw an error"));
221 | }
222 | })
223 | })
224 | })
225 |
226 | describe("Valid document",function(){
227 | it("should not throw",function(done){
228 | valid_doc.validate(function(err,doc){
229 | if(err){
230 | done(err);
231 | }else{
232 | done();
233 | }
234 | })
235 | })
236 | })
237 |
238 |
239 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 | // --------------------------------------------------------------------------------
3 | // Programmer: Jason Thorpe
4 | // Language: typescript
5 | // Purpose: mocha testing
6 | // Comments:
7 | // --------------------------------------------------------------------------------
8 | Object.defineProperty(exports, "__esModule", { value: true });
9 | ///
10 | ///
11 | ///
12 | var chai = require("chai");
13 | var expect = chai.expect;
14 | var mongoose = require('mongoose');
15 | mongoose.Promise = require('bluebird');
16 | var Schema = mongoose.Schema;
17 | var ajv_plugin = require('../index');
18 | mongoose.plugin(ajv_plugin);
19 | var AJV = require('ajv'), ajv = new AJV();
20 | ajv.validate({ "type": "string", "format": "ipv4" }, "123.4.5.789");
21 | //------------------------------
22 | // define an AJV JSONschema:
23 | //------------------------------
24 | var contact_json_schema = {
25 | "type": "object",
26 | "properties": {
27 | "name": {
28 | "type": "string"
29 | },
30 | "email": {
31 | "type": "string",
32 | "fomrat": "email"
33 | },
34 | "birthday": {
35 | "oneOf": [
36 | { "$ref": "#/definitions/date" },
37 | { "$ref": "#/definitions/date-time" }
38 | ]
39 | }
40 | },
41 | "required": [
42 | "name",
43 | "email"
44 | ],
45 | "definitions": {
46 | "date": {
47 | "type": "string",
48 | "format": "date"
49 | },
50 | "date-time": {
51 | "type": "string",
52 | "format": "date-time"
53 | }
54 | }
55 | };
56 | var team_json_schema = {
57 | "type": "object",
58 | "properties": {
59 | "team_name": {
60 | "type": "string",
61 | // team names must be between 5 and 30 characters
62 | "minLength": 5,
63 | "maxLength": 30
64 | },
65 | "players": {
66 | "type": "array",
67 | "minItems": 2,
68 | "maxItems": 10,
69 | "items": {
70 | "type": "string"
71 | }
72 | }
73 | }
74 | };
75 | describe("Schemas", function () {
76 | it("should be valid", function () {
77 | expect(ajv.validateSchema(contact_json_schema)).to.be.true;
78 | expect(ajv.validateSchema(team_json_schema)).to.be.true;
79 | });
80 | });
81 | // ----------------------------------------
82 | // build the models
83 | // ----------------------------------------
84 | // use AJV to validate fields within a document
85 | var Player_schema = new Schema({
86 | "user_name": String,
87 | "rank": Number,
88 | "ip_address": {
89 | "type": String,
90 | "ajv-schema": {
91 | "type": 'string',
92 | "format": 'ipv4' // AJV convenience String Format
93 | }
94 | },
95 | "contact": {
96 | "type": Schema.Types.Mixed,
97 | "ajv-schema": contact_json_schema // use AJV to validate this object
98 | },
99 | });
100 | // add the AJV plugin to the schema
101 | //-- Player_schema.plugin(ajv_plugin);
102 | // Create a model from the schema
103 | var Player = mongoose.model('Player', Player_schema);
104 | var Team_schema = new Schema({
105 | "team_name": String,
106 | "players": [String],
107 | "ajv-schema": team_json_schema,
108 | });
109 | //-- Team_schema.plugin(ajv_plugin);
110 | var Team = mongoose.model('Team', Team_schema);
111 | // ----------------------------------------
112 | // build the model instances
113 | // ----------------------------------------
114 | var valid_attrs = new Player({
115 | "user_name": "Felix",
116 | "rank": 5,
117 | "ip_address": "123.45.67.89",
118 | "contact": {
119 | "name": "Jack",
120 | "email": "plaza626@email.com",
121 | "birthday": "1925-02-08"
122 | }
123 | });
124 | var invalid_string_attribute = new Player({
125 | "user_name": "Oscar",
126 | "rank": 7,
127 | "ip_address": "123.4.5.678",
128 | "contact": {
129 | "name": "Walter",
130 | "email": "RedWingsFan@poker.com",
131 | "birthday": "1920-10-01" // invalid date format format
132 | },
133 | });
134 | var invalid_object_attribute = new Player({
135 | "user_name": "Oscar",
136 | "rank": 7,
137 | "ip_address": "123.45.67.89",
138 | "contact": {
139 | "name": "Walter",
140 | "email": "RedWingsFan@poker.com",
141 | "birthday": "October 1, 1920" // invalid date format format
142 | },
143 | });
144 | var invalid_doc = new Team({
145 | "team_name": "Just Me",
146 | "players": ["Bridget"] // too few players
147 | });
148 | var valid_doc = new Team({
149 | "team_name": "ThursdayNightPoker",
150 | "players": ["Oscar", "Felix", "Speed", "Vinnie", "Roy", "Murray"]
151 | });
152 | // ----------------------------------------
153 | // testing
154 | // ----------------------------------------
155 | describe("Invalid nested object", function () {
156 | it("should not validate", function (done) {
157 | invalid_object_attribute.validate(function (err, doc) {
158 | if (err) {
159 | done();
160 | }
161 | else {
162 | done(new Error("Failed to throw an error"));
163 | }
164 | });
165 | });
166 | });
167 | describe("Invalid string attribute", function () {
168 | it("should not validate", function (done) {
169 | invalid_string_attribute.validate(function (err, doc) {
170 | if (err) {
171 | done();
172 | }
173 | else {
174 | done(new Error("Failed to throw an error"));
175 | }
176 | });
177 | });
178 | });
179 | describe("Valid attributes", function () {
180 | it("should not throw", function (done) {
181 | valid_attrs.validate(function (err, doc) {
182 | if (err) {
183 | done(err);
184 | }
185 | else {
186 | done();
187 | }
188 | });
189 | });
190 | });
191 | describe("Invalid document", function () {
192 | it("should not validate", function (done) {
193 | invalid_doc.validate(function (err, doc) {
194 | if (err) {
195 | done();
196 | }
197 | else {
198 | done(new Error("Failed to throw an error"));
199 | }
200 | });
201 | });
202 | });
203 | describe("Valid document", function () {
204 | it("should not throw", function (done) {
205 | valid_doc.validate(function (err, doc) {
206 | if (err) {
207 | done(err);
208 | }
209 | else {
210 | done();
211 | }
212 | });
213 | });
214 | });
215 |
--------------------------------------------------------------------------------