Created
September 20, 2019 12:56
-
-
Save doncatnip/58b35b1576143d203c1daa05d166ecde to your computer and use it in GitHub Desktop.
A json schema validation wrapper for vue composition API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import _ from 'lodash' | |
import {reactive, ref} from '@vue/composition-api' | |
import Ajv from 'ajv' | |
const ajv = new Ajv({allErrors:true}) | |
/*** | |
* TODO: proper documentation including nested properties and arrays | |
* | |
* json schema validation | |
* | |
* usage: | |
* const {$v, validate, revalidate} = useValidation(schema, messages) | |
* | |
* schema is an official json schema draft-7 | |
* messages can contain associated custom error messages | |
* if messages are ommitted, default json schema messages are used instead | |
* | |
* example: | |
* const {...} = useValidation | |
* ( { required: ['id'] | |
* , properties: | |
* { id: {type: 'number'} | |
* } | |
* } | |
* , { required: | |
* { id: 'Please provide an ID' | |
* } | |
* , properties: | |
* { id: {type: 'ID must be a number.'} | |
} | |
* } | |
* ) | |
* | |
******/ | |
const createReactiveValidity = properties => { | |
const validity = {'$isInvalid':undefined} | |
let state | |
for (const [key,value] of Object.entries(properties)) { | |
if (value.type=='array') { | |
state = idx => { | |
if (validity[key].arrayState[idx]===undefined) | |
validity[key].arrayState[idx] = createReactiveValidity(value.items.properties) | |
return validity[key].arrayState[idx] | |
} | |
state.arrayState = {} | |
} else state = {state: undefined, feedback: undefined} | |
validity[key] = state | |
} | |
return reactive(validity) | |
} | |
const clearErrorFeedback = validity => { | |
for (const [key,err] of Object.entries(validity)) { | |
if (key==='$isInvalid') { | |
validity.$isInvalid = undefined | |
continue | |
} | |
if (err.arrayState) { | |
for (const subValidity of Object.values(err.arrayState)) | |
clearErrorFeedback(subValidity) | |
continue | |
} | |
err.state = undefined | |
err.feedback = undefined | |
} | |
} | |
const unsetEmptyFields = data => { | |
for (const [key,value] of Object.entries(data)) { | |
if (Array.isArray(value)) { | |
for (const subData of value) | |
if (_.isObject(subData)) | |
unsetEmptyFields(subData) | |
continue | |
} | |
if (!value) | |
data[key] = undefined | |
} | |
} | |
const ARRAY_PROPERTY = /^(.*)\[([0-9]+)\]$/ | |
export const useValidation = (schema,messages) => { | |
//compile json schema | |
const validateSchema = ajv.compile(schema) | |
//create reactive state for fields and nested fields recursively | |
const $v = createReactiveValidity(schema.properties) | |
const validate = (data,options={}) => { | |
data = _.cloneDeep(data) | |
if (options.invalidOnly && $v.$isInvalid!==true) | |
return; | |
//clear current error feedback recursively | |
clearErrorFeedback($v) | |
//set empty fields to undefined recursively | |
//so that 'required' validators trigger | |
unsetEmptyFields(data) | |
const valid = validateSchema(data) | |
if (!valid) { | |
console.log('errors', validateSchema.errors) | |
let invalidLayers = new Set([$v]) | |
for (const err of validateSchema.errors) { | |
let feedback = messages | |
let path = err.dataPath.split('.') | |
let validityLayer = $v | |
for (const p of path) { | |
if (!p) continue | |
let m = p.match(ARRAY_PROPERTY) | |
if (m&&m.length==3) { | |
validityLayer = validityLayer[m[1]](Number(m[2])) | |
invalidLayers.add(validityLayer) | |
feedback = _.has(feedback, ['properties', m[1], 'items']) | |
&&feedback.properties[m[1]].items | |
} else { | |
validityLayer = validityLayer[p] | |
feedback = _.has(feedback, ['properties',p]) | |
&&feedback.properties[p] | |
} | |
} | |
if (err.keyword=='required') { | |
validityLayer = validityLayer[err.params.missingProperty] | |
feedback = _.has(feedback,['required',err.params.missingProperty]) | |
&&feedback.required[err.params.missingProperty] | |
} else | |
feedback = _.has(feedback,err.keyword)&&feedback[err.keyword] | |
validityLayer.state = false | |
validityLayer.feedback = feedback||err.message | |
} | |
for (const layer of invalidLayers) | |
layer.$isInvalid = true | |
} | |
return valid | |
} | |
const revalidate = data => validate(data,{invalidOnly:true}) | |
return {$v, validate, revalidate} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment