UNPKG

datocms-structured-text-utils

Version:

A set of Typescript types and helpers to work with DatoCMS Structured Text fields.

144 lines (130 loc) 3.55 kB
import { Node, Document } from './types'; import { allowedAttributes, allowedChildren, inlineNodeTypes, } from './definitions'; export function validate( document: Document | null | undefined, ): { valid: boolean; message?: string } { if (document === null || document === undefined) { return { valid: true }; } if (document.schema !== 'dast') { return { valid: false, message: `.schema is not "dast":\n\n ${JSON.stringify( document, null, 2, )}`, }; } const nodes: Node[] = [document.document]; let node: Node = document.document; while (nodes.length > 0) { const next = nodes.pop(); if (!next) { break; } node = next; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { type, ...attributes } = node; const invalidAttribute = Object.keys(attributes).find( (attr) => !allowedAttributes[node.type].includes(attr), ); if (invalidAttribute) { return { valid: false, message: `"${ node.type }" has an invalid attribute "${invalidAttribute}":\n\n ${JSON.stringify( node, null, 2, )}`, }; } if ('meta' in node) { if (!Array.isArray(node.meta)) { return { valid: false, message: `"${node.type}"'s meta is not an Array:\n\n ${JSON.stringify( node, null, 2, )}`, }; } const invalidMeta = node.meta.find( (entry) => typeof entry !== 'object' || !('id' in entry) || !('value' in entry) || typeof entry.value !== 'string', ); if (invalidMeta) { return { valid: false, message: `"${node.type}" has an invalid meta ${JSON.stringify( invalidMeta, )}:\n\n ${JSON.stringify(node, null, 2)}`, }; } } if ('marks' in node) { if (!Array.isArray(node.marks)) { return { valid: false, message: `"${ node.type }"'s marks is not an Array:\n\n ${JSON.stringify(node, null, 2)}`, }; } } if ('children' in node) { if (!Array.isArray(node.children)) { return { valid: false, message: `"${ node.type }"'s children is not an Array:\n\n ${JSON.stringify(node, null, 2)}`, }; } if (node.children.length === 0) { return { valid: false, message: `"${ node.type }"'s children cannot be an empty Array:\n\n ${JSON.stringify( node, null, 2, )}`, }; } let allowed = allowedChildren[node.type]; if (typeof allowed === 'string' && allowed === 'inlineNodes') { allowed = inlineNodeTypes; } const invalidChildIndex = (node.children as Array<Node | null>).findIndex( (child) => !child || !allowed.includes(child.type), ); if (invalidChildIndex !== -1) { const invalidChild = node.children[invalidChildIndex]; return { valid: false, message: `"${node.type}" has invalid child "${ invalidChild ? invalidChild.type : invalidChild }":\n\n ${JSON.stringify(node, null, 2)}`, }; } for (let i = node.children.length - 1; i >= 0; i--) { nodes.push(node.children[i]); } } } return { valid: true, }; }