UNPKG

vue-vuelidate-jsonschema

Version:

Create validation definitions for vuelidate based on json schema

232 lines (203 loc) 7.71 kB
'use strict' var mergeStrategy = require('./merge-validation-options') var reduce = require('lodash/reduce') var merge = require('lodash/merge') var set = require('lodash/set') var get = require('lodash/get') var difference = require('lodash/difference') var defaultsDeep = require('lodash/defaultsDeep') var omit = require('lodash/omit') var isPlainObject = require('lodash/isPlainObject') var cloneDeep = require('lodash/cloneDeep') var isFunction = require('lodash/isFunction') var flattenObject = require('flat') var propertyRules = require('./property-rules') var createDataProperties = require('./scaffold') var cachedVue function getVue(rootVm) { if (cachedVue) { return cachedVue } var InnerVue = rootVm.constructor /* istanbul ignore next */ while (InnerVue.super) { InnerVue = InnerVue.super } cachedVue = InnerVue return InnerVue } function normalizeSchemas(schemaConfig) { if (Array.isArray(schemaConfig)) { return schemaConfig.map(function(config) { if (config.mountPoint) { return config } else { return {mountPoint: 'schema', schema: config} } }) } else { if (schemaConfig.mountPoint) { return [schemaConfig] } else { return [ { mountPoint: 'schema', schema: schemaConfig } ] } } } function createFromSchema(schema, values) { var generated = createDataProperties([ { mountPoint: '.', schema: schema } ]) return defaultsDeep(values, generated) } function generateValidationSchema(schemas) { var root = {} var self = this var roots = schemas.filter(function(schemaConfig) { return schemaConfig.mountPoint === '.' }) roots.forEach(function(schemaConfig) { if (schemaConfig.schema.type !== 'object') { throw new Error('Schema with id ' + schemaConfig.schema.id + ' has mount point at the root and is not a schema of type object. This is not supported. For non object schmeas you must define a mount point.') } if (schemaConfig.schema.hasOwnProperty('patternProperties')) { throw new Error('Schema with id ' + schemaConfig.schema.id + ' has sibling validator patternProperties. This is not supported when mounting on root. Use a mount point.') } if (!(schemaConfig.schema.additionalProperties === true || schemaConfig.schema.additionalProperties === undefined)) { throw new Error('Schema with id ' + schemaConfig.schema.id + ' has sibling validators additionalProperties not equal to true or undefined. This is not supported when mounting on root. Since there are lots of extra properties on a vue instance.') } }) if (roots.length) { root = roots.reduce(function(all, schemaConfig) { merge(all, propertyRules.getPropertyValidationRules.call(self, schemaConfig.schema, true, true)) return all }, root) } var rest = difference(schemas, roots) return reduce(rest, function(all, schemaConfig) { var existing = get(all, schemaConfig.mountPoint) var parents = schemaConfig.mountPoint.split('.') set(all, schemaConfig.mountPoint, merge(existing, propertyRules.getPropertyValidationRules.call(self, schemaConfig.schema, true, true, null, parents))) return all }, root) } function createMixin(options) { options = options || {} return { beforeCreate: function() { var self = this var schema = this.$options.schema var propsData = this.$options.propsData var normalized = [] if (schema) { normalized = normalizeSchemas(schema) } if (propsData && propsData[options.schemaPropsName]) { normalized = normalized.concat(normalizeSchemas(propsData[options.schemaPropsName])) } if (!normalized.length) { return } var Vue = getVue(this) this.$options.validations = mergeStrategy(function() { if (this.$schema && !isFunction(this.$schema.then)) { return generateValidationSchema.call(this, this.$schema) } else { return {} } }, this.$options.validations) this.$options.methods = Vue.config.optionMergeStrategies.methods({ createFromSchema: createFromSchema, getSchemaData: function(schemaConfig) { var originallyArray = Array.isArray(schemaConfig) var normalizedSchemas = Array.isArray(schemaConfig) ? schemaConfig : [schemaConfig] var self = this return reduce(normalizedSchemas, function(all, schema) { var root = self var data = createDataProperties(normalizedSchemas) var flatStructure = isPlainObject(data) ? flattenObject(data) : {} if (schemaConfig.mountPoint !== '.' && !originallyArray) { data = get(self, schemaConfig.mountPoint) flatStructure = isPlainObject(data) ? flattenObject(data) : {} root = get(self, schemaConfig.mountPoint) if (isPlainObject(root)) { flatStructure = reduce(flatStructure, function(all, val, key) { all[String(key).replace(schemaConfig.mountPoint, '').replace(/^\./, '')] = val return all }, {}) } else { return root } } return reduce(flatStructure, function(all, val, path) { return set(all, path, get(root, path)) }, all) }, {}) } }, this.$options.methods) var calledSchemas = normalized.map(function(schemaConfig) { if (isFunction(schemaConfig.schema)) { var config = cloneDeep(schemaConfig) config.schema = schemaConfig.schema.call(self) return config } return schemaConfig }) var hasPromise = calledSchemas.some(function(schemaConfig) { return isFunction(schemaConfig.schema.then) }) if (hasPromise) { calledSchemas.forEach(function(config, i) { if (config.mountPoint === '.' && isFunction(config.schema.then)) { throw new Error('Schema with index ' + i + ' has mount point at the root and is a promise. This is not supported. You can\'t mount to root async. Due to vue limitation. Use a mount point.') } }) var allSchemaPromise = Promise.all(calledSchemas.map(function(schemaConfig) { if (isFunction(schemaConfig.schema.then)) { return schemaConfig.schema.then(function(schema) { var newConfig = omit(schemaConfig, 'schema') newConfig.schema = schema return newConfig }) } else { return schemaConfig } })).then(function(schemaConfigs) { // reactivity is already set up, we can just replace properties Object.assign(self, createDataProperties(schemaConfigs)) self.$schema = schemaConfigs }) Vue.util.defineReactive(this, '$schema', allSchemaPromise) this.$options.data = Vue.config.optionMergeStrategies.data(function() { return createDataProperties(calledSchemas, true) }, this.$options.data) } else { // rewrite schemas normalized Vue.util.defineReactive(this, '$schema', calledSchemas) this.$options.data = Vue.config.optionMergeStrategies.data(function() { return createDataProperties(calledSchemas) }, this.$options.data) } } } } module.exports = { createFromSchema: createFromSchema, mixin: createMixin(), install: function(Vue, options) { Vue.mixin(createMixin(options)) } }