helpers.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. 'use strict';
  2. var ValidationError = exports.ValidationError = function ValidationError (message, instance, schema, path, name, argument) {
  3. if(Array.isArray(path)){
  4. this.path = path;
  5. this.property = path.reduce(function(sum, item){
  6. return sum + makeSuffix(item);
  7. }, 'instance');
  8. }else if(path !== undefined){
  9. this.property = path;
  10. }
  11. if (message) {
  12. this.message = message;
  13. }
  14. if (schema) {
  15. var id = schema.$id || schema.id;
  16. this.schema = id || schema;
  17. }
  18. if (instance !== undefined) {
  19. this.instance = instance;
  20. }
  21. this.name = name;
  22. this.argument = argument;
  23. this.stack = this.toString();
  24. };
  25. ValidationError.prototype.toString = function toString() {
  26. return this.property + ' ' + this.message;
  27. };
  28. var ValidatorResult = exports.ValidatorResult = function ValidatorResult(instance, schema, options, ctx) {
  29. this.instance = instance;
  30. this.schema = schema;
  31. this.options = options;
  32. this.path = ctx.path;
  33. this.propertyPath = ctx.propertyPath;
  34. this.errors = [];
  35. this.throwError = options && options.throwError;
  36. this.throwFirst = options && options.throwFirst;
  37. this.throwAll = options && options.throwAll;
  38. this.disableFormat = options && options.disableFormat === true;
  39. };
  40. ValidatorResult.prototype.addError = function addError(detail) {
  41. var err;
  42. if (typeof detail == 'string') {
  43. err = new ValidationError(detail, this.instance, this.schema, this.path);
  44. } else {
  45. if (!detail) throw new Error('Missing error detail');
  46. if (!detail.message) throw new Error('Missing error message');
  47. if (!detail.name) throw new Error('Missing validator type');
  48. err = new ValidationError(detail.message, this.instance, this.schema, this.path, detail.name, detail.argument);
  49. }
  50. this.errors.push(err);
  51. if (this.throwFirst) {
  52. throw new ValidatorResultError(this);
  53. }else if(this.throwError){
  54. throw err;
  55. }
  56. return err;
  57. };
  58. ValidatorResult.prototype.importErrors = function importErrors(res) {
  59. if (typeof res == 'string' || (res && res.validatorType)) {
  60. this.addError(res);
  61. } else if (res && res.errors) {
  62. this.errors = this.errors.concat(res.errors);
  63. }
  64. };
  65. function stringizer (v,i){
  66. return i+': '+v.toString()+'\n';
  67. }
  68. ValidatorResult.prototype.toString = function toString(res) {
  69. return this.errors.map(stringizer).join('');
  70. };
  71. Object.defineProperty(ValidatorResult.prototype, "valid", { get: function() {
  72. return !this.errors.length;
  73. } });
  74. module.exports.ValidatorResultError = ValidatorResultError;
  75. function ValidatorResultError(result) {
  76. if(typeof Error.captureStackTrace === 'function'){
  77. Error.captureStackTrace(this, ValidatorResultError);
  78. }
  79. this.instance = result.instance;
  80. this.schema = result.schema;
  81. this.options = result.options;
  82. this.errors = result.errors;
  83. }
  84. ValidatorResultError.prototype = new Error();
  85. ValidatorResultError.prototype.constructor = ValidatorResultError;
  86. ValidatorResultError.prototype.name = "Validation Error";
  87. /**
  88. * Describes a problem with a Schema which prevents validation of an instance
  89. * @name SchemaError
  90. * @constructor
  91. */
  92. var SchemaError = exports.SchemaError = function SchemaError (msg, schema) {
  93. this.message = msg;
  94. this.schema = schema;
  95. Error.call(this, msg);
  96. if(typeof Error.captureStackTrace === 'function'){
  97. Error.captureStackTrace(this, SchemaError);
  98. }
  99. };
  100. SchemaError.prototype = Object.create(Error.prototype,
  101. {
  102. constructor: {value: SchemaError, enumerable: false},
  103. name: {value: 'SchemaError', enumerable: false},
  104. });
  105. var SchemaContext = exports.SchemaContext = function SchemaContext (schema, options, path, base, schemas) {
  106. this.schema = schema;
  107. this.options = options;
  108. if(Array.isArray(path)){
  109. this.path = path;
  110. this.propertyPath = path.reduce(function(sum, item){
  111. return sum + makeSuffix(item);
  112. }, 'instance');
  113. }else{
  114. this.propertyPath = path;
  115. }
  116. this.base = base;
  117. this.schemas = schemas;
  118. };
  119. SchemaContext.prototype.resolve = function resolve (target) {
  120. return (() => resolveUrl(this.base,target))();
  121. };
  122. SchemaContext.prototype.makeChild = function makeChild(schema, propertyName){
  123. var path = (propertyName===undefined) ? this.path : this.path.concat([propertyName]);
  124. var id = schema.$id || schema.id;
  125. let base = (() => resolveUrl(this.base,id||''))();
  126. var ctx = new SchemaContext(schema, this.options, path, base, Object.create(this.schemas));
  127. if(id && !ctx.schemas[base]){
  128. ctx.schemas[base] = schema;
  129. }
  130. return ctx;
  131. };
  132. var FORMAT_REGEXPS = exports.FORMAT_REGEXPS = {
  133. // 7.3.1. Dates, Times, and Duration
  134. 'date-time': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])[tT ](2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])(\.\d+)?([zZ]|[+-]([0-5][0-9]):(60|[0-5][0-9]))$/,
  135. 'date': /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-(3[01]|0[1-9]|[12][0-9])$/,
  136. 'time': /^(2[0-4]|[01][0-9]):([0-5][0-9]):(60|[0-5][0-9])$/,
  137. 'duration': /P(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S)|\d+(D|M(\d+D)?|Y(\d+M(\d+D)?)?)(T\d+(H(\d+M(\d+S)?)?|M(\d+S)?|S))?|\d+W)/i,
  138. // 7.3.2. Email Addresses
  139. // TODO: fix the email production
  140. 'email': /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/,
  141. 'idn-email': /^("(?:[!#-\[\]-\u{10FFFF}]|\\[\t -\u{10FFFF}])*"|[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*)@([!#-'*+\-/-9=?A-Z\^-\u{10FFFF}](?:\.?[!#-'*+\-/-9=?A-Z\^-\u{10FFFF}])*|\[[!-Z\^-\u{10FFFF}]*\])$/u,
  142. // 7.3.3. Hostnames
  143. // 7.3.4. IP Addresses
  144. 'ip-address': /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,
  145. // FIXME whitespace is invalid
  146. 'ipv6': /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/,
  147. // 7.3.5. Resource Identifiers
  148. // TODO: A more accurate regular expression for "uri" goes:
  149. // [A-Za-z][+\-.0-9A-Za-z]*:((/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?)?#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])|/?%[0-9A-Fa-f]{2}|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*(#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|/(/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?)?
  150. 'uri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/,
  151. 'uri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/,
  152. 'iri': /^[a-zA-Z][a-zA-Z0-9+.-]*:[^\s]*$/,
  153. 'iri-reference': /^(((([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:?)?)|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?)?))#(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|(([A-Za-z][+\-.0-9A-Za-z]*)?%[0-9A-Fa-f]{2}|[!$&-.0-9;=@_~-\u{10FFFF}]|[A-Za-z][+\-.0-9A-Za-z]*[!$&-*,;=@_~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-.0-9;=@-Z_a-z~-\u{10FFFF}])*((([/?](%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?#|[/?])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*)?|([A-Za-z][+\-.0-9A-Za-z]*(:%[0-9A-Fa-f]{2}|:[!$&-.0-;=?-Z_a-z~-\u{10FFFF}]|[/?])|\?)(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|([A-Za-z][+\-.0-9A-Za-z]*:)?\/((%[0-9A-Fa-f]{2}|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)(:\d*)?[/?]|[!$&-.0-;=?-Z_a-z~-\u{10FFFF}])(%[0-9A-Fa-f]{2}|[!$&-;=?-Z_a-z~-\u{10FFFF}])*|\/((%[0-9A-Fa-f]{2}|[!$&-.0-9;=A-Z_a-z~-\u{10FFFF}])+(:\d*)?|(\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?:\d*|\[(([Vv][0-9A-Fa-f]+\.[!$&-.0-;=A-Z_a-z~-\u{10FFFF}]+)?|[.0-:A-Fa-f]+)\])?)?|[A-Za-z][+\-.0-9A-Za-z]*:?)?$/u,
  154. 'uuid': /^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
  155. // 7.3.6. uri-template
  156. 'uri-template': /(%[0-9a-f]{2}|[!#$&(-;=?@\[\]_a-z~]|\{[!#&+,./;=?@|]?(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?(,(%[0-9a-f]{2}|[0-9_a-z])(\.?(%[0-9a-f]{2}|[0-9_a-z]))*(:[1-9]\d{0,3}|\*)?)*\})*/iu,
  157. // 7.3.7. JSON Pointers
  158. 'json-pointer': /^(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*$/iu,
  159. 'relative-json-pointer': /^\d+(#|(\/([\x00-\x2e0-@\[-}\x7f]|~[01])*)*)$/iu,
  160. // hostname regex from: http://stackoverflow.com/a/1420225/5628
  161. 'hostname': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
  162. 'host-name': /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/,
  163. 'utc-millisec': function (input) {
  164. return (typeof input === 'string') && parseFloat(input) === parseInt(input, 10) && !isNaN(input);
  165. },
  166. // 7.3.8. regex
  167. 'regex': function (input) {
  168. var result = true;
  169. try {
  170. new RegExp(input);
  171. } catch (e) {
  172. result = false;
  173. }
  174. return result;
  175. },
  176. // Other definitions
  177. // "style" was removed from JSON Schema in draft-4 and is deprecated
  178. 'style': /[\r\n\t ]*[^\r\n\t ][^:]*:[\r\n\t ]*[^\r\n\t ;]*[\r\n\t ]*;?/,
  179. // "color" was removed from JSON Schema in draft-4 and is deprecated
  180. 'color': /^(#?([0-9A-Fa-f]{3}){1,2}\b|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow|(rgb\(\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*,\s*\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\b\s*\))|(rgb\(\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*,\s*(\d?\d%|100%)+\s*\)))$/,
  181. 'phone': /^\+(?:[0-9] ?){6,14}[0-9]$/,
  182. 'alpha': /^[a-zA-Z]+$/,
  183. 'alphanumeric': /^[a-zA-Z0-9]+$/,
  184. };
  185. FORMAT_REGEXPS.regexp = FORMAT_REGEXPS.regex;
  186. FORMAT_REGEXPS.pattern = FORMAT_REGEXPS.regex;
  187. FORMAT_REGEXPS.ipv4 = FORMAT_REGEXPS['ip-address'];
  188. exports.isFormat = function isFormat (input, format, validator) {
  189. if (typeof input === 'string' && FORMAT_REGEXPS[format] !== undefined) {
  190. if (FORMAT_REGEXPS[format] instanceof RegExp) {
  191. return FORMAT_REGEXPS[format].test(input);
  192. }
  193. if (typeof FORMAT_REGEXPS[format] === 'function') {
  194. return FORMAT_REGEXPS[format](input);
  195. }
  196. } else if (validator && validator.customFormats &&
  197. typeof validator.customFormats[format] === 'function') {
  198. return validator.customFormats[format](input);
  199. }
  200. return true;
  201. };
  202. var makeSuffix = exports.makeSuffix = function makeSuffix (key) {
  203. key = key.toString();
  204. // This function could be capable of outputting valid a ECMAScript string, but the
  205. // resulting code for testing which form to use would be tens of thousands of characters long
  206. // That means this will use the name form for some illegal forms
  207. if (!key.match(/[.\s\[\]]/) && !key.match(/^[\d]/)) {
  208. return '.' + key;
  209. }
  210. if (key.match(/^\d+$/)) {
  211. return '[' + key + ']';
  212. }
  213. return '[' + JSON.stringify(key) + ']';
  214. };
  215. exports.deepCompareStrict = function deepCompareStrict (a, b) {
  216. if (typeof a !== typeof b) {
  217. return false;
  218. }
  219. if (Array.isArray(a)) {
  220. if (!Array.isArray(b)) {
  221. return false;
  222. }
  223. if (a.length !== b.length) {
  224. return false;
  225. }
  226. return a.every(function (v, i) {
  227. return deepCompareStrict(a[i], b[i]);
  228. });
  229. }
  230. if (typeof a === 'object') {
  231. if (!a || !b) {
  232. return a === b;
  233. }
  234. var aKeys = Object.keys(a);
  235. var bKeys = Object.keys(b);
  236. if (aKeys.length !== bKeys.length) {
  237. return false;
  238. }
  239. return aKeys.every(function (v) {
  240. return deepCompareStrict(a[v], b[v]);
  241. });
  242. }
  243. return a === b;
  244. };
  245. function deepMerger (target, dst, e, i) {
  246. if (typeof e === 'object') {
  247. dst[i] = deepMerge(target[i], e);
  248. } else {
  249. if (target.indexOf(e) === -1) {
  250. dst.push(e);
  251. }
  252. }
  253. }
  254. function copyist (src, dst, key) {
  255. dst[key] = src[key];
  256. }
  257. function copyistWithDeepMerge (target, src, dst, key) {
  258. if (typeof src[key] !== 'object' || !src[key]) {
  259. dst[key] = src[key];
  260. }
  261. else {
  262. if (!target[key]) {
  263. dst[key] = src[key];
  264. } else {
  265. dst[key] = deepMerge(target[key], src[key]);
  266. }
  267. }
  268. }
  269. function deepMerge (target, src) {
  270. var array = Array.isArray(src);
  271. var dst = array && [] || {};
  272. if (array) {
  273. target = target || [];
  274. dst = dst.concat(target);
  275. src.forEach(deepMerger.bind(null, target, dst));
  276. } else {
  277. if (target && typeof target === 'object') {
  278. Object.keys(target).forEach(copyist.bind(null, target, dst));
  279. }
  280. Object.keys(src).forEach(copyistWithDeepMerge.bind(null, target, src, dst));
  281. }
  282. return dst;
  283. }
  284. module.exports.deepMerge = deepMerge;
  285. /**
  286. * Validates instance against the provided schema
  287. * Implements URI+JSON Pointer encoding, e.g. "%7e"="~0"=>"~", "~1"="%2f"=>"/"
  288. * @param o
  289. * @param s The path to walk o along
  290. * @return any
  291. */
  292. exports.objectGetPath = function objectGetPath(o, s) {
  293. var parts = s.split('/').slice(1);
  294. var k;
  295. while (typeof (k=parts.shift()) == 'string') {
  296. var n = decodeURIComponent(k.replace(/~0/,'~').replace(/~1/g,'/'));
  297. if (!(n in o)) return;
  298. o = o[n];
  299. }
  300. return o;
  301. };
  302. function pathEncoder (v) {
  303. return '/'+encodeURIComponent(v).replace(/~/g,'%7E');
  304. }
  305. /**
  306. * Accept an Array of property names and return a JSON Pointer URI fragment
  307. * @param Array a
  308. * @return {String}
  309. */
  310. exports.encodePath = function encodePointer(a){
  311. // ~ must be encoded explicitly because hacks
  312. // the slash is encoded by encodeURIComponent
  313. return a.map(pathEncoder).join('');
  314. };
  315. /**
  316. * Calculate the number of decimal places a number uses
  317. * We need this to get correct results out of multipleOf and divisibleBy
  318. * when either figure is has decimal places, due to IEEE-754 float issues.
  319. * @param number
  320. * @returns {number}
  321. */
  322. exports.getDecimalPlaces = function getDecimalPlaces(number) {
  323. var decimalPlaces = 0;
  324. if (isNaN(number)) return decimalPlaces;
  325. if (typeof number !== 'number') {
  326. number = Number(number);
  327. }
  328. var parts = number.toString().split('e');
  329. if (parts.length === 2) {
  330. if (parts[1][0] !== '-') {
  331. return decimalPlaces;
  332. } else {
  333. decimalPlaces = Number(parts[1].slice(1));
  334. }
  335. }
  336. var decimalParts = parts[0].split('.');
  337. if (decimalParts.length === 2) {
  338. decimalPlaces += decimalParts[1].length;
  339. }
  340. return decimalPlaces;
  341. };
  342. exports.isSchema = function isSchema(val){
  343. return (typeof val === 'object' && val) || (typeof val === 'boolean');
  344. };
  345. /**
  346. * Resolve target URL from a base and relative URL.
  347. * Similar to Node's URL Lib's legacy resolve function.
  348. * Code from example in deprecation note in said library.
  349. * @param string
  350. * @param string
  351. * @returns {string}
  352. */
  353. var resolveUrl = exports.resolveUrl = function resolveUrl(from, to) {
  354. const resolvedUrl = new URL(to, new URL(from, 'resolve://'));
  355. if (resolvedUrl.protocol === 'resolve:') {
  356. const { pathname, search, hash } = resolvedUrl;
  357. return pathname + search + hash;
  358. }
  359. return resolvedUrl.toString();
  360. }