diff --git a/CHANGELOG.md b/CHANGELOG.md index e3c62f2..6d28576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 2.0.1 - 2020-06-15 + +Fix: + +- Fix deep nested dynamic types validation [[#132](https://github.com/talyssonoc/structure/issues/132)] + ## 2.0.0 - 2020-03-31 Refactors: diff --git a/lerna.json b/lerna.json index 7ad1a7c..1573fc3 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "2.0.0", + "version": "2.0.1", "npmClient": "yarn", "useWorkspaces": true } diff --git a/packages/jest-structure/package.json b/packages/jest-structure/package.json index 488e7d7..78a9f80 100644 --- a/packages/jest-structure/package.json +++ b/packages/jest-structure/package.json @@ -1,6 +1,6 @@ { "name": "jest-structure", - "version": "2.0.0", + "version": "2.0.1", "description": "Jest assertions to use with Structure", "main": "index.js", "author": "Talysson ", @@ -15,7 +15,7 @@ "node": ">=10.13.0" }, "devDependencies": { - "structure": "2.0.0" + "structure": "2.0.1" }, "peerDependencies": { "jest": "^25.1.0" diff --git a/packages/structure/dist/structure.js b/packages/structure/dist/structure.js index cf07170..42b9137 100644 --- a/packages/structure/dist/structure.js +++ b/packages/structure/dist/structure.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("lodash"),require("@hapi/joi")):"function"==typeof define&&define.amd?define("Structure",["lodash","joi"],e):"object"==typeof exports?exports.Structure=e(require("lodash"),require("@hapi/joi")):t.Structure=e(t._,t.joi)}(window,(function(t,e){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=8)}([function(e,n){e.exports=t},function(t,n){t.exports=e},function(t,e){t.exports={SCHEMA:Symbol("schema"),ATTRIBUTES:Symbol("attributes"),DEFAULT_ACCESSOR:Symbol("defaultAccessor")}},function(t,e,n){function r(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e1&&void 0!==arguments[1]?arguments[1]:{};if("object"!==r(e))throw u.classAsSecondParam(e);return function(n){var r=i.for({wrappedClass:n,attributeDefinitions:t,options:e}),u=new Proxy(n,{construct:function(t,e,n){var i=Reflect.construct(t,e,n),o=Object.assign({},e[0]);return r.initializeInstance(i,{attributes:o})}});return o.addTo(r,u),u}}},function(t,e,n){function r(t,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{},n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.strict,i=Object.assign({},this.attributes,{},e);return r?t.buildStrict(i):new t(i)}}}}])})); \ No newline at end of file +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("lodash"),require("@hapi/joi")):"function"==typeof define&&define.amd?define("Structure",["lodash","joi"],e):"object"==typeof exports?exports.Structure=e(require("lodash"),require("@hapi/joi")):t.Structure=e(t._,t.joi)}(window,(function(t,e){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=8)}([function(e,n){e.exports=t},function(t,n){t.exports=e},function(t,e){t.exports={SCHEMA:Symbol("schema"),ATTRIBUTES:Symbol("attributes"),DEFAULT_ACCESSOR:Symbol("defaultAccessor")}},function(t,e,n){function r(t){return function(t){if(Array.isArray(t))return u(t)}(t)||function(t){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(t))return Array.from(t)}(t)||o(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function i(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(t)))return;var n=[],r=!0,i=!1,o=void 0;try{for(var u,a=t[Symbol.iterator]();!(r=(u=a.next()).done)&&(n.push(u.value),!e||n.length!==e);r=!0);}catch(t){i=!0,o=t}finally{try{r||null==a.return||a.return()}finally{if(i)throw o}}return n}(t,e)||o(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(t,e){if(t){if("string"==typeof t)return u(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(t,e):void 0}}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n1&&void 0!==arguments[1]?arguments[1]:{};if("object"!==r(e))throw u.classAsSecondParam(e);return function(n){var r=i.for({wrappedClass:n,attributeDefinitions:t,options:e}),u=new Proxy(n,{construct:function(t,e,n){var i=Reflect.construct(t,e,n),o=Object.assign({},e[0]);return r.initializeInstance(i,{attributes:o})}});return o.addTo(r,u),u}}},function(t,e,n){function r(t,e){var n;if("undefined"==typeof Symbol||null==t[Symbol.iterator]){if(Array.isArray(t)||(n=function(t,e){if(!t)return;if("string"==typeof t)return i(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return i(t,e)}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var r=0,o=function(){};return{s:o,n:function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,a=!0,c=!1;return{s:function(){n=t[Symbol.iterator]()},n:function(){var t=n.next();return a=t.done,t},e:function(t){c=!0,u=t},f:function(){try{a||null==n.return||n.return()}finally{if(c)throw u}}}}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:i}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,a=!0,c=!1;return{s:function(){n=t[Symbol.iterator]()},n:function(){var t=n.next();return a=t.done,t},e:function(t){c=!0,u=t},f:function(){try{a||null==n.return||n.return()}finally{if(c)throw u}}}}(t);try{for(f.s();!(c=f.n()).done;){var l=c.value;n[l.name]=l}}catch(t){f.e(t)}finally{f.f()}return n}return c(u,null,[{key:"for",value:function(t,e){var n=e.schema;return new this(t=Object.keys(t).map((function(e){return{name:e,options:t[e]}})),{schema:n})}}]),c(u,[{key:"byKey",value:function(){var t=this;return this.reduce((function(e,n){return Object.assign({},e,(r={},i=n.name,o=t[n.name],i in r?Object.defineProperty(r,i,{value:o,enumerable:!0,configurable:!0,writable:!0}):r[i]=o,r));var r,i,o}),{})}}]),u}(l(Array));t.exports=m},function(t,e,n){function r(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,a=!0,c=!1;return{s:function(){n=t[Symbol.iterator]()},n:function(){var t=n.next();return a=t.done,t},e:function(t){c=!0,u=t},f:function(){try{a||null==n.return||n.return()}finally{if(c)throw u}}}}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,a=!0,c=!1;return{s:function(){n=t[Symbol.iterator]()},n:function(){var t=n.next();return a=t.done,t},e:function(t){c=!0,u=t},f:function(){try{a||null==n.return||n.return()}finally{if(c)throw u}}}}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n0&&void 0!==arguments[0]?arguments[0]:{},n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=n.strict,i=Object.assign({},this.attributes,e);return r?t.buildStrict(i):new t(i)}}}}])})); \ No newline at end of file diff --git a/packages/structure/package.json b/packages/structure/package.json index 9431c99..46363b5 100644 --- a/packages/structure/package.json +++ b/packages/structure/package.json @@ -1,6 +1,6 @@ { "name": "structure", - "version": "2.0.0", + "version": "2.0.1", "description": "A simple schema/attributes library built on top of modern JavaScript", "main": "src/index.js", "browser": "dist/structure.js", @@ -52,7 +52,7 @@ "babel-loader": "^8.1.0", "coveralls": "^3.1.0", "electron": "^9.0.0", - "jest-structure": "2.0.0", + "jest-structure": "2.0.1", "webpack": "^4.41.2", "webpack-cli": "^3.3.9" } diff --git a/packages/structure/src/validation/validations/nested.js b/packages/structure/src/validation/validations/nested.js index 52c6c31..8e4fcd1 100644 --- a/packages/structure/src/validation/validations/nested.js +++ b/packages/structure/src/validation/validations/nested.js @@ -45,20 +45,32 @@ function getNestedValidations(typeSchema) { return joiSchema; } -exports.resolveDynamicLinks = function resolveDynamicLinks({ schema, joiValidation }) { +const resolveDynamicLinks = function resolveDynamicLinks({ schema, joiValidation }) { return schema.attributeDefinitions.reduce((joiValidation, attributeDefinition) => { if (!attributeDefinition.hasDynamicType) { return joiValidation; } const type = attributeDefinition.resolveType(); + const nestedSchema = type[SCHEMA]; - if (!type[SCHEMA]) { + // warning: uses Joi internals + // https://github.com/hapijs/joi/blob/v16.1.8/lib/types/any.js#L72 ⤵ + // https://github.com/hapijs/joi/blob/v16.1.8/lib/base.js#L699 ⤵ + // https://github.com/hapijs/joi/blob/v16.1.8/lib/modify.js#L149 + if (!nestedSchema || joiValidation._ids._get(nestedSchema.identifier)) { return joiValidation; } - const attributeValidation = type[SCHEMA].validation; + const attributeValidation = nestedSchema.validation; - return joiValidation.shared(attributeValidation.joiValidation); + const sharedValidation = joiValidation.shared(attributeValidation.joiValidation); + + return resolveDynamicLinks({ + schema: nestedSchema, + joiValidation: sharedValidation, + }); }, joiValidation); }; + +exports.resolveDynamicLinks = resolveDynamicLinks; diff --git a/packages/structure/test/unit/validation/nestedStructure.spec.js b/packages/structure/test/unit/validation/nestedStructure.spec.js index 4397184..28a17a0 100644 --- a/packages/structure/test/unit/validation/nestedStructure.spec.js +++ b/packages/structure/test/unit/validation/nestedStructure.spec.js @@ -421,5 +421,101 @@ describe('validation', () => { }); }); }); + + describe('when nesting is deep', () => { + it('validates properly with manual validation', () => { + const Vehicle = attributes({ + year: { + type: Number, + required: true, + }, + })(class Vehicle {}); + + const UserPersonalInformation = attributes( + { + name: String, + vehicle: 'Vehicle', + }, + { + dynamics: { + Vehicle: () => Vehicle, + }, + } + )(class UserPersonalInformation {}); + + const AutoRiskProfile = attributes( + { + userPersonalInformation: { + type: 'UserPersonalInformation', + required: true, + }, + }, + { + dynamics: { + UserPersonalInformation: () => UserPersonalInformation, + }, + } + )(class AutoRiskProfile {}); + + expect(() => { + const autoRiskProfile = new AutoRiskProfile({ + userPersonalInformation: new UserPersonalInformation({ + name: 'a', + vehicle: new Vehicle({ + year: 2018, + }), + }), + }); + + autoRiskProfile.validate(); + }).not.toThrow(); + }); + + it('validates properly with strict mode', () => { + const Vehicle = attributes({ + year: { + type: Number, + required: true, + }, + })(class Vehicle {}); + + const UserPersonalInformation = attributes( + { + name: String, + vehicle: 'Vehicle', + }, + { + dynamics: { + Vehicle: () => Vehicle, + }, + } + )(class UserPersonalInformation {}); + + const AutoRiskProfile = attributes( + { + userPersonalInformation: { + type: 'UserPersonalInformation', + required: true, + }, + }, + { + dynamics: { + UserPersonalInformation: () => UserPersonalInformation, + }, + } + )(class AutoRiskProfile {}); + + expect(() => { + AutoRiskProfile.buildStrict({ + userPersonalInformation: new UserPersonalInformation({ + name: 'a', + vehicle: new Vehicle({ + year: 2018, + }), + }), + }); + }).not.toThrow(); + }); + }); }); });