diff --git a/src/core/findInputDefinitionSchema.ts b/src/core/findInputDefinitionSchema.ts index 6462d40b..c94cd199 100644 --- a/src/core/findInputDefinitionSchema.ts +++ b/src/core/findInputDefinitionSchema.ts @@ -1,25 +1,38 @@ -import { parse, pipe } from "@std"; +import { A, parse, pipe } from "@std"; import * as E from "fp-ts/Either"; import { ValiError, BaseSchema } from "valibot"; +import { FieldMinimal, FieldMinimalSchema, InputTypeToParserMap } from "./formDefinitionSchema"; import { AllFieldTypes } from "./formDefinition"; -import { FieldMinimal, InputTypeToParserMap, FieldMinimalSchema } from "./formDefinitionSchema"; - +function stringifyError(error: ValiError) { + return error.issues.map((issue) => `${issue.path?.map((i) => i.key)}: ${issue.message} got ${issue.input}`).join(', '); +} export class InvalidInputTypeError { static readonly _tag = "InvalidInputTypeError" as const; - constructor(public input: unknown) { } + constructor(readonly input: unknown) { } toString(): string { - return `InvalidInputTypeError: ${JSON.stringify(this.input)}`; + return `InvalidInputTypeError: "input.type" is invalid, got: ${JSON.stringify(this.input)}`; } } - export class InvalidInputError { static readonly _tag = "InvalidInputError" as const; constructor(public input: FieldMinimal, readonly error: ValiError) { } toString(): string { - return `InvalidInputError: ${this.error.issues.map((issue) => issue.message).join(', ')}`; + return `InvalidInputError: ${stringifyError(this.error)}`; + } +} + +export class InvalidFieldError { + static readonly _tag = "InvalidFieldError" as const; + constructor(public field: unknown, readonly error: ValiError) { } + toString(): string { + return `InvalidFieldError: ${stringifyError(this.error)}`; + } + static of(field: unknown) { + return (error: ValiError) => new InvalidFieldError(field, error); } } + function isValidInputType(input: unknown): input is AllFieldTypes { return 'string' === typeof input && input in InputTypeToParserMap; } @@ -33,9 +46,10 @@ function isValidInputType(input: unknown): input is AllFieldTypes { * @param fieldDefinition a field definition to find the input schema for * @returns a tuple of the basic field definition and the input schema */ -export function findInputDefinitionSchema(fieldDefinition: unknown): E.Either { +export function findInputDefinitionSchema(fieldDefinition: unknown): E.Either { return pipe( parse(FieldMinimalSchema, fieldDefinition), + E.mapLeft(InvalidFieldError.of(fieldDefinition)), E.chainW((field) => { const type = field.input.type; if (isValidInputType(type)) return E.right([field, InputTypeToParserMap[type]]); @@ -43,3 +57,29 @@ export function findInputDefinitionSchema(fieldDefinition: unknown): E.Either { + return pipe( + findInputDefinitionSchema(fieldUnparsed), + E.chainW(([field, inputSchema]) => pipe( + parse(inputSchema, field.input), + E.bimap( + (error) => new InvalidInputError(field, error), + () => field + )), + )) + }), + // A.partition(E.isLeft), + // Separated.right, + ); + +} diff --git a/src/core/formDefinitionSchema.test.ts b/src/core/formDefinitionSchema.test.ts new file mode 100644 index 00000000..b652114c --- /dev/null +++ b/src/core/formDefinitionSchema.test.ts @@ -0,0 +1,23 @@ +import { A, E, pipe } from "@std"; +import { findFieldErrors } from "./findInputDefinitionSchema"; + +describe("findFieldErrors", () => { + it("should return an empty array of detailed errors or unchanged fields if they are correct", () => { + const fields = [ + { name: 'fieldName', description: 'field description', input: { type: 'text' } }, + { name: 'fieldName', input: { type: 'text' } }, + { name: 'fieldName', description: '', input: { type: '' } }, + {}, + ]; + const errors = pipe( + findFieldErrors(fields), + A.map(E.mapLeft((e) => e.toString())) + ) + expect(errors).toHaveLength(4); + expect(errors[0]).toEqual(E.right({ name: 'fieldName', description: 'field description', input: { type: 'text' } })); + expect(errors[1]).toEqual(E.left('InvalidFieldError: description: Invalid type got undefined')); + expect(errors[2]).toEqual(E.left('InvalidInputTypeError: "input.type" is invalid, got: ""')); + expect(errors[3]).toEqual(E.left('InvalidFieldError: name: field name should be a string got undefined, description: Invalid type got undefined, input: Invalid type got undefined')); + }); + +}); diff --git a/src/core/formDefinitionSchema.ts b/src/core/formDefinitionSchema.ts index 485a8192..b087bcf9 100644 --- a/src/core/formDefinitionSchema.ts +++ b/src/core/formDefinitionSchema.ts @@ -1,9 +1,8 @@ -import { A, parse, pipe } from "@std"; import * as E from "fp-ts/Either"; +import { pipe, parse } from "@std"; import { object, number, literal, type Output, is, array, string, union, optional, minLength, toTrimmed, merge, unknown, ValiError, BaseSchema, enumType, passthrough } from "valibot"; import { AllFieldTypes, FormDefinition } from "./formDefinition"; -import * as Separated from "fp-ts/Separated"; -import { findInputDefinitionSchema, InvalidInputError } from "./findInputDefinitionSchema"; +import { findFieldErrors } from "./findInputDefinitionSchema"; /** * Here are the core logic around the main domain of the plugin, @@ -56,12 +55,6 @@ export const InputTypeToParserMap: Record = { dataview: InputDataviewSourceSchema, multiselect: MultiselectSchema, }; -export const FieldMinimalSchema = passthrough(object({ - name: string(), - input: object({ type: string() }) -})); - -export type FieldMinimal = Output; export const FieldDefinitionSchema = object({ name: nonEmptyString('field name'), @@ -69,6 +62,17 @@ export const FieldDefinitionSchema = object({ description: string(), input: InputTypeSchema }); +/** + * Only for error reporting purposes + */ +export const FieldMinimalSchema = passthrough(merge([ + FieldDefinitionSchema, + object({ input: object({ type: string() }) }) +])); + +export type FieldMinimal = Output; + + export const FieldListSchema = array(FieldDefinitionSchema); /** * This is the most basic representation of a form definition. @@ -117,20 +121,7 @@ export class MigrationError { return this.form; } get fieldErrors() { - return pipe( - this.form.fields, - A.map((field) => { - return pipe( - findInputDefinitionSchema(field), - E.chainW(([field, inputSchema]) => pipe( - parse(inputSchema, field.input), - E.mapLeft((error) => new InvalidInputError(field, error)) - )), - ) - }), - A.partition(E.isLeft), - Separated.right, - ); + return findFieldErrors(this.form.fields); } } /** diff --git a/src/std/index.ts b/src/std/index.ts index 31f258fa..d37256d3 100644 --- a/src/std/index.ts +++ b/src/std/index.ts @@ -1,6 +1,6 @@ import { pipe as p } from "fp-ts/function"; import { partitionMap, partition, map as mapArr } from "fp-ts/Array"; -import { isLeft, isRight, tryCatchK, map, getOrElse, right, left } from "fp-ts/Either"; +import { isLeft, isRight, tryCatchK, map, getOrElse, right, left, mapLeft } from "fp-ts/Either"; import { ValiError, parse as parseV } from "valibot"; export const pipe = p @@ -17,7 +17,8 @@ export const E = { right, tryCatchK, getOrElse, - map + map, + mapLeft, } export const parse = tryCatchK(parseV, (e: unknown) => e as ValiError)