From 52599338ec99a8320c6c3ae1993467558024e140 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Fri, 29 Mar 2024 13:51:01 +0530 Subject: [PATCH] feat: add validator.toJSON method to get compiled schema and refs --- bin/test.ts | 3 +- package.json | 1 + src/vine/validator.ts | 25 +++++- tests/integration/validator.spec.ts | 118 ++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/bin/test.ts b/bin/test.ts index 60d4b7b..60a6b59 100644 --- a/bin/test.ts +++ b/bin/test.ts @@ -1,3 +1,4 @@ +import { snapshot } from '@japa/snapshot' import { Assert, assert } from '@japa/assert' import { expectTypeOf } from '@japa/expect-type' import { processCLIArgs, configure, run } from '@japa/runner' @@ -48,7 +49,7 @@ configure({ files: ['tests/integration/**/*.spec(.js|.ts)'], }, ], - plugins: [assert(), expectTypeOf()], + plugins: [assert(), expectTypeOf(), snapshot()], }) /* diff --git a/package.json b/package.json index 5c8e350..66e324b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@japa/assert": "^2.1.0", "@japa/expect-type": "^2.0.1", "@japa/runner": "^3.1.1", + "@japa/snapshot": "^2.0.4", "@swc/core": "^1.4.11", "@types/dlv": "^1.1.4", "@types/node": "^20.11.30", diff --git a/src/vine/validator.ts b/src/vine/validator.ts index c41c56c..65c4465 100644 --- a/src/vine/validator.ts +++ b/src/vine/validator.ts @@ -8,7 +8,7 @@ */ import { Compiler, refsBuilder } from '@vinejs/compiler' -import type { MessagesProviderContact } from '@vinejs/compiler/types' +import type { MessagesProviderContact, Refs, RootNode } from '@vinejs/compiler/types' import { messages } from '../defaults.js' import { ITYPE, OTYPE, PARSE } from '../symbols.js' @@ -41,7 +41,15 @@ export class VineValidator< * Reference to static types */ declare [ITYPE]: Schema[typeof ITYPE]; - declare [OTYPE]: Schema[typeof OTYPE] + declare [OTYPE]: Schema[typeof OTYPE]; + + /** + * Reference to the compiled schema + */ + #compiled: { + schema: RootNode, + refs: Refs, + } /** * Messages provider to use on the validator @@ -102,6 +110,8 @@ export class VineValidator< * Compile the schema to a re-usable function */ const { compilerNode, refs } = this.#parse(schema) + this.#compiled = { schema: compilerNode, refs } + const metaDataValidator = options.metaDataValidator const validateFn = new Compiler(compilerNode, { convertEmptyStringsToNull: options.convertEmptyStringsToNull, @@ -147,4 +157,15 @@ export class VineValidator< } } } + + /** + * Returns the compiled schema and refs. + */ + toJSON() { + const { schema, refs } = this.#compiled + return { + schema: structuredClone(schema), + refs, + } + } } diff --git a/tests/integration/validator.spec.ts b/tests/integration/validator.spec.ts index 661858c..d3ebb25 100644 --- a/tests/integration/validator.spec.ts +++ b/tests/integration/validator.spec.ts @@ -196,3 +196,121 @@ test.group('Validator | extend schema classes', () => { assert.isTrue((vine as any).money()) }) }) + +test.group('Validator | toJSON', () => { + test('get JSON representation of the schema', async ({ assert }) => { + const author = vine.object({ + name: vine.string(), + email: vine.string().email(), + role: vine.string().in((field) => { + assert.deepEqual(field.meta, { choices: ['admin', 'guest'] }) + return field.meta.choices + }), + }) + + const validator = vine.compile(author) + assert.snapshot(validator.toJSON()).matchInline(` + { + "refs": { + "ref://1": { + "options": undefined, + "validator": [Function], + }, + "ref://2": { + "options": undefined, + "validator": [Function], + }, + "ref://3": { + "options": undefined, + "validator": [Function], + }, + "ref://4": { + "options": undefined, + "validator": [Function], + }, + "ref://5": { + "options": { + "choices": [Function], + }, + "validator": [Function], + }, + }, + "schema": { + "schema": { + "allowNull": false, + "allowUnknownProperties": false, + "bail": true, + "fieldName": "", + "groups": [], + "isOptional": false, + "parseFnId": undefined, + "properties": [ + { + "allowNull": false, + "bail": true, + "fieldName": "name", + "isOptional": false, + "parseFnId": undefined, + "propertyName": "name", + "type": "literal", + "validations": [ + { + "implicit": false, + "isAsync": false, + "ruleFnId": "ref://1", + }, + ], + }, + { + "allowNull": false, + "bail": true, + "fieldName": "email", + "isOptional": false, + "parseFnId": undefined, + "propertyName": "email", + "type": "literal", + "validations": [ + { + "implicit": false, + "isAsync": false, + "ruleFnId": "ref://2", + }, + { + "implicit": false, + "isAsync": false, + "ruleFnId": "ref://3", + }, + ], + }, + { + "allowNull": false, + "bail": true, + "fieldName": "role", + "isOptional": false, + "parseFnId": undefined, + "propertyName": "role", + "type": "literal", + "validations": [ + { + "implicit": false, + "isAsync": false, + "ruleFnId": "ref://4", + }, + { + "implicit": false, + "isAsync": false, + "ruleFnId": "ref://5", + }, + ], + }, + ], + "propertyName": "", + "type": "object", + "validations": [], + }, + "type": "root", + }, + } + `) + }).pin() +})