schema.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. "use strict";
  2. /**
  3. * @fileOverview Enables a schema and validation feature set to your document or other object.
  4. * @module documents/schema
  5. * @requires base
  6. * @requires jjv
  7. * @require lodash
  8. */
  9. var sys = require( "lodash" );
  10. var Validator = require( "jjv" );
  11. var Base = require( "../base" );
  12. /**
  13. * The validator mixin provides access to the features of the JSON validation system
  14. * @exports documents/schema
  15. * @mixin
  16. */
  17. var Schema = Base.compose( [Base], /** @lends documents/schema# */{
  18. constructor : function () {
  19. /**
  20. * The schema that defines the validation rules. This should probably be defined at the prototype for each
  21. * object or model classification. It can be an anonymous schema defined right here, or this can be
  22. * registered schema names to use, or just a single name
  23. *
  24. * @type {object}
  25. * @memberOf documents/schema#
  26. * @name schema
  27. */
  28. /**
  29. * If you want to register multiple schemas, use this property instead
  30. *
  31. * @type {object}
  32. * @memberOf documents/schema#
  33. * @name schemas
  34. */
  35. /**
  36. * The validation environment
  37. * @private
  38. * @type {jjv}
  39. */
  40. var env = new Validator();
  41. /**
  42. * The default name of the scheman when you use anonymous schemas. You can define this at the prototype for classified
  43. * schemas. The can also
  44. *
  45. * @type {string|function():{string}}
  46. * @memberOf documents/schema#
  47. * @name _defaultSchemaName
  48. */
  49. this._defaultSchemaName = sys.result( this, "_defaultSchemaName" ) || sys.uniqueId( "schema" );
  50. /**
  51. * The options to pass to the validator when it runs
  52. * @type {object|function():{object}}
  53. * @name validationOptions
  54. * @memberOf documents/schema#
  55. */
  56. this.validationOptions = sys.defaults( {}, sys.result( this, 'validationOptions' ), {checkRequired : true} );
  57. /**
  58. * Validate an object against the schema
  59. * @returns {object?}
  60. * @method
  61. * @name validate
  62. * @memberOf documents/schema#
  63. * @param {object=} record The record to validate
  64. * @param {string|object=} schemaName The name of a previously registered schema
  65. * @param {object=} options Options to pass to the validator
  66. * @example
  67. * // This supports these signatures:
  68. *
  69. * instance.validate(record, schemaName, options);
  70. *
  71. *
  72. * instance.validate(); // this, this._defaultSchemaName, this.validationOptions
  73. * instance.validate(record); // record, this._defaultSchemaName, this.validationOptions
  74. * instance.validate(schemaName); //this, schemaName, this.validationOptions
  75. * instance.validate(record, schemaName); //record, schemaName, this.validationOptions
  76. * instance.validate(schemaName, options); //this, schemaName, this.validationOptions
  77. */
  78. this.validate = function ( record, schemaName, options ) {
  79. if ( arguments.length === 0 ) {
  80. record = this;
  81. schemaName = this._defaultSchemaName;
  82. options = this.validationOptions;
  83. } else {
  84. if ( sys.isString( record ) ) {
  85. schemaName = record;
  86. record = this;
  87. }
  88. if ( sys.isEmpty( options ) ) {
  89. options = this.validationOptions;
  90. }
  91. }
  92. return env.validate( schemaName, record, options );
  93. };
  94. /**
  95. * Initialize the schema collection by registering the with the handler. You can call this at any time and as often as you like. It will be called once
  96. * by the constructor on any instance schemas
  97. * @method
  98. * @name registerSchemas
  99. * @memberOf documents/schema#
  100. * @param {hash} schemas A hash of schemas where the key is the name of the schema
  101. */
  102. this.registerSchemas = function ( schemas ) {
  103. var schema = sys.result( this, "schema" );
  104. var schemas = schemas || sys.result( this, "schemas" );
  105. if ( !sys.isEmpty( schema ) ) {
  106. env.addSchema( this._defaultSchemaName, schema );
  107. }
  108. if ( !sys.isEmpty( schemas ) ) {
  109. sys.each( schemas, function ( val, key ) {
  110. env.addSchema( val, key );
  111. } );
  112. }
  113. };
  114. /**
  115. * Extracts only the elements of the object that are defined in the schema
  116. * @memberOf documents/schema#
  117. * @name extract
  118. * @param {object=} record The record to extract from
  119. * @param {string=} schema The name of the schema to attach
  120. * @method
  121. */
  122. this.extract = function ( record, schema ) {
  123. if ( arguments.length === 0 ) {
  124. record = this;
  125. schema = this._defaultSchemaName;
  126. }
  127. if ( sys.isString( record ) ) {
  128. schema = record;
  129. record = this;
  130. }
  131. };
  132. /**
  133. * Create a type to be used in your schemas to define new validators
  134. * @memberOf documents/schema#
  135. * @name addType
  136. * @method
  137. * @param {string} name The name of the type
  138. * @param {function(object)} operation What to do with the type.
  139. * @param {object} operation.value The value to validation
  140. * @returns {boolean}
  141. */
  142. this.addType = env.addType;
  143. /**
  144. * It is also possible to add support for additional string formats through the addFormat function.
  145. * @memberOf documents/schema#
  146. * @name addFormat
  147. * @method
  148. * @param {string} name The name of the formatter
  149. * @param {function(object)} formatter How to format it
  150. * @param {object} formatter.value The value to format
  151. * @returns {boolean}
  152. */
  153. this.addFormat = env.addFormat;
  154. /**
  155. * It is possible to add support for custom checks (i.e., minItems, maxItems, minLength, maxLength, etc.) through the addCheck function
  156. * @memberOf documents/schema#
  157. * @name addCheck
  158. * @method
  159. * @param {string} name The name of the check
  160. * @param {function(...object)} formatter Perform the check
  161. * @param {object} formatter.value The value to check followed by any parameters from the schema
  162. * @returns {boolean}
  163. */
  164. this.addCheck = env.addCheck;
  165. /**
  166. * Custom coercion rules
  167. *
  168. * @memberOf documents/schema#
  169. * @name addTypeCoercion
  170. * @method
  171. * @param {string} name The name of the coercion
  172. * @param {function(object)} coercer Perform the coercion
  173. * @param {object} coercer.value The value to coerce
  174. * @returns {boolean}
  175. */
  176. this.addTypeCoercion = env.addTypeCoercion;
  177. /**
  178. * Get a registered schema by name
  179. * @param {string=} schemaName
  180. * @returns {object?}
  181. * @memberOf documents/schema#
  182. * @name getSchema
  183. * @method
  184. */
  185. this.getSchema = function ( schemaName ) {
  186. if ( sys.isEmpty( schemaName ) || !sys.isString() ) {
  187. schemaName = this._defaultSchemaName;
  188. }
  189. return env.schema[schemaName];
  190. }
  191. },
  192. /**
  193. * This method will create a new object that contains only the fields and no methods or other artifacts. This is useful
  194. * for creating objects to pass over the wire or save in a table. This is not deeply copied, so changes made to the
  195. * extracted object will be represented in this class for reference objects.
  196. *
  197. * @param {string=} schema The schema name to use
  198. * @param {object=} src The object to extract fields from
  199. * @return {object} Data-only version of the class instance.
  200. */
  201. extract : function ( schemaName, src ) {
  202. if ( sys.isObject( schemaName ) ) {
  203. src = schema;
  204. schemaName = this._defaultSchemaName;
  205. }
  206. if ( sys.isEmpty( src ) ) {
  207. src = this;
  208. }
  209. if ( sys.isFunction( src.toJSON ) ) {
  210. src = src.toJSON();
  211. }
  212. var schema = this.getSchema( schemaName ) || {};
  213. var newobj = {};
  214. sys.each( schema.properties, function ( prop, propname ) {
  215. if ( prop.properties && !sys.isUndefined( src[ propname ] ) ) {
  216. newobj[ propname ] = this.extract( prop, src[propname] );
  217. } else if ( !sys.isUndefined( src[ propname ] ) ) {
  218. newobj[ propname ] = src[ propname ];
  219. }
  220. }, this );
  221. return newobj;
  222. },
  223. /**
  224. * Builds a default document based on the schema. What this does is create a document from schema and for each property
  225. * that has a default value or is required, the resultant object will contain that property. It is useful for extending
  226. * values from some source that may be incomplete, like options or some such.
  227. * @param {json-schema} schema A schema to use to create the default document
  228. * @returns {object?}
  229. * @name defaultDoc
  230. * @memberOf documents/schema#
  231. * @method
  232. */
  233. defaultDoc : function ( schemaName ) {
  234. if ( sys.isEmpty( schemaName ) ) {
  235. schemaName = this._defaultSchemaName;
  236. }
  237. var newdoc = {};
  238. var schema;
  239. if ( sys.isObject( schemaName ) ) {
  240. schema = schemaName;
  241. } else {
  242. schema = this.getSchema( schemaName ) || {};
  243. }
  244. sys.each( schema.properties, function ( val, key ) {
  245. var def = val[ "default" ]; // keyword and all that
  246. if ( val.type === "object" && !sys.isEmpty( val.properties ) ) {
  247. newdoc[ key ] = this.defaultDoc( val );
  248. } else {
  249. if ( sys.isFunction( def ) || sys.isBoolean( def ) || sys.isNumber( def ) || !sys.isEmpty( def ) ) {
  250. if ( sys.isFunction( def ) ) {
  251. newdoc[ key ] = def( schema );
  252. } else {
  253. newdoc[ key ] = def;
  254. }
  255. } else if ( val.required ) {
  256. if ( val.type === 'string' ) {
  257. newdoc[ key ] = null;
  258. } else if ( val.type === 'object' ) {
  259. newdoc[ key ] = {};
  260. } else if ( val.type === 'array' ) {
  261. newdoc[ key ] = [];
  262. } else {
  263. newdoc[ key ] = null;
  264. }
  265. }
  266. }
  267. }, this );
  268. return newdoc;
  269. }
  270. } );
  271. module.exports = Schema;