UNPKG

tynder

Version:

TypeScript friendly Data validator for JavaScript.

398 lines (336 loc) 11.8 kB
// Copyright (c) 2020, Shellyl_N and Authors // license: ISC // https://github.com/shellyln { var AppState = {}; const exampleCodes = [ //// [0] //// {name: "Example1: Directory structure", code: `interface ACL { target: string; value: string; } /** Entry base */ interface EntryBase { /** Entry name */ name: string; /** ACL infos */ acl: ACL[]; /** Timestamp */ @stereotype('datetime') timestamp: string; } /** File entry */ interface File extends EntryBase { /** Entry type */ @recordType type: 'file'; } /** Folder entry */ interface Folder extends EntryBase { /** Entry type */ @recordType type: 'folder'; /** Child entries */ @constraint('unique', ['name']) entries: Entry[]; } /** Entry (union type) */ type Entry = File | Folder;`}, //// [1] //// {name: "Example2: Primitives", code: `type NumberType = number; type IntegerType = integer; type BigIntType = bigint; type StringType = string; type BooleanType = boolean; type NullType = null; type UndefinedType = undefined; type AnyType = any; type UnknownType = unknown; type NeverType = never; type NumberValueType = 3; type IntegerValueType = integer; type BigIntValueType = 7n; type StringValueType = 'XB'; type BooleanValueType = true;`}, //// [3] //// {name: "Example3: Fields", code: `type NumberType = number; interface A { numberTypeField: NumberType; integerTypeField: integer; bigIntTypeField: bigint; stringTypeField: string; booleanTypeField: boolean; nullTypeField: null; undefinedTypeField: undefined; anyTypeField: any; unknownTypeField: unknown; neverTypeField: never; numberValueTypeField: 3; integerValueTypeField: integer; bigIntValueTypeField: 7n; stringValueTypeField: 'XB'; booleanValueTypeField: true; } interface B { numberTypeField?: NumberType; integerTypeField?: integer; bigIntTypeField?: bigint; stringTypeField?: string; booleanTypeField?: boolean; nullTypeField?: null; undefinedTypeField?: undefined; anyTypeField?: any; unknownTypeField?: unknown; neverTypeField?: never; numberValueTypeField?: 3; integerValueTypeField?: integer; bigIntValueTypeField?: 7n; stringValueTypeField?: 'XB'; booleanValueTypeField?: true; } interface C extends A {} type D = Partial<A>; interface E { /** additional props */ [propNames: string]: any; }`}, ]; class AceEditor extends React.Component { constructor(props, context) { super(props, context); this.state = {}; this.editor = null; } componentDidMount() { this.editor = ace.edit(this.props.id); this.editor.setTheme('ace/theme/monokai'); this.editor.session.setMode('ace/mode/' + this.props.lang); AppState.AceEditor = AppState.AceEditor || {}; AppState.AceEditor[this.props.id] = this.editor; this.props.loadExample(0); } render() { return (lsx` (div (@ (className "AceEditorOuterWrap")) (div (@ (id ${this.props.id}) (className "AceEditorDiv") )))`); } } class ExampleLoader extends React.Component { constructor(props, context) { super(props, context); this.state = {}; } handleExampleSelected(i) { this.props.loadExample(i); } render() { return (lsx` (Template (select (@ (style (display "inline-block") (width "300px") ) (onChange ${(e) => this.handleExampleSelected(e.target.value)}) ) ($=for ${exampleCodes} (option (@ (value $index)) ($get $data "name") ) ) ) )`); } } class EvaluateButtons extends React.Component { constructor(props, context) { super(props, context); this.state = {}; } handleValidateByTynderClick(evt) { const schemaEditor = AppState.AceEditor[this.props.schemaEditorId]; const dataEditor = AppState.AceEditor[this.props.dataEditorId]; this.props.validateByTynder(schemaEditor.getValue(), dataEditor.getValue(), document.getElementById(this.props.outputId)); }; handleValidateByAjvClick(evt) { const schemaEditor = AppState.AceEditor[this.props.schemaEditorId]; const dataEditor = AppState.AceEditor[this.props.dataEditorId]; this.props.validateByAjv(schemaEditor.getValue(), dataEditor.getValue(), document.getElementById(this.props.outputId)); }; render() { return (lsx` (Template (button (@ (style (textTransform "none")) (className "waves-effect waves-light red lighten-1 btn") (onClick ${(e) => this.handleValidateByTynderClick(e)}) ) "Validate by Tynder") " " (button (@ (style (textTransform "none")) (className "waves-effect waves-light red lighten-1 btn") (onClick ${(e) => this.handleValidateByAjvClick(e)}) ) "Validate by Ajv (JSON Schema)") )`); } } class App extends React.Component { constructor(props, context) { super(props, context); this.state = {}; fetch('https://cdn.jsdelivr.net/npm/ajv@6.11.0/lib/refs/json-schema-draft-06.json') .then(async (resp) => { if (resp.ok) { this.jsonSchema = await resp.text(); } }) .catch(e => console.error(e.message)); } loadExample(i) { const editor = AppState.AceEditor['schemaEditor']; editor.setValue(exampleCodes[i].code); editor.clearSelection(); } validateByTynder(code, data, outputElement) { let r = ''; try { const targetEntryName = document.querySelector('#targetEntryName'); const schema = tynder.compile(code); const ctx = { checkAll: true, schema, stereotypes: new Map(tynder.stereotypes), customConstraints: new Map(tynder.customConstraints), }; if (tynder.validate(JSON.parse(data), tynder.getType(schema, targetEntryName.value), ctx)) { r = `Validation succeeded. (Tynder)`; } else { r = JSON.stringify(ctx.errors, null, 4); } } catch (e) { r = e.toString(); } let x = document.createElement('pre'); x.style.margin = '0'; x.spellcheck = false; x.contentEditable = true; x.innerText = r; ReactDOM.unmountComponentAtNode(outputElement); while (outputElement.firstChild) { outputElement.removeChild(outputElement.firstChild); } outputElement.appendChild(x); } validateByAjv(code, data, outputElement) { let r = ''; try { const schema = tynder.compile(code); if (this.jsonSchema) { const targetEntryName = document.querySelector('#targetEntryName'); const ajv = new Ajv(); ajv.addMetaSchema(JSON.parse(this.jsonSchema)); const validate = ajv.addSchema(tynder.generateJsonSchemaObject(schema)) .getSchema(`#/definitions/${targetEntryName.value}`); if (validate(JSON.parse(data))) { r = `Validation succeeded. (Ajv)`; } else { r = JSON.stringify(validate.errors, null, 4); } } else { r = `${'Failed to download the JSON Schema meta schema file (refs/json-schema-draft-06.json).'}\n\n${r}`; } } catch (e) { r = `${e.toString()}\n\n${r}`; } let x = document.createElement('pre'); x.style.margin = '0'; x.spellcheck = false; x.contentEditable = true; x.innerText = r; ReactDOM.unmountComponentAtNode(outputElement); while (outputElement.firstChild) { outputElement.removeChild(outputElement.firstChild); } outputElement.appendChild(x); } componentDidMount() { { M.updateTextFields(); const targetEntryName = document.querySelector('#targetEntryName'); targetEntryName.value = 'Folder'; } { const now = new Date(); const editor = AppState.AceEditor['dataEditor']; editor.setValue(JSON.stringify({ type: 'folder', name: 'foo', entries: [{ type: 'file', name: 'bar', acl: [{ target: 'a', value: 'deny', }], timestamp: now.toISOString(), }], acl: [{ target: 'a', value: 'deny', }], timestamp: now.toISOString(), }, null, 2)); editor.clearSelection(); } } render() { return (lsx` (Template (div (@ (style (margin "4px"))) (ExampleLoader (@ (loadExample ${(i) => this.loadExample(i)}) )) " " (div (@ (className "input-field inline") (style (margin "0") (padding "0") )) (input (@ (id "targetEntryName") (style (width "200px") (color "white") (margin "0") (padding "0") ))) (label (@ (id "targetEntryNameLabel") (for "targetEntryName") (className "active") ) "Target entry name:") ) " " (EvaluateButtons (@ (schemaEditorId "schemaEditor") (dataEditorId "dataEditor") (outputId "root") (validateByTynder ${(code, data, outputElement) => this.validateByTynder(code, data, outputElement)}) (validateByAjv ${(code, data, outputElement) => this.validateByAjv(code, data, outputElement)}) )) ) (div (@ (style (display "flex") (flexWrap "wrap") )) (AceEditor (@ (id "schemaEditor") (lang "typescript") (loadExample ${(i) => this.loadExample(i)}) )) (AceEditor (@ (id "dataEditor") (lang "json") (loadExample ${(i) => ""}) )) (div (@ (id "root") (className "grey OutletDiv") )) ) )`); } } window.lsx = liyad.LSX({ jsx: React.createElement, jsxFlagment: React.Fragment, components: { AceEditor, ExampleLoader, EvaluateButtons, App, }, }); }