From 367dcaf0ca96bfb607a995f9cf072ea8908c34c0 Mon Sep 17 00:00:00 2001 From: Daniel Naab Date: Thu, 7 Nov 2024 17:50:25 -0600 Subject: [PATCH] Parse/validate Blueprint JSON --- packages/forms/src/blueprint.ts | 6 +- packages/forms/src/builder/index.ts | 62 ++++++++++++++++++- .../forms/src/repository/get-form.test.ts | 5 +- packages/forms/src/repository/get-form.ts | 6 +- packages/forms/src/services/get-form.test.ts | 2 +- packages/forms/src/services/get-form.ts | 14 ++++- packages/forms/src/services/save-form.ts | 14 ++++- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/packages/forms/src/blueprint.ts b/packages/forms/src/blueprint.ts index 555a6862..4bcfae75 100644 --- a/packages/forms/src/blueprint.ts +++ b/packages/forms/src/blueprint.ts @@ -71,11 +71,11 @@ export const createForm = ( patterns: [ { id: 'root', - type: 'sequence', + type: 'page-set', data: { - patterns: [], + pages: [], }, - } satisfies SequencePattern, + } satisfies PageSetPattern, ], root: 'root', } diff --git a/packages/forms/src/builder/index.ts b/packages/forms/src/builder/index.ts index 1e61abb5..9ac890d6 100644 --- a/packages/forms/src/builder/index.ts +++ b/packages/forms/src/builder/index.ts @@ -1,4 +1,6 @@ -import { type VoidResult } from '@atj/common'; +import * as z from 'zod'; + +import { failure, success, type Result, type VoidResult } from '@atj/common'; import { addPageToPageSet, addPatternToFieldset, @@ -24,7 +26,63 @@ import { type FieldsetPattern } from '../patterns/fieldset/config.js'; import { type PageSetPattern } from '../patterns/page-set/config.js'; import type { Blueprint, FormSummary } from '../types.js'; import type { ParsedPdf } from '../documents/pdf/parsing-api.js'; -import type { DocumentFieldMap } from '../documents/types.js'; + +const createFormSchema = (config: FormConfig) => { + return z.object({ + summary: z.object({ + title: z.string(), + description: z.string(), + }), + root: z.string(), + patterns: z.record( + z.string(), + z.any().refine( + val => { + const patternConfig = config.patterns[val?.type]; + if (!patternConfig) { + return false; + } + const result = patternConfig.parseConfigData(val?.data); + if (!result.success) { + const message = Object.values(result.error) + .map(err => err.message || '') + .join(', '); + console.error(val?.type, result.error); + console.error(`Pattern config error: ${message}`); + } + return result.success; + }, + { + message: 'Invalid pattern', + } + ) + ), + outputs: z.array( + z.object({ + id: z.string(), + path: z.string(), + fields: z.record(z.string(), z.any()), + formFields: z.record(z.string(), z.string()), + }) + ), + }); +}; + +export const parseForm = (config: FormConfig, obj: any): Result => { + const formSchema = createFormSchema(config); + const result = formSchema.safeParse(obj); + if (result.error) { + return failure(result.error.message); + } + return success(result.data); +}; + +export const parseFormString = ( + config: FormConfig, + json: string +): Result => { + return parseForm(config, JSON.parse(json)); +}; export class BlueprintBuilder { bp: Blueprint; diff --git a/packages/forms/src/repository/get-form.test.ts b/packages/forms/src/repository/get-form.test.ts index 33734a8b..390c41b4 100644 --- a/packages/forms/src/repository/get-form.test.ts +++ b/packages/forms/src/repository/get-form.test.ts @@ -12,7 +12,7 @@ describeDatabase('getForm', () => { .insertInto('forms') .values({ id: '45c66187-64e2-4d75-a45a-e80f1d035bc5', - data: '{"summary":{"title":"Title","description":"Description"},"root":"root","patterns":{"root":{"type":"sequence","id":"root","data":{"patterns":[]}}},"outputs":[{"id":"test-id","path":"test.pdf","fields":{},"formFields":{}}]}', + data: '{"summary":{"title":"Title","description":"Description"},"root":"root","patterns":{"root":{"type":"page-set","id":"root","data":{"pages":[]}}},"outputs":[{"id":"test-id","path":"test.pdf","fields":{},"formFields":{}}]}', }) .execute(); @@ -20,7 +20,6 @@ describeDatabase('getForm', () => { db.ctx, '45c66187-64e2-4d75-a45a-e80f1d035bc5' ); - console.log(result); expect(result).toEqual(TEST_FORM); }); @@ -36,7 +35,7 @@ describeDatabase('getForm', () => { const TEST_FORM: Blueprint = { summary: { title: 'Title', description: 'Description' }, root: 'root', - patterns: { root: { type: 'sequence', id: 'root', data: { patterns: [] } } }, + patterns: { root: { type: 'page-set', id: 'root', data: { pages: [] } } }, outputs: [ { id: 'test-id', diff --git a/packages/forms/src/repository/get-form.ts b/packages/forms/src/repository/get-form.ts index ce961499..6fe36823 100644 --- a/packages/forms/src/repository/get-form.ts +++ b/packages/forms/src/repository/get-form.ts @@ -19,9 +19,5 @@ export const getForm: GetForm = async (ctx, formId) => { return null; } - return parseStringForm(selectResult.data); -}; - -const parseStringForm = (formString: string): Blueprint => { - return JSON.parse(formString); + return JSON.parse(selectResult.data); }; diff --git a/packages/forms/src/services/get-form.test.ts b/packages/forms/src/services/get-form.test.ts index 412f10c0..be59cee7 100644 --- a/packages/forms/src/services/get-form.test.ts +++ b/packages/forms/src/services/get-form.test.ts @@ -33,7 +33,7 @@ describe('getForm', () => { const result = await getForm(ctx, addResult.data.id); if (!result.success) { - expect.fail('Failed to add form:', result.error); + expect.fail(`Failed to get form: ${JSON.stringify(result.error)}`); } expect(result.data).toEqual(TEST_FORM); }); diff --git a/packages/forms/src/services/get-form.ts b/packages/forms/src/services/get-form.ts index 82737020..25b10161 100644 --- a/packages/forms/src/services/get-form.ts +++ b/packages/forms/src/services/get-form.ts @@ -1,7 +1,8 @@ import { type Result, failure, success } from '@atj/common'; -import { type Blueprint } from '../index.js'; +import { parseForm } from '../builder/index.js'; import { type FormServiceContext } from '../context/index.js'; +import { type Blueprint } from '../types.js'; type GetFormError = { status: number; @@ -21,5 +22,14 @@ export const getForm: GetForm = async (ctx, formId) => { message: 'Form not found', }); } - return success(result); + + const parseResult = parseForm(ctx.config, result); + if (!parseResult.success) { + return failure({ + status: 500, + message: parseResult.error, + }); + } + + return success(parseResult.data); }; diff --git a/packages/forms/src/services/save-form.ts b/packages/forms/src/services/save-form.ts index 133aed90..165b37d6 100644 --- a/packages/forms/src/services/save-form.ts +++ b/packages/forms/src/services/save-form.ts @@ -1,7 +1,8 @@ import { type Result, failure, success } from '@atj/common'; -import { Blueprint } from '../index.js'; import { type FormServiceContext } from '../context/index.js'; +import { type Blueprint } from '../types.js'; +import { parseForm } from '../builder/index.js'; type SaveFormError = { status: number; @@ -21,7 +22,16 @@ export const saveForm: SaveForm = async (ctx, formId, form) => { message: 'You must be logged in to save a form', }); } - const result = await ctx.repository.saveForm(formId, form); + + const parseResult = parseForm(ctx.config, form); + if (!parseResult.success) { + return failure({ + status: 422, + message: parseResult.error, + }); + } + + const result = await ctx.repository.saveForm(formId, parseResult.data); if (result.success === false) { return failure({ status: 500,