@roqueform/doubter-plugin
Version:
Validates Roqueform fields with Doubter shapes.
99 lines (98 loc) • 3.01 kB
JavaScript
/**
* Validates Roqueform fields with [Doubter](https://github.com/smikhalevski/doubter#readme) shapes.
*
* ```sh
* npm install --save-prod @roqueform/doubter-plugin
* ```
*
* Create a field validated by a Doubter shape:
*
* ```ts
* import * as d from 'doubter';
* import { createField } from 'roqueform';
* import errorsPlugin from 'roqueform/plugin/errors';
* import doubterPlugin, { concatDoubterIssues } from '@roqueform/doubter';
*
* const fieldShape = d.object({
* hello: d.string()
* });
*
* const field = createField({ hello: 'world' }, [
* errorsPlugin(concatDoubterIssues),
* doubterPlugin(fieldShape)
* ]);
*
* field.at('hello').validate() // ⮕ true
* ```
*
* @module @roqueform/doubter-plugin
*/
import validationPlugin from 'roqueform/plugin/validation';
/**
* Validates field with a [Doubter](https://github.com/smikhalevski/doubter#readme) shape.
*
* @param shape The shape that parses the field value.
* @template Value The root field value.
*/
export default function doubterPlugin(shape) {
return (field) => {
field._valueShape = field.parentField === null ? shape : field.parentField._valueShape?.at(field.key) || undefined;
validationPlugin(validator)(field);
};
}
/**
* Concatenates unique Doubter issues.
*/
export const concatDoubterIssues = (prevErrors, error) => {
for (const e of prevErrors) {
if (e.code !== undefined && error.code !== undefined ? e.code === error.code : e.message === error.message) {
return prevErrors;
}
}
return prevErrors.concat(error);
};
const validator = {
validate(field, options) {
const { validation, _valueShape } = field;
if (validation === null || _valueShape === undefined) {
// No validation
return;
}
applyResult(validation, _valueShape.try(field.value, options));
},
validateAsync(field, options) {
const { validation, _valueShape } = field;
if (validation === null || _valueShape === undefined) {
// No validation
return Promise.resolve();
}
return _valueShape.tryAsync(field.value, options).then(result => {
applyResult(validation, result);
});
},
};
function applyResult(validation, result) {
if (result.ok) {
return;
}
for (const issue of result.issues) {
let child = validation.rootField;
if (issue.path !== undefined) {
for (const key of issue.path) {
child = child.at(key);
}
}
if (child.validation !== validation) {
continue;
}
for (let field = validation.rootField; field.parentField !== null; field = field.parentField) {
(issue.path ||= []).unshift(field.key);
}
child.publish({
type: 'errorCaught',
target: child,
relatedTarget: validation.rootField,
payload: issue,
});
}
}