diff --git a/arktype/package.json b/arktype/package.json index 7c11e64b..9d9bfd97 100644 --- a/arktype/package.json +++ b/arktype/package.json @@ -1,7 +1,7 @@ { "name": "@hookform/resolvers/arktype", "amdName": "hookformResolversArktype", - "version": "1.0.0", + "version": "2.0.0", "private": true, "description": "React Hook Form validation resolver: arktype", "main": "dist/arktype.js", diff --git a/arktype/src/__tests__/Form-native-validation.tsx b/arktype/src/__tests__/Form-native-validation.tsx index b7684209..f6ae394c 100644 --- a/arktype/src/__tests__/Form-native-validation.tsx +++ b/arktype/src/__tests__/Form-native-validation.tsx @@ -57,14 +57,14 @@ test("form's native validation with Zod", async () => { usernameField = screen.getByPlaceholderText(/username/i) as HTMLInputElement; expect(usernameField.validity.valid).toBe(false); expect(usernameField.validationMessage).toBe( - 'username must be more than 1 characters (was 0)', + 'username must be more than length 1 (was 0)', ); // password passwordField = screen.getByPlaceholderText(/password/i) as HTMLInputElement; expect(passwordField.validity.valid).toBe(false); expect(passwordField.validationMessage).toBe( - 'password must be more than 1 characters (was 0)', + 'password must be more than length 1 (was 0)', ); await user.type(screen.getByPlaceholderText(/username/i), 'joe'); diff --git a/arktype/src/__tests__/Form.tsx b/arktype/src/__tests__/Form.tsx index 030cc52c..39c87d9a 100644 --- a/arktype/src/__tests__/Form.tsx +++ b/arktype/src/__tests__/Form.tsx @@ -47,10 +47,10 @@ test("form's validation with arkType and TypeScript's integration", async () => await user.click(screen.getByText(/submit/i)); expect( - screen.getByText('username must be more than 1 characters (was 0)'), + screen.getByText('username must be more than length 1 (was 0)'), ).toBeInTheDocument(); expect( - screen.getByText('password must be more than 1 characters (was 0)'), + screen.getByText('password must be more than length 1 (was 0)'), ).toBeInTheDocument(); expect(handleSubmit).not.toHaveBeenCalled(); }); diff --git a/arktype/src/__tests__/__fixtures__/data.ts b/arktype/src/__tests__/__fixtures__/data.ts index 165f9285..1960acb0 100644 --- a/arktype/src/__tests__/__fixtures__/data.ts +++ b/arktype/src/__tests__/__fixtures__/data.ts @@ -1,22 +1,20 @@ import { Field, InternalFieldName } from 'react-hook-form'; -import { type, arrayOf, union } from 'arktype'; +import { type } from 'arktype'; export const schema = type({ username: 'string>2', - password: union(['string>8', '&', '/.*[A-Za-z].*/'], ['/.*\\d.*/']), + password: '/.*[A-Za-z].*/>8|/.*\\d.*/', repeatPassword: 'string>1', - accessToken: union('string', 'number'), + accessToken: 'string|number', birthYear: '19001', - 'like?': arrayOf( - type({ - id: 'number', - name: 'string>3', - }), - ), + 'like?': type({ + id: 'number', + name: 'string>3', + }).array(), dateStr: 'Date', }); diff --git a/arktype/src/__tests__/__snapshots__/arktype.ts.snap b/arktype/src/__tests__/__snapshots__/arktype.ts.snap index 1f9e4bb3..e3a69285 100644 --- a/arktype/src/__tests__/__snapshots__/arktype.ts.snap +++ b/arktype/src/__tests__/__snapshots__/arktype.ts.snap @@ -3,72 +3,463 @@ exports[`arktypeResolver > should return a single error from arktypeResolver when validation fails 1`] = ` { "errors": { - "accessToken": { - "message": "accessToken must be defined", + "accessToken": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "a number or a string", + "relativePath": [ + "accessToken", + ], + }, + "missingValueDescription": "a number or a string", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "accessToken", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "accessToken", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, - "birthYear": { - "message": "birthYear must be a number (was string)", + "birthYear": ArkError { + "code": "domain", + "data": "birthYear", + "description": "a number", + "domain": "number", + "input": { + "code": "domain", + "description": "a number", + "domain": "number", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "birthYear", + ], "ref": undefined, "type": "domain", + Symbol(ArkTypeInternalKind): "error", }, - "dateStr": { - "message": "dateStr must be defined", + "dateStr": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "a Date", + "relativePath": [ + "dateStr", + ], + }, + "missingValueDescription": "a Date", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "dateStr", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "dateStr", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, - "email": { - "message": "email must be a valid email (was '')", + "email": ArkError { + "code": "regex", + "data": "", + "description": "a valid email", + "flags": "", + "input": { + "code": "regex", + "description": "a valid email", + "flags": "", + "rule": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}$", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "email", + ], "ref": { "name": "email", }, + "rule": "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,}$", "type": "regex", + Symbol(ArkTypeInternalKind): "error", }, - "enabled": { - "message": "enabled must be defined", + "enabled": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "boolean", + "relativePath": [ + "enabled", + ], + }, + "missingValueDescription": "boolean", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "enabled", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "enabled", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, "like": [ { - "id": { - "message": "like/0/id must be a number (was string)", + "id": ArkError { + "code": "domain", + "data": "z", + "description": "a number", + "domain": "number", + "input": { + "code": "domain", + "description": "a number", + "domain": "number", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "like", + 0, + "id", + ], "ref": undefined, "type": "domain", + Symbol(ArkTypeInternalKind): "error", }, - "name": { - "message": "like/0/name must be defined", + "name": ArkError { + "code": "required", + "data": { + "id": "z", + }, + "input": { + "code": "required", + "missingValueDescription": "a string and more than length 3", + "relativePath": [ + "name", + ], + }, + "missingValueDescription": "a string and more than length 3", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "like", + 0, + "name", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "name", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, }, ], - "password": { - "message": "At password, '___' must be... -• more than 8 characters -• a string matching /.*[A-Za-z].*/", + "password": ArkError { + "code": "union", + "data": "___", + "errors": [ + ArkError { + "code": "regex", + "data": "___", + "description": "matched by .*[A-Za-z].*", + "input": { + "code": "regex", + "description": "matched by .*[A-Za-z].*", + "rule": ".*[A-Za-z].*", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "password", + ], + "rule": ".*[A-Za-z].*", + Symbol(ArkTypeInternalKind): "error", + }, + ArkError { + "code": "regex", + "data": "___", + "description": "matched by .*\\\\d.*", + "input": { + "code": "regex", + "description": "matched by .*\\\\d.*", + "rule": ".*\\\\d.*", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "password", + ], + "rule": ".*\\\\d.*", + Symbol(ArkTypeInternalKind): "error", + }, + ], + "input": { + "code": "union", + "errors": [ + ArkError { + "code": "regex", + "data": "___", + "description": "matched by .*[A-Za-z].*", + "input": { + "code": "regex", + "description": "matched by .*[A-Za-z].*", + "rule": ".*[A-Za-z].*", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "password", + ], + "rule": ".*[A-Za-z].*", + Symbol(ArkTypeInternalKind): "error", + }, + ArkError { + "code": "regex", + "data": "___", + "description": "matched by .*\\\\d.*", + "input": { + "code": "regex", + "description": "matched by .*\\\\d.*", + "rule": ".*\\\\d.*", + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "password", + ], + "rule": ".*\\\\d.*", + Symbol(ArkTypeInternalKind): "error", + }, + ], + }, + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "password", + ], "ref": { "name": "password", }, - "type": "multi", + "type": "union", + Symbol(ArkTypeInternalKind): "error", }, - "repeatPassword": { - "message": "repeatPassword must be defined", + "repeatPassword": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "a string and more than length 1", + "relativePath": [ + "repeatPassword", + ], + }, + "missingValueDescription": "a string and more than length 1", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "repeatPassword", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "repeatPassword", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, - "tags": { - "message": "tags must be defined", + "tags": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "string[]", + "relativePath": [ + "tags", + ], + }, + "missingValueDescription": "string[]", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "tags", + ], "ref": undefined, - "type": "missing", + "relativePath": [ + "tags", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, - "username": { - "message": "username must be defined", + "username": ArkError { + "code": "required", + "data": { + "birthYear": "birthYear", + "email": "", + "like": [ + { + "id": "z", + }, + ], + "password": "___", + "url": "abc", + }, + "input": { + "code": "required", + "missingValueDescription": "a string and more than length 2", + "relativePath": [ + "username", + ], + }, + "missingValueDescription": "a string and more than length 2", + "nodeConfig": { + "actual": [Function], + "description": [Function], + "expected": [Function], + "message": [Function], + "problem": [Function], + }, + "path": [ + "username", + ], "ref": { "name": "username", }, - "type": "missing", + "relativePath": [ + "username", + ], + "type": "required", + Symbol(ArkTypeInternalKind): "error", }, }, "values": {}, diff --git a/arktype/src/arktype.ts b/arktype/src/arktype.ts index d42f0025..7d74ec4b 100644 --- a/arktype/src/arktype.ts +++ b/arktype/src/arktype.ts @@ -1,34 +1,24 @@ import { FieldError, FieldErrors } from 'react-hook-form'; import { toNestErrors, validateFieldsNatively } from '@hookform/resolvers'; import type { Resolver } from './types'; -import { Problems } from 'arktype'; +import { ArkErrors } from 'arktype'; -const parseErrorSchema = (e: Problems) => { - const errors: Record = {}; - for (; e.length; ) { - const error = e[0]; - const _path = error.path.join('.'); - - if (!errors[_path]) { - errors[_path] = { message: error.message, type: error.code }; - } - - // @ts-expect-error - false positive Property 'shift' does not exist on type 'Problems'. - e.shift(); - } - - return errors; +const parseErrorSchema = (e: ArkErrors): Record => { + // copy code to type to match FieldError shape + e.forEach((e) => Object.assign(e, { type: e.code })); + // need to cast here because TS doesn't understand we added the type field + return e.byPath as never; }; export const arktypeResolver: Resolver = (schema, _schemaOptions, resolverOptions = {}) => (values, _, options) => { - const result = schema(values); + const out = schema(values); - if (result.problems) { + if (out instanceof ArkErrors) { return { values: {}, - errors: toNestErrors(parseErrorSchema(result.problems), options), + errors: toNestErrors(parseErrorSchema(out), options), }; } @@ -36,6 +26,6 @@ export const arktypeResolver: Resolver = return { errors: {} as FieldErrors, - values: resolverOptions.raw ? values : result.data, + values: resolverOptions.raw ? values : out, }; }; diff --git a/arktype/src/types.ts b/arktype/src/types.ts index 7bf33666..4e0c9da6 100644 --- a/arktype/src/types.ts +++ b/arktype/src/types.ts @@ -3,7 +3,7 @@ import { Type } from 'arktype'; export type Resolver = >( schema: T, - schemaOptions?: never, + schemaOptions?: undefined, factoryOptions?: { /** * Return the raw input values rather than the parsed values. diff --git a/package.json b/package.json index cb8e41bd..6edee975 100644 --- a/package.json +++ b/package.json @@ -233,7 +233,7 @@ "@vitejs/plugin-react": "^4.0.4", "ajv": "^8.12.0", "ajv-errors": "^3.0.0", - "arktype": "1.0.19-alpha", + "arktype": "2.0.0-dev.14", "check-export-map": "^1.3.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff5f0ab3..00d6ee4d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,8 +48,8 @@ importers: specifier: ^3.0.0 version: 3.0.0(ajv@8.12.0) arktype: - specifier: 1.0.19-alpha - version: 1.0.19-alpha + specifier: 2.0.0-dev.14 + version: 2.0.0-dev.14 check-export-map: specifier: ^1.3.0 version: 1.3.0 @@ -169,6 +169,12 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} + '@arktype/schema@0.1.6': + resolution: {integrity: sha512-VqNFO2iEFfdVTHmWtUB0DKt9ieO+2PP57jPXU/iF58m3Rmm6vMNOMqNxpi5M7hl0Xskj0M1/xErGYmTpM8gReg==} + + '@arktype/util@0.0.44': + resolution: {integrity: sha512-zC5E/+fPtc4q+yQdSnQvRzoJQkYq42QH5chKL4TSB1uFdZPTRS0FpoOnNoIZMnpFAUUt6eI6tRGV5uMa3RGTyA==} + '@babel/code-frame@7.22.10': resolution: {integrity: sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==} engines: {node: '>=6.9.0'} @@ -1324,8 +1330,8 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - arktype@1.0.19-alpha: - resolution: {integrity: sha512-tcmWSZupIjjG/hBYAlL9JzheY3WP3upCPlD7UCbY65AbzZegLZXrbCM4+TI03t2rKRwT2TuSioH27Bdqst757Q==} + arktype@2.0.0-dev.14: + resolution: {integrity: sha512-Iu0xsil97Xps+AobQAQgCeL1plDcq9iwFiLwxvTt+xvT38BbPCr2CVSW/fMeiim+9eKfUCuqZ0Bq6m7kcVfIXA==} array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} @@ -3688,6 +3694,12 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 + '@arktype/schema@0.1.6': + dependencies: + '@arktype/util': 0.0.44 + + '@arktype/util@0.0.44': {} + '@babel/code-frame@7.22.10': dependencies: '@babel/highlight': 7.22.10 @@ -4950,7 +4962,10 @@ snapshots: dependencies: dequal: 2.0.3 - arktype@1.0.19-alpha: {} + arktype@2.0.0-dev.14: + dependencies: + '@arktype/schema': 0.1.6 + '@arktype/util': 0.0.44 array-buffer-byte-length@1.0.0: dependencies: