From 7c7e454e4398737546e1da4c506532e4dd8a8b76 Mon Sep 17 00:00:00 2001 From: Nikita Zolotykh Date: Fri, 29 May 2026 13:58:20 +0200 Subject: [PATCH] feat(unstable): wake up schema renderer --- src/lib/unstable/core/Entity/Entity.tsx | 150 +- .../core/Entity/__tests__/utils.test.ts | 843 --- src/lib/unstable/core/Entity/index.ts | 2 +- src/lib/unstable/core/Entity/types.ts | 42 +- src/lib/unstable/core/Entity/utils.ts | 69 +- .../core/SchemaRenderer/SchemaRenderer.tsx | 69 +- src/lib/unstable/core/SchemaRenderer/index.ts | 2 +- .../SchemaRendererContext.ts | 7 - .../core/SchemaRendererContext/index.ts | 2 - .../core/SchemaRendererContext/types.ts | 10 - .../SchemaRendererServiceField.tsx | 53 - .../core/SchemaRendererServiceField/index.ts | 1 - .../core/SchemaRendererServiceField/types.ts | 17 - .../utils/__tests__/common.test.ts | 384 -- .../utils/__tests__/get-ajv-validate.test.ts | 178 - .../utils/__tests__/process-ajv-error.test.ts | 6088 ----------------- .../process-entity-parameters-error.test.ts | 240 - .../__tests__/process-error-items.test.ts | 241 - .../__tests__/process-errors-state.test.ts | 128 - .../utils/get-validate.ts | 80 - .../SchemaRendererServiceField/utils/index.ts | 1 - .../utils/process-error-items.ts | 71 - src/lib/unstable/core/constants.ts | 3 +- src/lib/unstable/core/index.ts | 16 +- .../__tests__/async-validation.test.ts | 337 - .../async-validation/async-validation.ts | 58 - .../core/mutators/async-validation/types.ts | 58 - src/lib/unstable/core/mutators/index.ts | 12 - .../set-errors/__tests__/set-errors.test.ts | 307 - .../core/mutators/set-errors/index.ts | 2 - .../core/mutators/set-errors/set-errors.ts | 40 - .../core/mutators/set-errors/types.ts | 43 - src/lib/unstable/core/types/components.ts | 49 +- src/lib/unstable/core/types/config.ts | 28 +- src/lib/unstable/core/types/helpers.ts | 54 +- src/lib/unstable/core/types/index.ts | 1 - src/lib/unstable/core/types/schema.ts | 898 ++- .../core/useSchemaRenderer/constants.ts | 1 + .../core/useSchemaRenderer/hooks/index.ts | 2 + .../useSchemaRenderer/hooks/useEntityState.ts | 24 + .../hooks/useSchemaRendererMutators.ts | 41 + .../unstable/core/useSchemaRenderer/index.ts | 7 + .../array-object-errors.ts | 22 + .../mutators/array-object-errors/index.ts | 2 + .../mutators/array-object-errors/types.ts | 23 + .../async-validation/async-validation.ts | 56 + .../mutators/async-validation/index.ts | 0 .../mutators/async-validation/types.ts | 58 + .../external-errors/external-errors.ts | 49 + .../mutators/external-errors/index.ts | 2 + .../mutators/external-errors/types.ts | 49 + .../core/useSchemaRenderer/mutators/index.ts | 19 + .../mutators/schema-mutators/index.ts | 2 + .../schema-mutators/schema-mutators.ts | 120 + .../mutators/schema-mutators/types.ts | 41 + .../unstable/core/useSchemaRenderer/types.ts | 42 + .../useSchemaRenderer/useSchemaRenderer.ts | 121 + .../utils/common.ts | 105 +- .../utils/get-ajv-validate.ts | 23 +- .../useSchemaRenderer/utils/get-validate.ts | 143 + .../core/useSchemaRenderer/utils/index.ts | 2 + .../utils/process-ajv-error.ts | 18 +- .../utils/process-ajv-validate-errors.ts | 50 +- .../utils/process-entity-parameters-error.ts | 11 +- .../utils/process-errors-state.ts | 18 +- .../core/useSchemaRendererField/index.ts | 1 - .../useSchemaRendererField.ts | 38 - src/lib/unstable/core/useSetErrors/index.ts | 2 - src/lib/unstable/core/useSetErrors/types.ts | 10 - .../core/useSetErrors/useSetErrors.tsx | 32 - src/lib/unstable/core/utils.ts | 6 + .../core/utils/__tests__/common.test.ts | 188 - src/lib/unstable/core/utils/common.ts | 50 - src/lib/unstable/core/utils/index.ts | 1 - src/lib/unstable/kit/Accordeon.tsx | 3 +- src/lib/unstable/kit/Any.tsx | 28 + src/lib/unstable/kit/ArrayBase.tsx | 9 +- src/lib/unstable/kit/MultiSelect.tsx | 4 +- src/lib/unstable/kit/ObjectBase.tsx | 59 +- src/lib/unstable/kit/Row.tsx | 4 +- src/lib/unstable/kit/Text.tsx | 100 +- src/lib/unstable/kit/config.ts | 80 +- src/stories/Unstable.stories.tsx | 282 +- 83 files changed, 2424 insertions(+), 10108 deletions(-) delete mode 100644 src/lib/unstable/core/Entity/__tests__/utils.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererContext/SchemaRendererContext.ts delete mode 100644 src/lib/unstable/core/SchemaRendererContext/index.ts delete mode 100644 src/lib/unstable/core/SchemaRendererContext/types.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/index.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/types.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/common.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/get-ajv-validate.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-ajv-error.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-entity-parameters-error.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-error-items.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-errors-state.test.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/get-validate.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/index.ts delete mode 100644 src/lib/unstable/core/SchemaRendererServiceField/utils/process-error-items.ts delete mode 100644 src/lib/unstable/core/mutators/async-validation/__tests__/async-validation.test.ts delete mode 100644 src/lib/unstable/core/mutators/async-validation/async-validation.ts delete mode 100644 src/lib/unstable/core/mutators/async-validation/types.ts delete mode 100644 src/lib/unstable/core/mutators/index.ts delete mode 100644 src/lib/unstable/core/mutators/set-errors/__tests__/set-errors.test.ts delete mode 100644 src/lib/unstable/core/mutators/set-errors/index.ts delete mode 100644 src/lib/unstable/core/mutators/set-errors/set-errors.ts delete mode 100644 src/lib/unstable/core/mutators/set-errors/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/constants.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/hooks/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/hooks/useEntityState.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/hooks/useSchemaRendererMutators.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/array-object-errors.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/async-validation.ts rename src/lib/unstable/core/{ => useSchemaRenderer}/mutators/async-validation/index.ts (100%) create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/external-errors.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/index.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/schema-mutators.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/types.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/useSchemaRenderer.ts rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/common.ts (53%) rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/get-ajv-validate.ts (66%) create mode 100644 src/lib/unstable/core/useSchemaRenderer/utils/get-validate.ts create mode 100644 src/lib/unstable/core/useSchemaRenderer/utils/index.ts rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/process-ajv-error.ts (81%) rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/process-ajv-validate-errors.ts (65%) rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/process-entity-parameters-error.ts (80%) rename src/lib/unstable/core/{SchemaRendererServiceField => useSchemaRenderer}/utils/process-errors-state.ts (61%) delete mode 100644 src/lib/unstable/core/useSchemaRendererField/index.ts delete mode 100644 src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts delete mode 100644 src/lib/unstable/core/useSetErrors/index.ts delete mode 100644 src/lib/unstable/core/useSetErrors/types.ts delete mode 100644 src/lib/unstable/core/useSetErrors/useSetErrors.tsx create mode 100644 src/lib/unstable/core/utils.ts delete mode 100644 src/lib/unstable/core/utils/__tests__/common.test.ts delete mode 100644 src/lib/unstable/core/utils/common.ts delete mode 100644 src/lib/unstable/core/utils/index.ts create mode 100644 src/lib/unstable/kit/Any.tsx diff --git a/src/lib/unstable/core/Entity/Entity.tsx b/src/lib/unstable/core/Entity/Entity.tsx index 5aa2744b..bfacbbf6 100644 --- a/src/lib/unstable/core/Entity/Entity.tsx +++ b/src/lib/unstable/core/Entity/Entity.tsx @@ -1,70 +1,144 @@ import React from 'react'; -import {SchemaRendererContext} from '../SchemaRendererContext'; +import {useField} from 'react-final-form'; + +import {JsonSchemaType, SchemaRendererMode} from '../constants'; import type {JsonSchema} from '../types'; -import {useSchemaRendererField} from '../useSchemaRendererField'; +import {useEntityState} from '../useSchemaRenderer'; import {getRenderKit} from './utils'; export interface EntityProps { name: string; - schema: JsonSchema; + schema?: JsonSchema; } -const EntityComponent: React.FC = ({name, schema}) => { - const {config, mode} = React.useContext(SchemaRendererContext); - - const renderKit = React.useMemo( - () => getRenderKit({config, mode, schema}), - [config, mode, schema], - ); +const EntityComponent: React.FC = ({name, schema: schemaProps = {}}) => { + const {config, error, mode} = useEntityState(name); const options = React.useMemo( () => ({ - data: {schema}, - defaultValue: schema.default, + data: {schema: schemaProps}, + defaultValue: schemaProps.default, subscription: { - error: true, + data: true, + error: + schemaProps.type !== JsonSchemaType.Array && + schemaProps.type !== JsonSchemaType.Object, submitFailed: true, touched: true, validating: true, value: true, }, }), - [schema], + [schemaProps], ); - const field = useSchemaRendererField(name, options); + const field = useField(name, options); - if (!renderKit.View) { - return null; - } + const schema = React.useMemo( + () => field.meta.data?.schema || schemaProps, + [field.meta.data?.schema, schemaProps], + ); + + const meta = React.useMemo(() => { + if (schema.type === JsonSchemaType.Array || schema.type === JsonSchemaType.Object) { + return { + ...field.meta, + error, + }; + } + + return field.meta; + }, [field.meta, error, schema?.type]); + + const renderKit = React.useMemo(() => getRenderKit({config, schema}), [config, schema]); let content = null; - if (renderKit.independent) { - content = ( - - ); - } else { - content = ; - - if (renderKit.Wrapper) { - content = ( - - {content} - - ); + if (mode === SchemaRendererMode.Form) { + const formKit = renderKit[SchemaRendererMode.Form]; + + if (formKit.Component) { + if (formKit.independent) { + content = ( + + ); + } else { + content = ( + + ); + + if (formKit.Wrapper) { + content = ( + + {content} + + ); + } + } + } + } + + if (mode === SchemaRendererMode.Overview) { + const overviewKit = renderKit[SchemaRendererMode.Overview]; + + if (overviewKit.Component) { + if (overviewKit.independent) { + content = ( + + ); + } else { + content = ( + + ); + + if (overviewKit.Wrapper) { + content = ( + + {content} + + ); + } + } } } - return
{content}
; + return content ?
{content}
: null; }; export const Entity = React.memo(EntityComponent); diff --git a/src/lib/unstable/core/Entity/__tests__/utils.test.ts b/src/lib/unstable/core/Entity/__tests__/utils.test.ts deleted file mode 100644 index 232053e7..00000000 --- a/src/lib/unstable/core/Entity/__tests__/utils.test.ts +++ /dev/null @@ -1,843 +0,0 @@ -import {JsonSchemaType, SchemaRendererMode} from '../../constants'; -import type { - IndependentView, - JsonSchemaArray, - JsonSchemaBoolean, - JsonSchemaNumber, - JsonSchemaObject, - JsonSchemaString, - SchemaRendererConfig, - SimpleView, - Wrapper, -} from '../../types'; -import {getRenderKit} from '../utils'; - -const SimpleArrayFormComponent: SimpleView = () => null; -const SimpleBooleanFormComponent: SimpleView = () => null; -const SimpleNumberFormComponent: SimpleView = () => null; -const SimpleObjectFormComponent: SimpleView = () => null; -const SimpleStringFormComponent: SimpleView = () => null; - -const SimpleArrayOverviewComponent: SimpleView = () => null; -const SimpleBooleanOverviewComponent: SimpleView = () => null; -const SimpleNumberOverviewComponent: SimpleView = () => null; -const SimpleObjectOverviewComponent: SimpleView = () => null; -const SimpleStringOverviewComponent: SimpleView = () => null; - -const IndependentArrayFormComponent: IndependentView = () => null; -const IndependentBooleanFormComponent: IndependentView = () => null; -const IndependentNumberFormComponent: IndependentView = () => null; -const IndependentObjectFormComponent: IndependentView = () => null; -const IndependentStringFormComponent: IndependentView = () => null; - -const IndependentArrayOverviewComponent: IndependentView = () => null; -const IndependentBooleanOverviewComponent: IndependentView = () => null; -const IndependentNumberOverviewComponent: IndependentView = () => null; -const IndependentObjectOverviewComponent: IndependentView = () => null; -const IndependentStringOverviewComponent: IndependentView = () => null; - -const ArrayWrapper: Wrapper = () => null; -const BooleanWrapper: Wrapper = () => null; -const NumberWrapper: Wrapper = () => null; -const ObjectWrapper: Wrapper = () => null; -const StringWrapper: Wrapper = () => null; - -const schemaRendererConfig: SchemaRendererConfig = { - [JsonSchemaType.Array]: { - views: { - simple: { - [SchemaRendererMode.Form]: { - Component: SimpleArrayFormComponent, - }, - [SchemaRendererMode.Overview]: { - Component: SimpleArrayOverviewComponent, - }, - }, - independent: { - [SchemaRendererMode.Form]: { - Component: IndependentArrayFormComponent, - independent: true, - }, - [SchemaRendererMode.Overview]: { - Component: IndependentArrayOverviewComponent, - independent: true, - }, - }, - }, - wrappers: { - base: ArrayWrapper, - }, - validators: {}, - }, - [JsonSchemaType.Boolean]: { - views: { - simple: { - [SchemaRendererMode.Form]: { - Component: SimpleBooleanFormComponent, - }, - [SchemaRendererMode.Overview]: { - Component: SimpleBooleanOverviewComponent, - }, - }, - independent: { - [SchemaRendererMode.Form]: { - Component: IndependentBooleanFormComponent, - independent: true, - }, - [SchemaRendererMode.Overview]: { - Component: IndependentBooleanOverviewComponent, - independent: true, - }, - }, - }, - wrappers: { - base: BooleanWrapper, - }, - validators: {}, - }, - [JsonSchemaType.Number]: { - views: { - simple: { - [SchemaRendererMode.Form]: { - Component: SimpleNumberFormComponent, - }, - [SchemaRendererMode.Overview]: { - Component: SimpleNumberOverviewComponent, - }, - }, - independent: { - [SchemaRendererMode.Form]: { - Component: IndependentNumberFormComponent, - independent: true, - }, - [SchemaRendererMode.Overview]: { - Component: IndependentNumberOverviewComponent, - independent: true, - }, - }, - }, - wrappers: { - base: NumberWrapper, - }, - validators: {}, - }, - [JsonSchemaType.Object]: { - views: { - simple: { - [SchemaRendererMode.Form]: { - Component: SimpleObjectFormComponent, - }, - [SchemaRendererMode.Overview]: { - Component: SimpleObjectOverviewComponent, - }, - }, - independent: { - [SchemaRendererMode.Form]: { - Component: IndependentObjectFormComponent, - independent: true, - }, - [SchemaRendererMode.Overview]: { - Component: IndependentObjectOverviewComponent, - independent: true, - }, - }, - }, - wrappers: { - base: ObjectWrapper, - }, - validators: {}, - }, - [JsonSchemaType.String]: { - views: { - simple: { - [SchemaRendererMode.Form]: { - Component: SimpleStringFormComponent, - }, - [SchemaRendererMode.Overview]: { - Component: SimpleStringOverviewComponent, - }, - }, - independent: { - [SchemaRendererMode.Form]: { - Component: IndependentStringFormComponent, - independent: true, - }, - [SchemaRendererMode.Overview]: { - Component: IndependentStringOverviewComponent, - independent: true, - }, - }, - }, - wrappers: { - base: StringWrapper, - }, - validators: {}, - }, -}; - -describe('core/Entity/utils', () => { - describe('getRenderKit', () => { - test('should return render kit with simple array form component', () => { - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: SimpleArrayFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ArrayWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with simple array overview component', () => { - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: SimpleArrayOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ArrayWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with independent array form component', () => { - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: IndependentArrayFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ArrayWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with independent array overview component', () => { - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: IndependentArrayOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ArrayWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with simple boolean form component', () => { - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: SimpleBooleanFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: BooleanWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with simple boolean overview component', () => { - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: SimpleBooleanOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: BooleanWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with independent boolean form component', () => { - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: IndependentBooleanFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: BooleanWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with independent boolean overview component', () => { - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: IndependentBooleanOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: BooleanWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with simple number form component', () => { - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: SimpleNumberFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: NumberWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with simple number overview component', () => { - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: SimpleNumberOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: NumberWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with independent number form component', () => { - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: IndependentNumberFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: NumberWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with independent number overview component', () => { - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: IndependentNumberOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: NumberWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with simple object form component', () => { - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: SimpleObjectFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ObjectWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with simple object overview component', () => { - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: SimpleObjectOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ObjectWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with independent object form component', () => { - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: IndependentObjectFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ObjectWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with independent object overview component', () => { - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: IndependentObjectOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: ObjectWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with simple string form component', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: SimpleStringFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: StringWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with simple string overview component', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: SimpleStringOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: StringWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with independent string form component', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: IndependentStringFormComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: StringWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with independent string overview component', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'independent', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Overview, - schema, - }); - - expect(result).toEqual({ - View: IndependentStringOverviewComponent, - viewProps: {viewProp: 'viewProp'}, - Wrapper: StringWrapper, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: true, - }); - }); - - test('should return render kit with undefined components when viewType/wrapperType are missing', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: undefined, - viewProps: {viewProp: 'viewProp'}, - Wrapper: undefined, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with undefined components when viewType/wrapperType are unknown', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'unknown', - wrapperType: 'unknown', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: undefined, - viewProps: {viewProp: 'viewProp'}, - Wrapper: undefined, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - - test('should return render kit with undefined components when entityParameters is empty', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: undefined, - viewProps: {}, - Wrapper: undefined, - wrapperProps: {}, - independent: undefined, - }); - }); - - test('should return render kit with undefined components when entityParameters is null', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: null as any, - }; - - const result = getRenderKit({ - config: schemaRendererConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: undefined, - viewProps: {}, - Wrapper: undefined, - wrapperProps: {}, - independent: undefined, - }); - }); - - test('should return render kit with undefined components when config is empty', () => { - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - viewType: 'simple', - wrapperType: 'base', - viewProps: {viewProp: 'viewProp'}, - wrapperProps: {wrapperProp: 'wrapperProp'}, - }, - }; - const emptyConfig = { - [JsonSchemaType.String]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Array]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Boolean]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Number]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Object]: { - views: {}, - wrappers: {}, - validators: {}, - }, - }; - - const result = getRenderKit({ - config: emptyConfig, - mode: SchemaRendererMode.Form, - schema, - }); - - expect(result).toEqual({ - View: undefined, - viewProps: {viewProp: 'viewProp'}, - Wrapper: undefined, - wrapperProps: {wrapperProp: 'wrapperProp'}, - independent: undefined, - }); - }); - }); -}); diff --git a/src/lib/unstable/core/Entity/index.ts b/src/lib/unstable/core/Entity/index.ts index 4bc5e410..b63c6df1 100644 --- a/src/lib/unstable/core/Entity/index.ts +++ b/src/lib/unstable/core/Entity/index.ts @@ -1 +1 @@ -export * from './Entity'; +export {Entity, type EntityProps} from './Entity'; diff --git a/src/lib/unstable/core/Entity/types.ts b/src/lib/unstable/core/Entity/types.ts index 2d597c8c..2e7a6dcc 100644 --- a/src/lib/unstable/core/Entity/types.ts +++ b/src/lib/unstable/core/Entity/types.ts @@ -1,30 +1,24 @@ import type {SchemaRendererMode} from '../constants'; -import type { - IndependentView, - JsonSchema, - SchemaRendererConfig, - SimpleView, - Wrapper, -} from '../types'; +import type {Control, JsonSchema, SchemaRendererConfig, View, Wrapper} from '../types'; export type GetRenderKitParams = { - config: SchemaRendererConfig; - mode: SchemaRendererMode; + config?: SchemaRendererConfig; schema: Schema; }; -export type GetRenderKitReturn = - | { - View: SimpleView | undefined; - Wrapper: Wrapper | undefined; - independent: false | undefined; - viewProps: Record; - wrapperProps: Record; - } - | { - View: IndependentView; - Wrapper: Wrapper | undefined; - independent: true; - viewProps: Record; - wrapperProps: Record; - }; +export type GetRenderKitReturn = { + [SchemaRendererMode.Form]: { + Component: Control | undefined; + props: Record; + independent: boolean | undefined; + Wrapper: Wrapper | undefined; + wrapperProps: Record; + }; + [SchemaRendererMode.Overview]: { + Component: View | undefined; + props: Record; + independent: boolean | undefined; + Wrapper: Wrapper | undefined; + wrapperProps: Record; + }; +}; diff --git a/src/lib/unstable/core/Entity/utils.ts b/src/lib/unstable/core/Entity/utils.ts index 72f6984d..e1deb7bc 100644 --- a/src/lib/unstable/core/Entity/utils.ts +++ b/src/lib/unstable/core/Entity/utils.ts @@ -1,38 +1,71 @@ import get from 'lodash/get'; -import {EMPTY_OBJECT} from '../constants'; -import type {JsonSchema, View, Wrapper} from '../types'; +import {EMPTY_OBJECT, type JsonSchemaType, SchemaRendererMode} from '../constants'; +import type {Control, JsonSchema, View, Wrapper} from '../types'; +import {getSchemaType} from '../utils'; import type {GetRenderKitParams, GetRenderKitReturn} from './types'; export const getRenderKit = ({ config, - mode, schema, }: GetRenderKitParams): GetRenderKitReturn => { - const viewType: string | undefined = get(schema, 'entityParameters.viewType'); - const ViewComponent: View | undefined = get( + const schemaType: JsonSchemaType = getSchemaType(schema); + + const controlType: string | undefined = get(schema, 'entityParameters.controlType'); + const ControlComponent: Control | undefined = get( + config, + `${schemaType}.controls.${controlType}.Component`, + ); + const controlProps: Record = + get(schema, 'entityParameters.controlProps') || EMPTY_OBJECT; + const controlIndependent: boolean | undefined = get( + config, + `${schemaType}.controls.${controlType}.independent`, + ); + + const controlWrapperType: string | undefined = get( + schema, + 'entityParameters.controlWrapperType', + ); + const ControlWrapperComponent: Wrapper | undefined = get( config, - `${schema.type}.views.${viewType}.${mode}.Component`, + `${schemaType}.wrappers.${controlWrapperType}`, ); - const viewProps = get(schema, 'entityParameters.viewProps', EMPTY_OBJECT); - const independent: boolean | undefined = get( + const controlWrapperProps: Record = + get(schema, 'entityParameters.controlWrapperProps') || EMPTY_OBJECT; + + const viewType: string | undefined = get(schema, 'entityParameters.viewType'); + const ViewComponent: View | undefined = get(config, `${schemaType}.views.${viewType}`); + const viewProps: Record = + get(schema, 'entityParameters.viewProps') || EMPTY_OBJECT; + const viewIndependent: boolean | undefined = get( config, - `${schema.type}.views.${viewType}.${mode}.independent`, + `${schemaType}.views.${viewType}.independent`, ); - const wrapperType: string | undefined = get(schema, 'entityParameters.wrapperType'); - const WrapperComponent: Wrapper | undefined = get( + const viewWrapperType: string | undefined = get(schema, 'entityParameters.viewWrapperType'); + const ViewWrapperComponent: Wrapper | undefined = get( config, - `${schema.type}.wrappers.${wrapperType}`, + `${schemaType}.wrappers.${viewWrapperType}`, ); - const wrapperProps = get(schema, 'entityParameters.wrapperProps', EMPTY_OBJECT); + const viewWrapperProps: Record = + get(schema, 'entityParameters.viewWrapperProps') || EMPTY_OBJECT; return { - View: ViewComponent, - viewProps, - Wrapper: WrapperComponent, - wrapperProps, - independent, + [SchemaRendererMode.Form]: { + Component: ControlComponent, + props: controlProps, + independent: controlIndependent, + Wrapper: ControlWrapperComponent, + wrapperProps: controlWrapperProps, + }, + [SchemaRendererMode.Overview]: { + Component: ViewComponent, + props: viewProps, + independent: viewIndependent, + Wrapper: ViewWrapperComponent, + wrapperProps: viewWrapperProps, + }, }; }; diff --git a/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx b/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx index cee5f87e..8e2d0e38 100644 --- a/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx +++ b/src/lib/unstable/core/SchemaRenderer/SchemaRenderer.tsx @@ -1,73 +1,14 @@ import React from 'react'; import {Entity} from '../Entity'; -import {SchemaRendererContext, type SchemaRendererContextType} from '../SchemaRendererContext'; -import {SchemaRendererServiceField} from '../SchemaRendererServiceField'; -import type {SchemaRendererMode} from '../constants'; -import type {ErrorMessages, JsonSchema, SchemaRendererConfig} from '../types'; +import {type UseSchemaRendererParams, useSchemaRenderer} from '../useSchemaRenderer'; -export interface SchemaRendererProps { - config: SchemaRendererConfig; - errorMessages?: ErrorMessages; - mode: SchemaRendererMode; - /** - * The `name` prop must be a non-empty string. - * - * In `final-form` and `react-final-form`, the `name` is used as a key to register - * the field within the form. If you pass an empty string (`name=""`), the field will - * not be registered, its value will not be tracked, and validation will not work. - * - * This can lead to: - * - the field being missing from the form `values`; - * - the `validate` function not being called; - * - no error or touched state updates; - * - inconsistent or broken form behavior. - * - * Always provide a unique, non-empty string for the field name. - * - * @example - * // Incorrect - * - * - * // Correct - * - */ - name: string; - schema: JsonSchema; -} +export interface SchemaRendererProps extends Omit {} -const SchemaRendererComponent: React.FC = ({ - config, - errorMessages, - mode, - name: headName, - schema, -}) => { - const serviceFieldName = `DYNAMIC_FORMS_SERVICE_FIELD.${headName}`; +const SchemaRendererComponent: React.FC = (props) => { + const {schema} = useSchemaRenderer(props); - const context: SchemaRendererContextType = React.useMemo( - () => ({ - config, - mode, - headName, - schema, - serviceFieldName, - }), - [config, mode, headName, schema, serviceFieldName], - ); - - return ( - - - - - ); + return schema ? : null; }; export const SchemaRenderer = React.memo(SchemaRendererComponent); diff --git a/src/lib/unstable/core/SchemaRenderer/index.ts b/src/lib/unstable/core/SchemaRenderer/index.ts index a78a1425..d8922110 100644 --- a/src/lib/unstable/core/SchemaRenderer/index.ts +++ b/src/lib/unstable/core/SchemaRenderer/index.ts @@ -1 +1 @@ -export * from './SchemaRenderer'; +export {SchemaRenderer, type SchemaRendererProps} from './SchemaRenderer'; diff --git a/src/lib/unstable/core/SchemaRendererContext/SchemaRendererContext.ts b/src/lib/unstable/core/SchemaRendererContext/SchemaRendererContext.ts deleted file mode 100644 index 83335032..00000000 --- a/src/lib/unstable/core/SchemaRendererContext/SchemaRendererContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -import type {SchemaRendererContextType} from './types'; - -export const SchemaRendererContext = React.createContext( - null as unknown as SchemaRendererContextType, -); diff --git a/src/lib/unstable/core/SchemaRendererContext/index.ts b/src/lib/unstable/core/SchemaRendererContext/index.ts deleted file mode 100644 index 9e2483b2..00000000 --- a/src/lib/unstable/core/SchemaRendererContext/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './SchemaRendererContext'; -export * from './types'; diff --git a/src/lib/unstable/core/SchemaRendererContext/types.ts b/src/lib/unstable/core/SchemaRendererContext/types.ts deleted file mode 100644 index c5073ee9..00000000 --- a/src/lib/unstable/core/SchemaRendererContext/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type {SchemaRendererMode} from '../constants'; -import type {JsonSchema, SchemaRendererConfig} from '../types'; - -export interface SchemaRendererContextType { - config: SchemaRendererConfig; - mode: SchemaRendererMode; - headName: string; - schema: JsonSchema; - serviceFieldName: string; -} diff --git a/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx b/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx deleted file mode 100644 index d96de57d..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/SchemaRendererServiceField.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; - -import {useField, useForm} from 'react-final-form'; - -import type {ErrorMessages, JsonSchema, SchemaRendererConfig} from '../types'; - -import {getValidate} from './utils'; - -export interface SchemaRendererProps { - config: SchemaRendererConfig; - errorMessages?: ErrorMessages; - headName: string; - mainSchema: JsonSchema; - serviceFieldName: string; -} - -const SchemaRendererServiceFieldComponent: React.FC = ({ - config, - errorMessages, - headName, - mainSchema, - serviceFieldName, -}) => { - const {setValidationCache, setValidationWaiters} = useForm().mutators; - - const validate = React.useMemo( - () => - getValidate({ - config, - errorMessages, - headName, - mainSchema, - serviceFieldName, - setValidationCache, - setValidationWaiters, - }), - [ - config, - errorMessages, - headName, - mainSchema, - serviceFieldName, - setValidationCache, - setValidationWaiters, - ], - ); - - useField(serviceFieldName, {data: {schema: mainSchema}, subscription: {}, validate}); - - return null; -}; - -export const SchemaRendererServiceField = React.memo(SchemaRendererServiceFieldComponent); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/index.ts b/src/lib/unstable/core/SchemaRendererServiceField/index.ts deleted file mode 100644 index faf5298a..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SchemaRendererServiceField'; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/types.ts b/src/lib/unstable/core/SchemaRendererServiceField/types.ts deleted file mode 100644 index 88710de8..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/types.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type {ErrorObject} from 'ajv'; - -import type {FieldValue, JsonSchema, SyncValidateError, Validator} from '../types'; - -export type EntityParametersError = ErrorObject< - 'entityParameters', - { - schema: JsonSchema; - validator: Validator; - value: FieldValue | null | undefined; - } ->; - -export interface ValidateErrorItem { - error: SyncValidateError; - path: string[]; -} diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/common.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/common.test.ts deleted file mode 100644 index f455c9cd..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/common.test.ts +++ /dev/null @@ -1,384 +0,0 @@ -import {JsonSchemaType} from '../../../constants'; -import type { - JsonSchemaArray, - JsonSchemaNumber, - JsonSchemaObject, - JsonSchemaString, -} from '../../../types'; -import { - getSchemaByInstancePath, - getSchemaBySchemaPath, - getValuePaths, - parseInstancePath, - parseSchemaPath, -} from '../common'; - -const nameSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const streetSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const citySchema: JsonSchemaString = {type: JsonSchemaType.String}; -const addressSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: {street: streetSchema, city: citySchema}, -}; -const stringTagSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const numberTagSchema: JsonSchemaNumber = {type: JsonSchemaType.Number}; -const tagsSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - items: [stringTagSchema, numberTagSchema], -}; -const labelSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const labelsSchema: JsonSchemaArray = {type: JsonSchemaType.Array, items: labelSchema}; -const specialFieldSchema: JsonSchemaNumber = {type: JsonSchemaType.Number}; -const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - name: nameSchema, - address: addressSchema, - tags: tagsSchema, - labels: labelsSchema, - 'special/field': specialFieldSchema, - }, -}; - -describe('SchemaRendererServiceField/utils/common', () => { - describe('parseSchemaPath', () => { - it('should parse a simple schema path', () => { - const result = parseSchemaPath('#/properties/name/minLength'); - - expect(result).toEqual(['properties', 'name']); - }); - - it('should parse a nested schema path', () => { - const result = parseSchemaPath('#/properties/address/properties/street/maxLength'); - - expect(result).toEqual(['properties', 'address', 'properties', 'street']); - }); - - it('should handle URL encoded characters', () => { - const result = parseSchemaPath('#/properties/special%20field/minimum'); - - expect(result).toEqual(['properties', 'special field']); - }); - - it('should handle schema path with only one segment', () => { - const result = parseSchemaPath('#/type'); - - expect(result).toEqual([]); - }); - - it('should handle path with tilde escaping for slash', () => { - const result = parseSchemaPath('#/properties/path~1to~1file/maximum'); - - expect(result).toEqual(['properties', 'path/to/file']); - }); - - it('should handle path with tilde escaping for tilde', () => { - const result = parseSchemaPath('#/properties/tilde~0character/not'); - - expect(result).toEqual(['properties', 'tilde~character']); - }); - - it('should handle complex paths with multiple escaped characters', () => { - const result = parseSchemaPath( - '#/properties/complex~1path~0with~1special%20chars/const', - ); - - expect(result).toEqual(['properties', 'complex/path~with/special chars']); - }); - - it('should handle paths with array indexes', () => { - const result = parseSchemaPath('#/properties/items/0/name/minLength'); - - expect(result).toEqual(['properties', 'items', '0', 'name']); - }); - - it('should return an empty array for invalid schema path format', () => { - const result = parseSchemaPath('invalid-path'); - - expect(result).toEqual([]); - }); - }); - - describe('parseInstancePath', () => { - it('should return an empty array for empty instance path', () => { - const result = parseInstancePath(''); - - expect(result).toEqual([]); - }); - - it('should parse a simple instance path', () => { - const result = parseInstancePath('/name'); - - expect(result).toEqual(['name']); - }); - - it('should parse a nested instance path', () => { - const result = parseInstancePath('/address/street'); - - expect(result).toEqual(['address', 'street']); - }); - - it('should handle path with tilde escaping for slash', () => { - const result = parseInstancePath('/path~1to~1file'); - - expect(result).toEqual(['path/to/file']); - }); - - it('should handle path with tilde escaping for tilde', () => { - const result = parseInstancePath('/tilde~0character'); - - expect(result).toEqual(['tilde~character']); - }); - - it('should handle complex paths with multiple escaped characters', () => { - const result = parseInstancePath('/complex~1path~0with~1special'); - - expect(result).toEqual(['complex/path~with/special']); - }); - - it('should handle paths with array indexes', () => { - const result = parseInstancePath('/items/0/name'); - - expect(result).toEqual(['items', '0', 'name']); - }); - - it('should handle deep nested paths', () => { - const result = parseInstancePath('/users/5/addresses/2/street'); - - expect(result).toEqual(['users', '5', 'addresses', '2', 'street']); - }); - - it('should handle paths with consecutive slashes', () => { - const result = parseInstancePath('/path//with//double/slashes'); - - expect(result).toEqual(['path', '', 'with', '', 'double', 'slashes']); - }); - }); - - describe('getSchemaBySchemaPath', () => { - it('should return the main schema for empty path array', () => { - const result = getSchemaBySchemaPath('#/', mainSchema); - - expect(result).toBe(mainSchema); - }); - - it('should return the schema for a simple path', () => { - const result = getSchemaBySchemaPath('#/properties/name/minLength', mainSchema); - - expect(result).toBe(nameSchema); - }); - - it('should return the schema for a nested path', () => { - const result = getSchemaBySchemaPath( - '#/properties/address/properties/street/minLength', - mainSchema, - ); - - expect(result).toBe(streetSchema); - }); - - it('should return the schema for a path with array indexes', () => { - const result = getSchemaBySchemaPath('#/properties/tags/items/1/maximum', mainSchema); - - expect(result).toBe(numberTagSchema); - }); - - it('should handle array with single schema for items', () => { - const result = getSchemaBySchemaPath('#/properties/labels/items/minLength', mainSchema); - - expect(result).toBe(labelSchema); - }); - - it('should return undefined for a path that does not exist', () => { - const result = getSchemaBySchemaPath('#/properties/nonexistent/minLength', mainSchema); - - expect(result).toBeUndefined(); - }); - - it('should handle paths with special characters', () => { - const result = getSchemaBySchemaPath( - '#/properties/special~1field/maxLength', - mainSchema, - ); - - expect(result).toBe(specialFieldSchema); - }); - }); - - describe('getSchemaByInstancePath', () => { - it('should return the main schema for empty instance path', () => { - const result = getSchemaByInstancePath('', mainSchema); - - expect(result).toBe(mainSchema); - }); - - it('should return the schema for a simple object property path', () => { - const result = getSchemaByInstancePath('/name', mainSchema); - - expect(result).toBe(nameSchema); - }); - - it('should return the schema for a nested object property path', () => { - const result = getSchemaByInstancePath('/address/street', mainSchema); - - expect(result).toBe(streetSchema); - }); - - it('should return the schema for an array item path with specific index', () => { - const result = getSchemaByInstancePath('/tags/1', mainSchema); - - expect(result).toBe(numberTagSchema); - }); - - it('should handle array with single schema for items', () => { - const result = getSchemaByInstancePath('/labels/0', mainSchema); - - expect(result).toBe(labelSchema); - }); - - it('should return undefined for a path that does not exist', () => { - const result = getSchemaByInstancePath('/nonexistent', mainSchema); - - expect(result).toBeUndefined(); - }); - - it('should handle paths with special characters', () => { - const result = getSchemaByInstancePath('/special~1field', mainSchema); - - expect(result).toBe(specialFieldSchema); - }); - - it('should return undefined for a path that starts valid but ends invalid', () => { - const result = getSchemaByInstancePath('/address/nonexistent', mainSchema); - - expect(result).toBeUndefined(); - }); - - it('should return undefined when traversing non-object and non-array schemas', () => { - const result = getSchemaByInstancePath('/name/invalid', mainSchema); - - expect(result).toBeUndefined(); - }); - }); - - describe('getValuePaths', () => { - it('should return a path for a primitive value', () => { - const result = getValuePaths('test'); - - expect(result).toEqual([]); - }); - - it('should return a path for a number value', () => { - const result = getValuePaths(42); - - expect(result).toEqual([]); - }); - - it('should return a path for a boolean value', () => { - const result = getValuePaths(true); - - expect(result).toEqual([]); - }); - - it('should return a path for null', () => { - const result = getValuePaths(null); - - expect(result).toEqual([]); - }); - - it('should return a path for undefined', () => { - const result = getValuePaths(undefined); - - expect(result).toEqual([]); - }); - - it('should return an empty array for an empty array', () => { - const result = getValuePaths([]); - - expect(result).toEqual([]); - }); - - it('should return an empty array for an empty object', () => { - const result = getValuePaths({}); - - expect(result).toEqual([]); - }); - - it('should return paths for an array of primitive values', () => { - const result = getValuePaths(['a', 'b', 'c']); - - expect(result).toEqual([['0'], ['1'], ['2']]); - }); - - it('should return paths for an object with primitive values', () => { - const result = getValuePaths({a: 1, b: 2, c: 3}); - - expect(result).toEqual([['a'], ['b'], ['c']]); - }); - - it('should return paths for a nested array', () => { - const result = getValuePaths([ - ['a', 'b'], - ['c', 'd'], - ]); - - expect(result).toEqual([ - ['0', '0'], - ['0', '1'], - ['1', '0'], - ['1', '1'], - ]); - }); - - it('should return paths for a nested object', () => { - const result = getValuePaths({a: {b: 1}, c: {d: 2}}); - - expect(result).toEqual([ - ['a', 'b'], - ['c', 'd'], - ]); - }); - - it('should return paths for a mixed nested array and object', () => { - const result = getValuePaths([{a: 1}, {b: 2}]); - - expect(result).toEqual([ - ['0', 'a'], - ['1', 'b'], - ]); - }); - - it('should return paths for a complex nested structure', () => { - const result = getValuePaths({ - a: [1, 2], - b: {c: 3, d: [4, {e: 5}]}, - }); - - expect(result).toEqual([ - ['a', '0'], - ['a', '1'], - ['b', 'c'], - ['b', 'd', '0'], - ['b', 'd', '1', 'e'], - ]); - }); - - it('should handle initial path parameter', () => { - const result = getValuePaths( - { - a: [1, 2], - b: {c: 3, d: [4, {e: 5}]}, - }, - ['parent'], - ); - - expect(result).toEqual([ - ['parent', 'a', '0'], - ['parent', 'a', '1'], - ['parent', 'b', 'c'], - ['parent', 'b', 'd', '0'], - ['parent', 'b', 'd', '1', 'e'], - ]); - }); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/get-ajv-validate.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/get-ajv-validate.test.ts deleted file mode 100644 index 6556e13d..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/get-ajv-validate.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import {JsonSchemaType} from '../../../constants'; -import type { - JsonSchemaNumber, - JsonSchemaObject, - JsonSchemaString, - SchemaRendererConfig, - Validator, -} from '../../../types'; -import {getAjvValidate} from '../get-ajv-validate'; - -describe('SchemaRendererServiceField/utils/get-ajv-validate', () => { - const emailValidator: Validator = (value) => { - if (typeof value !== 'string' || !value.includes('@')) { - return 'Invalid email format'; - } - - return false; - }; - const emailSchema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - validatorType: 'emailValidator', - }, - }; - const emailValue = 'email'; - const emailError = { - instancePath: '/email', - keyword: 'entityParameters', - message: '', - params: {validator: emailValidator, value: emailValue, schema: emailSchema}, - schemaPath: '#/properties/email/entityParameters', - }; - - const ageValidator: Validator = (value) => { - if (typeof value !== 'number' || value < 18 || value > 100) { - return 'Age must be between 18 and 100'; - } - - return false; - }; - const ageSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - validatorType: 'ageValidator', - }, - }; - const ageValue = 10; - const ageError = { - instancePath: '/age', - keyword: 'entityParameters', - message: '', - params: {validator: ageValidator, value: ageValue, schema: ageSchema}, - schemaPath: '#/properties/age/entityParameters', - }; - - it('should validate with entityParameters validator', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: {email: emailSchema}, - }; - - const config: SchemaRendererConfig = { - [JsonSchemaType.Array]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Boolean]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Number]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Object]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.String]: { - views: {}, - wrappers: {}, - validators: {emailValidator}, - }, - }; - - const ajvValidate = getAjvValidate({config, mainSchema}); - - ajvValidate({email: emailValue}); - - expect(ajvValidate.errors).toEqual([emailError]); - }); - - it('should handle multiple validators for different fields', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: {email: emailSchema, age: ageSchema}, - }; - - const config: SchemaRendererConfig = { - [JsonSchemaType.Array]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Boolean]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Number]: { - views: {}, - wrappers: {}, - validators: {ageValidator}, - }, - [JsonSchemaType.Object]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.String]: { - views: {}, - wrappers: {}, - validators: {emailValidator}, - }, - }; - - const ajvValidate = getAjvValidate({config, mainSchema}); - - ajvValidate({email: emailValue, age: ageValue}); - - expect(ajvValidate.errors).toEqual([emailError, ageError]); - }); - - it('should handle empty validator type', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: {email: emailSchema, age: ageSchema}, - }; - - const config: SchemaRendererConfig = { - [JsonSchemaType.Array]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Boolean]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Number]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.Object]: { - views: {}, - wrappers: {}, - validators: {}, - }, - [JsonSchemaType.String]: { - views: {}, - wrappers: {}, - validators: {emailValidator}, - }, - }; - - const ajvValidate = getAjvValidate({config, mainSchema}); - - ajvValidate({email: emailValue, age: ageValue}); - - expect(ajvValidate.errors).toEqual([emailError]); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-ajv-error.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-ajv-error.test.ts deleted file mode 100644 index ac29e5a3..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-ajv-error.test.ts +++ /dev/null @@ -1,6088 +0,0 @@ -import Ajv from 'ajv'; - -import {JsonSchemaType} from '../../../constants'; -import type { - ArrayValue, - JsonSchemaArray, - JsonSchemaBoolean, - JsonSchemaNumber, - JsonSchemaObject, - JsonSchemaString, - ObjectValue, -} from '../../../types'; -import {processAjvError} from '../process-ajv-error'; - -const ajv = new Ajv({ - allErrors: true, - allowMatchingProperties: true, -}); - -describe('SchemaRendererServiceField/utils/process-ajv-error', () => { - describe('with type array schema', () => { - const mainSchema: JsonSchemaArray = {type: JsonSchemaType.Array}; - const errorMessages = {type: 'type error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/type', - keyword: 'type', - params: { - type: 'array', - }, - message: 'must be array', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'not array'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const typeErrorMessage = 'type error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - type: typeErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: typeErrorMessage, - }); - }); - }); - - describe('with boolean contains array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - contains: true, - }; - const errorMessages = {contains: 'boolean contains error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/contains', - keyword: 'contains', - params: { - minContains: 1, - }, - message: 'must contain at least 1 valid item(s)', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.contains, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const containsErrorMessage = 'boolean contains error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - contains: containsErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: containsErrorMessage, - }); - }); - }); - - describe('with schema contains array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - contains: {type: JsonSchemaType.Number}, - }; - const errorMessages = {contains: 'schema contains error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/contains', - keyword: 'contains', - params: { - minContains: 1, - }, - message: 'must contain at least 1 valid item(s)', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.contains, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const containsErrorMessage = 'schema contains error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - contains: containsErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: containsErrorMessage, - }); - }); - }); - - describe('with max items array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - maxItems: 0, - }; - const errorMessages = {maxItems: 'max items error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/maxItems', - keyword: 'maxItems', - params: { - limit: 0, - }, - message: 'must NOT have more than 0 items', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.maxItems, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const maxItemsErrorMessage = 'max items error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - maxItems: maxItemsErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: maxItemsErrorMessage, - }); - }); - }); - - describe('with min items array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - minItems: 1, - }; - const errorMessages = {minItems: 'min items error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/minItems', - keyword: 'minItems', - params: { - limit: 1, - }, - message: 'must NOT have fewer than 1 items', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.minItems, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const minItemsErrorMessage = 'min items error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - minItems: minItemsErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: minItemsErrorMessage, - }); - }); - }); - - describe('with unique items array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - uniqueItems: true, - }; - const errorMessages = {uniqueItems: 'unique items error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/uniqueItems', - keyword: 'uniqueItems', - params: { - i: 1, - j: 0, - }, - message: 'must NOT have duplicate items (items ## 0 and 1 are identical)', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['', '']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.uniqueItems, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const uniqueItemsErrorMessage = 'unique items error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - uniqueItems: uniqueItemsErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: uniqueItemsErrorMessage, - }); - }); - }); - - describe('with all of array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - allOf: [{type: JsonSchemaType.Array, const: ['value']}], - }; - const errorMessages = {const: 'const(by all of) error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/allOf/0/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const allOfConstErrorMessage = - 'const(by all of, by schema path) error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - allOf: [ - { - type: JsonSchemaType.Array, - const: ['value'], - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }, - ], - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const allOfConstErrorMessage = - 'const(by all of, by instance path) error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - }); - - describe('with any of array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - anyOf: [{type: JsonSchemaType.Array, const: ['value']}], - }; - const errorMessages = {anyOf: 'any of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/anyOf/0/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.anyOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const anyOfErrorMessage = 'any of error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - anyOf: anyOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: anyOfErrorMessage, - }); - }); - }); - - describe('with const array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - const: ['value'], - }; - const errorMessages = {const: 'const error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = []; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - const: constErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: constErrorMessage, - }); - }); - }); - - describe('with if then array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - if: {type: JsonSchemaType.Array, const: ['']}, - then: {type: JsonSchemaType.Array, const: ['value']}, - }; - const errorMessages = {then: 'if then error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'then', - }, - message: 'must match "then" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/then/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.then, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifThenErrorMessage = 'if then error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - then: ifThenErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifThenErrorMessage, - }); - }); - }); - - describe('with if else array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - if: {type: JsonSchemaType.Array, const: ['value']}, - else: {type: JsonSchemaType.Array, const: ['value']}, - }; - const errorMessages = {else: 'if else error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'else', - }, - message: 'must match "else" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/else/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.else, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifElseErrorMessage = 'if else error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - else: ifElseErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifElseErrorMessage, - }); - }); - }); - - describe('with enum array schema', () => { - const mainSchema: JsonSchemaArray = {type: JsonSchemaType.Array, enum: [['value']]}; - const errorMessages = {enum: 'enum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/enum', - keyword: 'enum', - params: { - allowedValues: [['value']], - }, - message: 'must be equal to one of the allowed values', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.enum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const enumErrorMessage = 'enum error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - enum: enumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: enumErrorMessage, - }); - }); - }); - - describe('with not array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - not: {type: JsonSchemaType.Array, const: ['value']}, - }; - const errorMessages = {not: 'not error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/not', - keyword: 'not', - params: {}, - message: 'must NOT be valid', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['value']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.not, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const notErrorMessage = 'not error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - not: notErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: notErrorMessage, - }); - }); - }); - - describe('with one of array schema', () => { - const mainSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - oneOf: [{type: JsonSchemaType.Array, const: ['value']}], - }; - const errorMessages = {oneOf: 'one of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/oneOf', - keyword: 'oneOf', - params: { - passingSchemas: null, - }, - message: 'must match exactly one schema in oneOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/oneOf/0/const', - keyword: 'const', - params: { - allowedValue: ['value'], - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ArrayValue = ['']; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.oneOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const oneOfErrorMessage = 'one of error from schema entity parameters'; - const schema: JsonSchemaArray = { - type: JsonSchemaType.Array, - entityParameters: { - errorMessages: { - oneOf: oneOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: oneOfErrorMessage, - }); - }); - }); - - describe('with type boolean schema', () => { - const mainSchema: JsonSchemaBoolean = {type: JsonSchemaType.Boolean}; - const errorMessages = {type: 'type error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/type', - keyword: 'type', - params: { - type: 'boolean', - }, - message: 'must be boolean', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'not boolean'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const typeErrorMessage = 'type error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - type: typeErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: typeErrorMessage, - }); - }); - }); - - describe('with all of boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - allOf: [{type: JsonSchemaType.Boolean, const: true}], - }; - const errorMessages = {const: 'const(by all of) error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/allOf/0/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const allOfConstErrorMessage = - 'const(by all of, by schema path) error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - allOf: [ - { - type: JsonSchemaType.Boolean, - const: true, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }, - ], - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const allOfConstErrorMessage = - 'const(by all of, by instance path) error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - }); - - describe('with any of boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - anyOf: [{type: JsonSchemaType.Boolean, const: true}], - }; - const errorMessages = {anyOf: 'any of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/anyOf/0/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.anyOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const anyOfErrorMessage = 'any of error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - anyOf: anyOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: anyOfErrorMessage, - }); - }); - }); - - describe('with const boolean schema', () => { - const mainSchema: JsonSchemaBoolean = {type: JsonSchemaType.Boolean, const: true}; - const errorMessages = {const: 'const error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - const: constErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: constErrorMessage, - }); - }); - }); - - describe('with if then boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - if: {type: JsonSchemaType.Boolean, const: false}, - then: {type: JsonSchemaType.Boolean, const: true}, - }; - const errorMessages = {then: 'if then error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'then', - }, - message: 'must match "then" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/then/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.then, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifThenErrorMessage = 'if then error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - then: ifThenErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifThenErrorMessage, - }); - }); - }); - - describe('with if else boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - if: {type: JsonSchemaType.Boolean, const: true}, - else: {type: JsonSchemaType.Boolean, const: true}, - }; - const errorMessages = {else: 'if else error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'else', - }, - message: 'must match "else" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/else/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.else, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifElseErrorMessage = 'if else error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - else: ifElseErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifElseErrorMessage, - }); - }); - }); - - describe('with enum boolean schema', () => { - const mainSchema: JsonSchemaBoolean = {type: JsonSchemaType.Boolean, enum: [true]}; - const errorMessages = {enum: 'enum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/enum', - keyword: 'enum', - params: { - allowedValues: [true], - }, - message: 'must be equal to one of the allowed values', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.enum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const enumErrorMessage = 'enum error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - enum: enumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: enumErrorMessage, - }); - }); - }); - - describe('with not boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - not: {type: JsonSchemaType.Boolean, const: false}, - }; - const errorMessages = {not: 'not error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/not', - keyword: 'not', - params: {}, - message: 'must NOT be valid', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.not, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const notErrorMessage = 'not error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - not: notErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: notErrorMessage, - }); - }); - }); - - describe('with one of boolean schema', () => { - const mainSchema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - oneOf: [{type: JsonSchemaType.Boolean, const: true}], - }; - const errorMessages = {oneOf: 'one of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/oneOf', - keyword: 'oneOf', - params: { - passingSchemas: null, - }, - message: 'must match exactly one schema in oneOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/oneOf/0/const', - keyword: 'const', - params: { - allowedValue: true, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = false; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.oneOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const oneOfErrorMessage = 'one of error from schema entity parameters'; - const schema: JsonSchemaBoolean = { - type: JsonSchemaType.Boolean, - entityParameters: { - errorMessages: { - oneOf: oneOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: oneOfErrorMessage, - }); - }); - }); - - describe('with type number schema', () => { - const mainSchema: JsonSchemaNumber = {type: JsonSchemaType.Number}; - const errorMessages = {type: 'type error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/type', - keyword: 'type', - params: { - type: 'number', - }, - message: 'must be number', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'not number'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const typeErrorMessage = 'type error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - type: typeErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: typeErrorMessage, - }); - }); - }); - - describe('with exclusive maximum number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - exclusiveMaximum: 1, - }; - const errorMessages = {exclusiveMaximum: 'exclusive maximum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/exclusiveMaximum', - keyword: 'exclusiveMaximum', - params: { - comparison: '<', - limit: 1, - }, - message: 'must be < 1', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 1; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.exclusiveMaximum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const exclusiveMaximumErrorMessage = - 'exclusive maximum error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - exclusiveMaximum: exclusiveMaximumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: exclusiveMaximumErrorMessage, - }); - }); - }); - - describe('with exclusive minimum number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - exclusiveMinimum: 1, - }; - const errorMessages = {exclusiveMinimum: 'exclusive minimum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/exclusiveMinimum', - keyword: 'exclusiveMinimum', - params: { - comparison: '>', - limit: 1, - }, - message: 'must be > 1', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 1; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.exclusiveMinimum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const exclusiveMinimumErrorMessage = - 'exclusive minimum error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - exclusiveMinimum: exclusiveMinimumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: exclusiveMinimumErrorMessage, - }); - }); - }); - - describe('with maximum number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - maximum: 1, - }; - const errorMessages = {maximum: 'maximum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/maximum', - keyword: 'maximum', - params: { - comparison: '<=', - limit: 1, - }, - message: 'must be <= 1', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 2; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.maximum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const maximumErrorMessage = 'maximum error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - maximum: maximumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: maximumErrorMessage, - }); - }); - }); - - describe('with minimum number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - minimum: 1, - }; - const errorMessages = {minimum: 'minimum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/minimum', - keyword: 'minimum', - params: { - comparison: '>=', - limit: 1, - }, - message: 'must be >= 1', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.minimum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const minimumErrorMessage = 'minimum error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - minimum: minimumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: minimumErrorMessage, - }); - }); - }); - - describe('with multiple of number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - multipleOf: 2, - }; - const errorMessages = {multipleOf: 'multiple of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/multipleOf', - keyword: 'multipleOf', - params: { - multipleOf: 2, - }, - message: 'must be multiple of 2', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 1; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.multipleOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const multipleOfErrorMessage = 'multiple of error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - multipleOf: multipleOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: multipleOfErrorMessage, - }); - }); - }); - - describe('with all of number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - allOf: [{type: JsonSchemaType.Number, const: 1}], - }; - const errorMessages = {const: 'const(by all of) error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/allOf/0/const', - keyword: 'const', - params: { - allowedValue: 1, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const allOfConstErrorMessage = - 'const(by all of, by schema path) error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - allOf: [ - { - type: JsonSchemaType.Number, - const: 1, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }, - ], - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const allOfConstErrorMessage = - 'const(by all of, by instance path) error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - }); - - describe('with any of number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - anyOf: [{type: JsonSchemaType.Number, const: 1}], - }; - const errorMessages = {anyOf: 'any of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/anyOf/0/const', - keyword: 'const', - params: { - allowedValue: 1, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.anyOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const anyOfErrorMessage = 'any of error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - anyOf: anyOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: anyOfErrorMessage, - }); - }); - }); - - describe('with const number schema', () => { - const mainSchema: JsonSchemaNumber = {type: JsonSchemaType.Number, const: 1}; - const errorMessages = {const: 'const error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/const', - keyword: 'const', - params: { - allowedValue: 1, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - const: constErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: constErrorMessage, - }); - }); - }); - - describe('with if then number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - if: {type: JsonSchemaType.Number, const: 1}, - then: {type: JsonSchemaType.Number, const: 2}, - }; - const errorMessages = {then: 'if then error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'then', - }, - message: 'must match "then" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/then/const', - keyword: 'const', - params: { - allowedValue: 2, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 1; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.then, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifThenErrorMessage = 'if then error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - then: ifThenErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifThenErrorMessage, - }); - }); - }); - - describe('with if else number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - if: {type: JsonSchemaType.Number, const: 1}, - else: {type: JsonSchemaType.Number, const: 2}, - }; - const errorMessages = {else: 'if else error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'else', - }, - message: 'must match "else" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/else/const', - keyword: 'const', - params: { - allowedValue: 2, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.else, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifElseErrorMessage = 'if else error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - else: ifElseErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifElseErrorMessage, - }); - }); - }); - - describe('with enum number schema', () => { - const mainSchema: JsonSchemaNumber = {type: JsonSchemaType.Number, enum: [1]}; - const errorMessages = {enum: 'enum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/enum', - keyword: 'enum', - params: { - allowedValues: [1], - }, - message: 'must be equal to one of the allowed values', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.enum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const enumErrorMessage = 'enum error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - enum: enumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: enumErrorMessage, - }); - }); - }); - - describe('with not number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - not: {type: JsonSchemaType.Number, const: 1}, - }; - const errorMessages = {not: 'not error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/not', - keyword: 'not', - params: {}, - message: 'must NOT be valid', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 1; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.not, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const notErrorMessage = 'not error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - not: notErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: notErrorMessage, - }); - }); - }); - - describe('with one of number schema', () => { - const mainSchema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - oneOf: [{type: JsonSchemaType.Number, const: 1}], - }; - const errorMessages = {oneOf: 'one of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/oneOf', - keyword: 'oneOf', - params: { - passingSchemas: null, - }, - message: 'must match exactly one schema in oneOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/oneOf/0/const', - keyword: 'const', - params: { - allowedValue: 1, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.oneOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const oneOfErrorMessage = 'one of error from schema entity parameters'; - const schema: JsonSchemaNumber = { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - oneOf: oneOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: oneOfErrorMessage, - }); - }); - }); - - describe('with type object schema', () => { - const mainSchema: JsonSchemaObject = {type: JsonSchemaType.Object}; - const errorMessages = {type: 'type error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/type', - keyword: 'type', - params: { - type: 'object', - }, - message: 'must be object', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'not object'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const typeErrorMessage = 'type error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - type: typeErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: typeErrorMessage, - }); - }); - }); - - describe('with boolean additional properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - additionalProperties: false, - }; - const errorMessages = { - additionalProperties: 'boolean additional properties error from error messages', - }; - const error = { - instancePath: '', - schemaPath: '#/additionalProperties', - keyword: 'additionalProperties', - params: { - additionalProperty: 'additional', - }, - message: 'must NOT have additional properties', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {additional: 'value'}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.additionalProperties, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const additionalPropertiesErrorMessage = - 'boolean additional properties error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - additionalProperties: additionalPropertiesErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: additionalPropertiesErrorMessage, - }); - }); - }); - - describe('with schema additional properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - additionalProperties: {type: JsonSchemaType.Number}, - }; - const errorMessages = { - type: 'type(by schema additional properties) error from error messages', - }; - const error = { - instancePath: '/additional', - schemaPath: '#/additionalProperties/type', - keyword: 'type', - params: { - type: 'number', - }, - message: 'must be number', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {additional: 'value'}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['additional'], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['additional'], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const additionalPropertiesTypeErrorMessage = - 'type(by schema additional properties) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - additionalProperties: { - type: JsonSchemaType.Number, - entityParameters: { - errorMessages: { - type: additionalPropertiesTypeErrorMessage, - }, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['additional'], - error: additionalPropertiesTypeErrorMessage, - }); - }); - }); - - describe('with string dependencies properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - first: {type: JsonSchemaType.String}, - second: {type: JsonSchemaType.String}, - }, - dependencies: { - second: ['first'], - }, - }; - const errorMessages = { - dependencies: 'string dependencies error from error messages', - }; - const error = { - instancePath: '', - schemaPath: '#/dependencies', - keyword: 'dependencies', - params: { - property: 'second', - missingProperty: 'first', - depsCount: 1, - deps: 'first', - }, - message: 'must have property first when property second is present', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {second: ''}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: errorMessages.dependencies, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const dependenciesErrorMessage = - 'string dependencies error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - dependencies: dependenciesErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: dependenciesErrorMessage, - }); - }); - }); - - describe('with schema dependencies properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - first: {type: JsonSchemaType.String}, - second: {type: JsonSchemaType.String}, - }, - dependencies: { - second: { - type: JsonSchemaType.Object, - properties: { - first: { - type: JsonSchemaType.String, - const: 'value', - }, - }, - }, - }, - }; - const errorMessages = { - const: 'const(by schema dependencies) error from error messages', - }; - const error = { - instancePath: '/first', - schemaPath: '#/dependencies/second/properties/first/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {first: '', second: ''}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - first: { - type: JsonSchemaType.String, - entityParameters: {errorMessages: {const: constErrorMessage}}, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: constErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const dependenciesConstErrorMessage = - 'const(by schema dependencies) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - dependencies: { - second: { - type: JsonSchemaType.Object, - properties: { - first: { - type: JsonSchemaType.String, - const: 'value', - entityParameters: { - errorMessages: { - const: dependenciesConstErrorMessage, - }, - }, - }, - }, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: dependenciesConstErrorMessage, - }); - }); - }); - - describe('with max properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - maxProperties: 0, - }; - const errorMessages = {maxProperties: 'max properties error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/maxProperties', - keyword: 'maxProperties', - params: { - limit: 0, - }, - message: 'must NOT have more than 0 properties', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {first: ''}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.maxProperties, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const maxPropertiesErrorMessage = 'max properties error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - maxProperties: maxPropertiesErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: maxPropertiesErrorMessage, - }); - }); - }); - - describe('with min properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - minProperties: 1, - }; - const errorMessages = {minProperties: 'min properties error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/minProperties', - keyword: 'minProperties', - params: { - limit: 1, - }, - message: 'must NOT have fewer than 1 properties', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.minProperties, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const minPropertiesErrorMessage = 'min properties error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - minProperties: minPropertiesErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: minPropertiesErrorMessage, - }); - }); - }); - - describe('with pattern properties object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - patternProperties: { - '^first': { - type: JsonSchemaType.String, - const: 'value', - }, - }, - }; - const errorMessages = {const: 'const(by pattern properties) error from error messages'}; - const error = { - instancePath: '/first', - schemaPath: '#/patternProperties/%5Efirst/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {first: ''}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const patternPropertiesConstErrorMessage = - 'const(by pattern properties) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - patternProperties: { - '^first': { - type: JsonSchemaType.String, - const: 'value', - entityParameters: { - errorMessages: { - const: patternPropertiesConstErrorMessage, - }, - }, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: patternPropertiesConstErrorMessage, - }); - }); - }); - - describe('with property names object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - propertyNames: { - type: JsonSchemaType.String, - maxLength: 1, - }, - }; - const errorMessages = {propertyNames: 'property names error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/propertyNames', - keyword: 'propertyNames', - params: { - propertyName: 'first', - }, - message: 'property name must be valid', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/propertyNames/maxLength', - keyword: 'maxLength', - params: { - limit: 1, - }, - message: 'must NOT have more than 1 characters', - propertyName: 'first', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {first: ''}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.propertyNames, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const propertyNamesErrorMessage = 'property names error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - propertyNames: propertyNamesErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: propertyNamesErrorMessage, - }); - }); - }); - - describe('with required object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - required: ['first'], - }; - const errorMessages = {required: 'required error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/required', - keyword: 'required', - params: { - missingProperty: 'first', - }, - message: "must have required property 'first'", - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: errorMessages.required, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const requiredErrorMessage = - 'required(by schema path) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - required: requiredErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: requiredErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const requiredErrorMessage = - 'required(by instance path) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - first: { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - required: requiredErrorMessage, - }, - }, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: ['first'], - error: requiredErrorMessage, - }); - }); - }); - - describe('with all of object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - allOf: [{type: JsonSchemaType.Object, const: {first: 'value'}}], - }; - const errorMessages = {const: 'const(by all of) error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/allOf/0/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const allOfConstErrorMessage = - 'const(by all of, by schema path) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - allOf: [ - { - type: JsonSchemaType.Object, - const: {first: 'value'}, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }, - ], - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const allOfConstErrorMessage = - 'const(by all of, by instance path) error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - }); - - describe('with any of object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - anyOf: [{type: JsonSchemaType.Object, const: {first: 'value'}}], - }; - const errorMessages = {anyOf: 'any of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/anyOf/0/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.anyOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const anyOfErrorMessage = 'any of error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - anyOf: anyOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: anyOfErrorMessage, - }); - }); - }); - - describe('with const object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - const: {first: 'value'}, - }; - const errorMessages = {const: 'const error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - const: constErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: constErrorMessage, - }); - }); - }); - - describe('with if then object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - if: {type: JsonSchemaType.Object, const: {}}, - then: {type: JsonSchemaType.Object, const: {first: 'value'}}, - }; - const errorMessages = {then: 'if then error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'then', - }, - message: 'must match "then" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/then/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.then, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifThenErrorMessage = 'if then error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - then: ifThenErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifThenErrorMessage, - }); - }); - }); - - describe('with if else object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - if: {type: JsonSchemaType.Object, const: {first: 'value'}}, - else: {type: JsonSchemaType.Object, const: {first: 'value'}}, - }; - const errorMessages = {else: 'if else error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'else', - }, - message: 'must match "else" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/else/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.else, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifElseErrorMessage = 'if else error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - else: ifElseErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifElseErrorMessage, - }); - }); - }); - - describe('with enum object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - enum: [{first: 'value'}], - }; - const errorMessages = {enum: 'enum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/enum', - keyword: 'enum', - params: { - allowedValues: [{first: 'value'}], - }, - message: 'must be equal to one of the allowed values', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.enum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const enumErrorMessage = 'enum error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - enum: enumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: enumErrorMessage, - }); - }); - }); - - describe('with not object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - not: {type: JsonSchemaType.Object, const: {}}, - }; - const errorMessages = {not: 'not error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/not', - keyword: 'not', - params: {}, - message: 'must NOT be valid', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.not, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const notErrorMessage = 'not error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - not: notErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: notErrorMessage, - }); - }); - }); - - describe('with one of object schema', () => { - const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - oneOf: [{type: JsonSchemaType.Object, const: {first: 'value'}}], - }; - const errorMessages = {oneOf: 'one of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/oneOf', - keyword: 'oneOf', - params: { - passingSchemas: null, - }, - message: 'must match exactly one schema in oneOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/oneOf/0/const', - keyword: 'const', - params: { - allowedValue: { - first: 'value', - }, - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value: ObjectValue = {}; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.oneOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const oneOfErrorMessage = 'one of error from schema entity parameters'; - const schema: JsonSchemaObject = { - type: JsonSchemaType.Object, - entityParameters: { - errorMessages: { - oneOf: oneOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: oneOfErrorMessage, - }); - }); - }); - - describe('with type string schema', () => { - const mainSchema: JsonSchemaString = {type: JsonSchemaType.String}; - const errorMessages = {type: 'type error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/type', - keyword: 'type', - params: { - type: 'string', - }, - message: 'must be string', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 0; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.type, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const typeErrorMessage = 'type error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - type: typeErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: typeErrorMessage, - }); - }); - }); - - describe('with max length string schema', () => { - const mainSchema: JsonSchemaString = {type: JsonSchemaType.String, maxLength: 1}; - const errorMessages = {maxLength: 'max length error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/maxLength', - keyword: 'maxLength', - params: { - limit: 1, - }, - message: 'must NOT have more than 1 characters', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'value'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.maxLength, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const maxLengthErrorMessage = 'max length error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - maxLength: maxLengthErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: maxLengthErrorMessage, - }); - }); - }); - - describe('with min length string schema', () => { - const mainSchema: JsonSchemaString = {type: JsonSchemaType.String, minLength: 1}; - const errorMessages = {minLength: 'min length error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/minLength', - keyword: 'minLength', - params: { - limit: 1, - }, - message: 'must NOT have fewer than 1 characters', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.minLength, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const minLengthErrorMessage = 'min length error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - minLength: minLengthErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: minLengthErrorMessage, - }); - }); - }); - - describe('with pattern string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - pattern: '^[0-9]', - }; - const errorMessages = {pattern: 'pattern error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/pattern', - keyword: 'pattern', - params: { - pattern: '^[0-9]', - }, - message: 'must match pattern "^[0-9]"', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'value'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.pattern, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const patternErrorMessage = 'pattern error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - pattern: patternErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: patternErrorMessage, - }); - }); - }); - - describe('with all of string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - allOf: [{type: JsonSchemaType.String, const: 'value'}], - }; - const errorMessages = {const: 'const(by all of) error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/allOf/0/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters by schema path', () => { - const allOfConstErrorMessage = - 'const(by all of, by schema path) error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - allOf: [ - { - type: JsonSchemaType.String, - const: 'value', - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }, - ], - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - - it('should call onError with message from schema entity parameters by instance path', () => { - const allOfConstErrorMessage = - 'const(by all of, by instance path) error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - const: allOfConstErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: allOfConstErrorMessage, - }); - }); - }); - - describe('with any of string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - anyOf: [{type: JsonSchemaType.String, const: 'value'}], - }; - const errorMessages = {anyOf: 'any of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/anyOf', - keyword: 'anyOf', - params: {}, - message: 'must match a schema in anyOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/anyOf/0/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.anyOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const anyOfErrorMessage = 'any of error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - anyOf: anyOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: anyOfErrorMessage, - }); - }); - }); - - describe('with const string schema', () => { - const mainSchema: JsonSchemaString = {type: JsonSchemaType.String, const: 'value'}; - const errorMessages = {const: 'const error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.const, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const constErrorMessage = 'const error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - const: constErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: constErrorMessage, - }); - }); - }); - - describe('with if then string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - if: {type: JsonSchemaType.String, const: ''}, - then: {type: JsonSchemaType.String, const: 'value'}, - }; - const errorMessages = {then: 'if then error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'then', - }, - message: 'must match "then" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/then/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.then, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifThenErrorMessage = 'if then error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - then: ifThenErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifThenErrorMessage, - }); - }); - }); - - describe('with if else string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - if: {type: JsonSchemaType.String, const: 'value'}, - else: {type: JsonSchemaType.String, const: 'value'}, - }; - const errorMessages = {else: 'if else error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/if', - keyword: 'if', - params: { - failingKeyword: 'else', - }, - message: 'must match "else" schema', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/else/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.else, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const ifElseErrorMessage = 'if else error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - else: ifElseErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: ifElseErrorMessage, - }); - }); - }); - - describe('with enum string schema', () => { - const mainSchema: JsonSchemaString = {type: JsonSchemaType.String, enum: ['value']}; - const errorMessages = {enum: 'enum error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/enum', - keyword: 'enum', - params: { - allowedValues: ['value'], - }, - message: 'must be equal to one of the allowed values', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.enum, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const enumErrorMessage = 'enum error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - enum: enumErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: enumErrorMessage, - }); - }); - }); - - describe('with not string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - not: {type: JsonSchemaType.String, const: 'value'}, - }; - const errorMessages = {not: 'not error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/not', - keyword: 'not', - params: {}, - message: 'must NOT be valid', - }; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = 'value'; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual([error]); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.not, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const notErrorMessage = 'not error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - not: notErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: notErrorMessage, - }); - }); - }); - - describe('with one of string schema', () => { - const mainSchema: JsonSchemaString = { - type: JsonSchemaType.String, - oneOf: [{type: JsonSchemaType.String, const: 'value'}], - }; - const errorMessages = {oneOf: 'one of error from error messages'}; - const error = { - instancePath: '', - schemaPath: '#/oneOf', - keyword: 'oneOf', - params: { - passingSchemas: null, - }, - message: 'must match exactly one schema in oneOf', - }; - const errors = [ - { - instancePath: '', - schemaPath: '#/oneOf/0/const', - keyword: 'const', - params: { - allowedValue: 'value', - }, - message: 'must be equal to constant', - }, - error, - ]; - const headName = ''; - const onError = jest.fn(); - - test('errors must match', () => { - const value = ''; - const validate = ajv.compile(mainSchema); - - validate(value); - - expect(validate.errors).toEqual(errors); - }); - - it('should call onError with default ajv error message', () => { - processAjvError({ - error, - errorMessages: undefined, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: error.message, - }); - }); - - it('should call onError with message from error messages map', () => { - processAjvError({ - error, - errorMessages, - headName, - mainSchema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: errorMessages.oneOf, - }); - }); - - it('should call onError with message from schema entity parameters', () => { - const oneOfErrorMessage = 'one of error from schema entity parameters'; - const schema: JsonSchemaString = { - type: JsonSchemaType.String, - entityParameters: { - errorMessages: { - oneOf: oneOfErrorMessage, - }, - }, - }; - - processAjvError({ - error, - errorMessages, - headName, - mainSchema: schema, - onError, - }); - - expect(onError).toHaveBeenCalledWith({ - path: [], - error: oneOfErrorMessage, - }); - }); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-entity-parameters-error.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-entity-parameters-error.test.ts deleted file mode 100644 index 6a1e04fd..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-entity-parameters-error.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import {JsonSchemaType} from '../../../constants'; -import type {ValidationState} from '../../../mutators'; -import type {JsonSchemaString, ObjectValue} from '../../../types'; -import type {EntityParametersError} from '../../types'; -import {processEntityParametersError} from '../process-entity-parameters-error'; - -describe('SchemaRendererServiceField/utils/process-entity-parameters-error', () => { - const fieldName = 'field'; - const fieldInstancePath = `/${fieldName}`; - const fieldValue = 'field value'; - const fieldValidateResult = 'field validate result'; - const fieldSchema: JsonSchemaString = {type: JsonSchemaType.String}; - const fieldValidator = jest.fn(); - const fieldEntityParametersError: EntityParametersError = { - instancePath: fieldInstancePath, - keyword: 'entityParameters', - schemaPath: '#/entityParameters', - params: { - schema: fieldSchema, - validator: fieldValidator, - value: fieldValue, - }, - }; - - const headName = 'headName'; - const allValues: ObjectValue = {[headName]: {[fieldName]: fieldValue}}; - const onError = jest.fn(); - const onAsyncError = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('with cache hit', () => { - const validationState: ValidationState = { - cache: { - [fieldInstancePath]: [ - { - schema: fieldSchema, - validator: fieldValidator, - value: fieldValue, - result: fieldValidateResult, - }, - ], - }, - }; - - it('should call onError with cached result', () => { - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(onError).toHaveBeenCalledWith({ - error: fieldValidateResult, - path: [headName, fieldName], - }); - expect(fieldValidator).not.toHaveBeenCalled(); - expect(onAsyncError).not.toHaveBeenCalled(); - }); - }); - - describe('with no cache and no waiter', () => { - const validationState: ValidationState = {}; - - it('should call validator and onError with synchronous result', () => { - fieldValidator.mockReturnValue(fieldValidateResult); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onError).toHaveBeenCalledWith({ - error: fieldValidateResult, - path: [headName, fieldName], - }); - expect(onAsyncError).not.toHaveBeenCalled(); - }); - - it('should call validator and onAsyncError with asynchronous result', () => { - const promise = Promise.resolve(fieldValidateResult); - - fieldValidator.mockReturnValue(promise); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onAsyncError).toHaveBeenCalledWith({ - instancePath: fieldInstancePath, - params: fieldEntityParametersError.params, - promise, - }); - expect(onError).not.toHaveBeenCalled(); - }); - }); - - describe('with waiter that does not match error params', () => { - const validationState: ValidationState = { - waiters: { - [fieldInstancePath]: { - schema: fieldSchema, - validator: jest.fn(), - value: 'different value', - }, - }, - }; - - it('should call validator and onError with synchronous result', () => { - fieldValidator.mockReturnValue(fieldValidateResult); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onError).toHaveBeenCalledWith({ - error: fieldValidateResult, - path: [headName, fieldName], - }); - expect(onAsyncError).not.toHaveBeenCalled(); - }); - - it('should call validator and onAsyncError with asynchronous result', () => { - const promise = Promise.resolve(fieldValidateResult); - - fieldValidator.mockReturnValue(promise); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onAsyncError).toHaveBeenCalledWith({ - instancePath: fieldInstancePath, - params: fieldEntityParametersError.params, - promise, - }); - expect(onError).not.toHaveBeenCalled(); - }); - }); - - describe('with matching waiter', () => { - const validationState: ValidationState = { - waiters: { - [fieldInstancePath]: { - schema: fieldSchema, - validator: fieldValidator, - value: fieldValue, - }, - }, - }; - - it('should not call validator, onError, or onAsyncError', () => { - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState, - }); - - expect(fieldValidator).not.toHaveBeenCalled(); - expect(onError).not.toHaveBeenCalled(); - expect(onAsyncError).not.toHaveBeenCalled(); - }); - }); - - describe('with undefined validationState', () => { - it('should call validator and onError with synchronous result', () => { - fieldValidator.mockReturnValue(fieldValidateResult); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState: undefined, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onError).toHaveBeenCalledWith({ - error: fieldValidateResult, - path: [headName, fieldName], - }); - expect(onAsyncError).not.toHaveBeenCalled(); - }); - - it('should call validator and onAsyncError with asynchronous result', () => { - const promise = Promise.resolve(fieldValidateResult); - - fieldValidator.mockReturnValue(promise); - - processEntityParametersError({ - allValues, - error: fieldEntityParametersError, - headName, - onAsyncError, - onError, - validationState: undefined, - }); - - expect(fieldValidator).toHaveBeenCalledWith(fieldValue, allValues); - expect(onAsyncError).toHaveBeenCalledWith({ - instancePath: fieldInstancePath, - params: fieldEntityParametersError.params, - promise, - }); - expect(onError).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-error-items.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-error-items.test.ts deleted file mode 100644 index ebcfd91e..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-error-items.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -import {ARRAY_AND_OBJECT_ERRORS, JsonSchemaType} from '../../../constants'; -import type {ValidateErrorItem} from '../../types'; -import {processErrorItems} from '../process-error-items'; - -describe('processErrorItems', () => { - const headName = 'headName'; - const fieldName1 = 'fieldName1'; - const fieldName2 = 'fieldName2'; - const fieldName3 = 'fieldName3'; - const fieldName4 = 'fieldName4'; - - it('should return an object with ARRAY_AND_OBJECT_ERRORS property', () => { - const result = processErrorItems({ - errorItems: [], - headName, - mainSchema: {type: JsonSchemaType.Object}, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}}); - }); - - it('should skip error items with falsy error property', () => { - const errorItems: ValidateErrorItem[] = [ - {error: undefined, path: [headName, fieldName1]}, - {error: false, path: [headName, fieldName2]}, - ]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.String}, - [fieldName2]: {type: JsonSchemaType.String}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}}); - }); - - it('should handle boolean error values for non-array/object schemas', () => { - const error = true; - const errorItems: ValidateErrorItem[] = [{error, path: [headName, fieldName1]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.String}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}, [headName]: {[fieldName1]: error}}); - }); - - it('should handle string error values for non-array/object schemas', () => { - const error = 'error message'; - const errorItems: ValidateErrorItem[] = [{error, path: [headName, fieldName1]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.String}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}, [headName]: {[fieldName1]: error}}); - }); - - it('should handle undefined error values for non-array/object schemas', () => { - const errorItems: ValidateErrorItem[] = [{error: undefined, path: [headName, fieldName1]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.String}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}}); - }); - - it('should handle array schema types by adding to ARRAY_AND_OBJECT_ERRORS', () => { - const error = 'array error'; - const errorItems: ValidateErrorItem[] = [{error, path: [headName, fieldName1]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.Array}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {[`${headName}.${fieldName1}`]: error}}); - }); - - it('should handle object schema types by adding to ARRAY_AND_OBJECT_ERRORS', () => { - const error = 'object error'; - const errorItems: ValidateErrorItem[] = [{error, path: [headName, fieldName1]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.Object}, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {[`${headName}.${fieldName1}`]: error}}); - }); - - it('should handle object-like error values by processing nested paths', () => { - const errorObject = {[fieldName1]: {[fieldName2]: 'nested error'}}; - const errorItems: ValidateErrorItem[] = [{error: errorObject, path: [headName]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: { - type: JsonSchemaType.Object, - properties: { - [fieldName2]: {type: JsonSchemaType.String}, - }, - }, - }, - }, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}, [headName]: errorObject}); - }); - - it('should handle object-like error values with array/object nested schemas', () => { - const error2 = 'array error'; - const error3 = 'object error'; - const errorObject = { - [fieldName1]: { - [fieldName2]: error2, - [fieldName3]: error3, - }, - }; - - const errorItems: ValidateErrorItem[] = [{error: errorObject, path: [headName]}]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: { - type: JsonSchemaType.Object, - properties: { - [fieldName2]: {type: JsonSchemaType.Array}, - [fieldName3]: {type: JsonSchemaType.Object}, - }, - }, - }, - }, - }); - - expect(result).toEqual({ - [ARRAY_AND_OBJECT_ERRORS]: { - [`${headName}.${fieldName1}.${fieldName2}`]: error2, - [`${headName}.${fieldName1}.${fieldName3}`]: error3, - }, - }); - }); - - it('should handle multiple error items', () => { - const error1 = 'string error'; - const error4 = 'nested error'; - const errorItems: ValidateErrorItem[] = [ - {error: error1, path: [headName, fieldName1]}, - {error: true, path: [headName, fieldName2]}, - {error: {[fieldName4]: error4}, path: [headName, fieldName3]}, - ]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: { - type: JsonSchemaType.Object, - properties: { - [fieldName1]: {type: JsonSchemaType.String}, - [fieldName2]: {type: JsonSchemaType.Boolean}, - [fieldName3]: { - type: JsonSchemaType.Object, - properties: {[fieldName4]: {type: JsonSchemaType.Array}}, - }, - }, - }, - }); - - expect(result).toEqual({ - [ARRAY_AND_OBJECT_ERRORS]: { - [`${headName}.${fieldName3}.${fieldName4}`]: error4, - }, - [headName]: {[fieldName1]: error1, [fieldName2]: true}, - }); - }); - - it('should do nothing if schema is not found', () => { - const errorItems: ValidateErrorItem[] = [ - { - error: 'error message', - path: [headName, 'nonExistentField'], - }, - ]; - - const result = processErrorItems({ - errorItems, - headName, - mainSchema: {type: JsonSchemaType.Object}, - }); - - expect(result).toEqual({[ARRAY_AND_OBJECT_ERRORS]: {}}); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-errors-state.test.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-errors-state.test.ts deleted file mode 100644 index 82b8fef3..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/__tests__/process-errors-state.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import {processErrorsState} from '../process-errors-state'; - -describe('processErrorsState', () => { - it('should return empty arrays when errorsState is undefined', () => { - const result = processErrorsState({ - errorsState: undefined, - }); - - expect(result.externalPriorityErrorItems).toEqual([]); - expect(result.externalRegularErrorItems).toEqual([]); - }); - - it('should process priorityErrors correctly', () => { - const result = processErrorsState({ - errorsState: { - priorityErrors: { - field1: 'Error 1', - field2: true, - }, - regularErrors: {}, - }, - }); - - expect(result.externalPriorityErrorItems).toEqual([ - {path: ['field1'], error: 'Error 1'}, - {path: ['field2'], error: true}, - ]); - expect(result.externalRegularErrorItems).toEqual([]); - }); - - it('should process regularErrors correctly', () => { - const result = processErrorsState({ - errorsState: { - priorityErrors: {}, - regularErrors: { - field3: 'Error 3', - field4: { - nested: 'Nested error', - }, - }, - }, - }); - - expect(result.externalPriorityErrorItems).toEqual([]); - expect(result.externalRegularErrorItems).toEqual([ - {path: ['field3'], error: 'Error 3'}, - {path: ['field4'], error: {nested: 'Nested error'}}, - ]); - }); - - it('should process both priorityErrors and regularErrors correctly', () => { - const result = processErrorsState({ - errorsState: { - priorityErrors: { - field1: 'Error 1', - field2: true, - }, - regularErrors: { - field3: 'Error 3', - field4: { - nested: 'Nested error', - }, - }, - }, - }); - - expect(result.externalPriorityErrorItems).toEqual([ - {path: ['field1'], error: 'Error 1'}, - {path: ['field2'], error: true}, - ]); - expect(result.externalRegularErrorItems).toEqual([ - {path: ['field3'], error: 'Error 3'}, - {path: ['field4'], error: {nested: 'Nested error'}}, - ]); - }); - - it('should handle complex paths correctly', () => { - const result = processErrorsState({ - errorsState: { - priorityErrors: { - 'parent.child': 'Nested error', - }, - regularErrors: { - 'array[0]': 'Array error', - }, - }, - }); - - expect(result.externalPriorityErrorItems).toEqual([ - {path: ['parent', 'child'], error: 'Nested error'}, - ]); - expect(result.externalRegularErrorItems).toEqual([ - {path: ['array', '0'], error: 'Array error'}, - ]); - }); - - it('should handle undefined priorityErrors and regularErrors', () => { - const result = processErrorsState({ - errorsState: {}, - }); - - expect(result.externalPriorityErrorItems).toEqual([]); - expect(result.externalRegularErrorItems).toEqual([]); - }); - - it('should handle various error types correctly', () => { - const result = processErrorsState({ - errorsState: { - priorityErrors: { - string: 'String error', - boolean: true, - object: {key: 'value'}, - array: ['item1', 'item2'], - undefined: undefined, - }, - regularErrors: {}, - }, - }); - - expect(result.externalPriorityErrorItems).toEqual([ - {path: ['string'], error: 'String error'}, - {path: ['boolean'], error: true}, - {path: ['object'], error: {key: 'value'}}, - {path: ['array'], error: ['item1', 'item2']}, - {path: ['undefined'], error: undefined}, - ]); - }); -}); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/get-validate.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/get-validate.ts deleted file mode 100644 index 2de44868..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/get-validate.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type {FieldValidator} from 'final-form'; -import get from 'lodash/get'; - -import type { - ErrorsState, - SetValidationCacheMutator, - SetValidationWaitersMutator, - ValidationState, -} from '../../mutators'; -import type { - ErrorMessages, - JsonSchema, - ObjectValue, - SchemaRendererConfig, - SchemaToValueType, -} from '../../types'; - -import {getAjvValidate} from './get-ajv-validate'; -import {processAjvValidateErrors} from './process-ajv-validate-errors'; -import {processErrorItems} from './process-error-items'; -import {processErrorsState} from './process-errors-state'; - -type GetValidateParams = { - config: SchemaRendererConfig; - errorMessages?: ErrorMessages; - headName: string; - mainSchema: Schema; - serviceFieldName: string; - setValidationCache: SetValidationCacheMutator; - setValidationWaiters: SetValidationWaitersMutator; -}; - -type GetValidateReturn = FieldValidator>; - -export const getValidate = ({ - config, - errorMessages, - headName, - mainSchema, - serviceFieldName, - setValidationCache, - setValidationWaiters, -}: GetValidateParams): GetValidateReturn => { - const ajvValidate = getAjvValidate({config, mainSchema}); - - return (_value, allValues, meta) => { - ajvValidate(get(allValues, headName)); - - const {ajvErrorItems, entityParametersErrorItems, waiters} = processAjvValidateErrors({ - ajvValidateErrors: ajvValidate.errors || [], - allValues: allValues as ObjectValue, - errorMessages, - headName, - mainSchema, - serviceFieldName, - setValidationCache, - validationState: meta?.data as ValidationState | undefined, - }); - const {externalPriorityErrorItems, externalRegularErrorItems} = processErrorsState({ - errorsState: meta?.data as ErrorsState | undefined, - }); - - if (Object.keys(waiters).length) { - setValidationWaiters({serviceFieldName, waiters}); - } - - const result = processErrorItems({ - errorItems: [ - ...externalRegularErrorItems, - ...ajvErrorItems, - ...entityParametersErrorItems, - ...externalPriorityErrorItems, - ], - headName, - mainSchema, - }); - - return result; - }; -}; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/index.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/index.ts deleted file mode 100644 index 13283897..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-validate'; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-error-items.ts b/src/lib/unstable/core/SchemaRendererServiceField/utils/process-error-items.ts deleted file mode 100644 index 1f303a49..00000000 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-error-items.ts +++ /dev/null @@ -1,71 +0,0 @@ -import get from 'lodash/get'; -import isBoolean from 'lodash/isBoolean'; -import isObjectLike from 'lodash/isObjectLike'; -import isString from 'lodash/isString'; -import isUndefined from 'lodash/isUndefined'; -import set from 'lodash/set'; - -import {ARRAY_AND_OBJECT_ERRORS, JsonSchemaType} from '../../constants'; -import type {JsonSchema, SyncValidateError} from '../../types'; -import {getSchemaByFinalFormPath} from '../../utils'; -import type {ValidateErrorItem} from '../types'; - -import {getValuePaths} from './common'; - -interface ProcessErrorItemsParams { - errorItems: ValidateErrorItem[]; - headName: string; - mainSchema: Schema; -} - -type ProcessErrorItemsReturn = { - [ARRAY_AND_OBJECT_ERRORS]: {[key: string]: boolean | string | undefined}; -} & {[key: string]: SyncValidateError}; - -export const processErrorItems = ({ - errorItems, - headName, - mainSchema, -}: ProcessErrorItemsParams): ProcessErrorItemsReturn => { - const result: ProcessErrorItemsReturn = { - [ARRAY_AND_OBJECT_ERRORS]: {}, - }; - - const setError = (path: string[], error: boolean | string | undefined) => { - const itemSchema = getSchemaByFinalFormPath(path, headName, mainSchema); - - if (itemSchema) { - const arrayOrObjectSchema = - itemSchema.type === JsonSchemaType.Array || - itemSchema.type === JsonSchemaType.Object; - - if (arrayOrObjectSchema) { - result[ARRAY_AND_OBJECT_ERRORS][path.join('.')] = error; - } else { - set(result, path, error); - } - } - }; - - errorItems.forEach((item) => { - if (!item.error) { - return; - } - - if (isObjectLike(item.error)) { - getValuePaths(item.error).forEach((path) => { - setError([...item.path, ...path], get(item.error, path)); - }); - - return; - } - - if (isBoolean(item.error) || isString(item.error) || isUndefined(item.error)) { - setError(item.path, item.error); - - return; - } - }); - - return result; -}; diff --git a/src/lib/unstable/core/constants.ts b/src/lib/unstable/core/constants.ts index 7d3da4bc..b8016570 100644 --- a/src/lib/unstable/core/constants.ts +++ b/src/lib/unstable/core/constants.ts @@ -4,6 +4,7 @@ export enum SchemaRendererMode { } export enum JsonSchemaType { + Any = 'any', Array = 'array', Boolean = 'boolean', Number = 'number', @@ -12,5 +13,3 @@ export enum JsonSchemaType { } export const EMPTY_OBJECT = {}; - -export const ARRAY_AND_OBJECT_ERRORS = 'ARRAY_AND_OBJECT_ERRORS'; diff --git a/src/lib/unstable/core/index.ts b/src/lib/unstable/core/index.ts index cd4dc0b5..7a5f0ffd 100644 --- a/src/lib/unstable/core/index.ts +++ b/src/lib/unstable/core/index.ts @@ -1,7 +1,11 @@ -export * from './Entity'; -export * from './SchemaRenderer'; +export {Entity, type EntityProps} from './Entity'; +export {SchemaRenderer, type SchemaRendererProps} from './SchemaRenderer'; export {JsonSchemaType, SchemaRendererMode} from './constants'; -export {schemaRendererMutators} from './mutators'; -export * from './types'; -export * from './useSchemaRendererField'; -export * from './useSetErrors'; +export type * from './types'; +export { + schemaRendererMutators, + type UseSchemaRendererParams, + type UseSchemaRendererReturn, + useSchemaRenderer, + useSchemaRendererMutators, +} from './useSchemaRenderer'; diff --git a/src/lib/unstable/core/mutators/async-validation/__tests__/async-validation.test.ts b/src/lib/unstable/core/mutators/async-validation/__tests__/async-validation.test.ts deleted file mode 100644 index dac80398..00000000 --- a/src/lib/unstable/core/mutators/async-validation/__tests__/async-validation.test.ts +++ /dev/null @@ -1,337 +0,0 @@ -import type {InternalFieldState, InternalFormState, MutableState, Tools} from 'final-form'; - -import {JsonSchemaType} from '../../../constants'; -import {setValidationCache, setValidationWaiters} from '../async-validation'; -import type {ValidationCache, ValidationWaiter} from '../types'; - -describe('async-validation', () => { - describe('setValidationWaiters', () => { - const fieldName = 'fieldName'; - const serviceFieldName = 'serviceFieldName'; - const tools = {} as Tools<{}, {}>; - - let serviceField: InternalFieldState; - let field: InternalFieldState; - let mutableState: MutableState<{}, {}>; - let waiter: ValidationWaiter; - - beforeEach(() => { - serviceField = {data: {}} as InternalFieldState; - field = {} as InternalFieldState; - mutableState = { - fields: {}, - formState: {} as InternalFormState, - fieldSubscribers: {}, - }; - waiter = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'value', - }; - }); - - it('should not modify state if service field does not exist', () => { - setValidationWaiters( - [{serviceFieldName, waiters: {[fieldName]: waiter}}], - mutableState, - tools, - ); - - expect(mutableState.fields).toEqual({}); - }); - - it('should not modify state if waiters are not provided', () => { - mutableState.fields = {[serviceFieldName]: serviceField}; - - setValidationWaiters( - [{serviceFieldName, waiters: undefined as any}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data).toEqual({}); - }); - - it('should add waiter to the validation state', () => { - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: field, - }; - - setValidationWaiters( - [{serviceFieldName, waiters: {[fieldName]: waiter}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({ - [fieldName]: waiter, - }); - expect(mutableState.fields[fieldName].validating).toBe(true); - }); - - it('should add waiters to the validation state', () => { - const anotherFieldName = 'anotherFieldName'; - const anotherField = {} as InternalFieldState; - const anotherWaiter = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'anotherValue', - }; - - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: {...field, validating: false}, - [anotherFieldName]: {...anotherField, validating: false}, - }; - - setValidationWaiters( - [ - { - serviceFieldName, - waiters: { - [fieldName]: waiter, - [anotherFieldName]: anotherWaiter, - }, - }, - ], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({ - [fieldName]: waiter, - [anotherFieldName]: anotherWaiter, - }); - expect(mutableState.fields[fieldName].validating).toBe(true); - expect(mutableState.fields[anotherFieldName].validating).toBe(true); - }); - - it('should merge waiters with existing waiters', () => { - const anotherFieldName = 'anotherFieldName'; - const anotherWaiter = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'anotherValue', - }; - - serviceField.data = { - waiters: { - [anotherFieldName]: anotherWaiter, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: field, - }; - - setValidationWaiters( - [{serviceFieldName, waiters: {[fieldName]: waiter}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({ - [anotherFieldName]: anotherWaiter, - [fieldName]: waiter, - }); - expect(mutableState.fields[fieldName].validating).toBe(true); - }); - }); - - describe('setValidationCache', () => { - const fieldName = 'fieldName'; - const serviceFieldName = 'serviceFieldName'; - const tools = {} as Tools<{}, {}>; - - let serviceField: InternalFieldState; - let field: InternalFieldState; - let mutableState: MutableState<{}, {}>; - let waiter: ValidationWaiter; - let cache: ValidationCache; - - beforeEach(() => { - serviceField = { - data: {}, - } as InternalFieldState; - field = {} as InternalFieldState; - mutableState = { - fields: {}, - formState: {} as InternalFormState, - fieldSubscribers: {}, - }; - waiter = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'value', - }; - cache = { - ...waiter, - result: 'result', - }; - }); - - it('should not modify state if field does not exist', () => { - setValidationCache( - [{serviceFieldName, cache: {[fieldName]: cache}}], - mutableState, - tools, - ); - - expect(mutableState.fields).toEqual({}); - }); - - it('should not modify state if cache is not provided', () => { - mutableState.fields = {[serviceFieldName]: serviceField}; - - setValidationCache([{serviceFieldName, cache: undefined as any}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data).toEqual({}); - }); - - it('should add cache to the validation state', () => { - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: field, - }; - - setValidationCache( - [{serviceFieldName, cache: {[fieldName]: cache}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.cache).toEqual({ - [fieldName]: [cache], - }); - }); - - it('should append to existing cache', () => { - const existingCache: ValidationCache = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'existingValue', - result: 'existingResult', - }; - - serviceField.data = { - cache: { - [fieldName]: [existingCache], - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: field, - }; - - setValidationCache( - [{serviceFieldName, cache: {[fieldName]: cache}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.cache).toEqual({ - [fieldName]: [existingCache, cache], - }); - }); - - it('should clear waiter and set validating to false when waiter matches cache', () => { - serviceField.data = { - waiters: { - [fieldName]: waiter, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: {...field, validating: true}, - }; - - setValidationCache( - [{serviceFieldName, cache: {[fieldName]: cache}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({}); - expect(mutableState.fields[fieldName].validating).toBe(false); - }); - - it('should not clear waiter when waiter does not match cache', () => { - const anotherCache: ValidationCache = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'anotherValue', - result: 'anotherResult', - }; - - serviceField.data = { - waiters: { - [fieldName]: waiter, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: {...field, validating: true}, - }; - - setValidationCache( - [{serviceFieldName, cache: {[fieldName]: anotherCache}}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({ - [fieldName]: waiter, - }); - expect(mutableState.fields[fieldName].validating).toBe(true); - }); - - it('should handle multiple cache entries', () => { - const anotherFieldName = 'anotherFieldName'; - const anotherField = {} as InternalFieldState; - const anotherWaiter = { - schema: {type: JsonSchemaType.String}, - validator: jest.fn(), - value: 'anotherValue', - }; - const anotherCache = { - ...anotherWaiter, - result: 'anotherResult', - }; - - serviceField.data = { - waiters: { - [fieldName]: waiter, - [anotherFieldName]: anotherWaiter, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - [fieldName]: {...field, validating: true}, - [anotherFieldName]: {...anotherField, validating: true}, - }; - - setValidationCache( - [ - { - serviceFieldName, - cache: { - [fieldName]: cache, - [anotherFieldName]: anotherCache, - }, - }, - ], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.waiters).toEqual({}); - expect(mutableState.fields[fieldName].validating).toBe(false); - expect(mutableState.fields[anotherFieldName].validating).toBe(false); - expect(mutableState.fields[serviceFieldName].data.cache).toEqual({ - [fieldName]: [cache], - [anotherFieldName]: [anotherCache], - }); - }); - }); -}); diff --git a/src/lib/unstable/core/mutators/async-validation/async-validation.ts b/src/lib/unstable/core/mutators/async-validation/async-validation.ts deleted file mode 100644 index 55d9d180..00000000 --- a/src/lib/unstable/core/mutators/async-validation/async-validation.ts +++ /dev/null @@ -1,58 +0,0 @@ -import isEqual from 'lodash/isEqual'; -import omit from 'lodash/omit'; - -import type { - SetValidationCacheFunction, - SetValidationWaitersFunction, - ValidationState, -} from './types'; - -export const setValidationWaiters: SetValidationWaitersFunction = ( - [{serviceFieldName, waiters}], - mutableState, -) => { - const validationState = mutableState.fields[serviceFieldName]?.data as - | ValidationState - | undefined; - - if (validationState && waiters) { - Object.keys(waiters).forEach((waiterName) => { - validationState.waiters = { - ...validationState.waiters, - [waiterName]: waiters[waiterName], - }; - - const waiterField = mutableState.fields[waiterName]; - - if (waiterField) { - waiterField.validating = true; - } - }); - } -}; - -export const setValidationCache: SetValidationCacheFunction = ( - [{cache, serviceFieldName}], - mutableState, -) => { - const validationState = mutableState.fields[serviceFieldName]?.data as - | ValidationState - | undefined; - - if (validationState && cache) { - Object.keys(cache).forEach((cacheName) => { - validationState.cache = { - ...validationState.cache, - [cacheName]: [...(validationState.cache?.[cacheName] || []), cache[cacheName]], - }; - - const cacheField = mutableState.fields[cacheName]; - const waiter = validationState.waiters?.[cacheName]; - - if (cacheField && waiter && isEqual(waiter, omit(cache[cacheName], 'result'))) { - validationState.waiters = omit(validationState.waiters, cacheName); - cacheField.validating = false; - } - }); - } -}; diff --git a/src/lib/unstable/core/mutators/async-validation/types.ts b/src/lib/unstable/core/mutators/async-validation/types.ts deleted file mode 100644 index 6ac56bb8..00000000 --- a/src/lib/unstable/core/mutators/async-validation/types.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type {MutableState, Tools} from 'final-form'; - -import type {FieldValue, JsonSchema, SyncValidateError, Validator} from '../../types'; - -export interface ValidationWaiter { - schema: JsonSchema; - validator: Validator; - value: FieldValue | null | undefined; -} - -export interface ValidationCache extends ValidationWaiter { - result: SyncValidateError; -} - -export interface ValidationState { - cache?: { - [key: string]: ValidationCache[] | undefined; - }; - waiters?: { - [key: string]: ValidationWaiter | undefined; - }; -} - -export interface SetValidationWaitersParams { - serviceFieldName: string; - waiters: { - [key: string]: ValidationWaiter; - }; -} - -export type SetValidationWaitersFunction< - FormValues = object, - InitialFormValues = Partial, -> = ( - args: [SetValidationWaitersParams], - state: MutableState, - tools: Tools, -) => void; - -export type SetValidationWaitersMutator = (params: SetValidationWaitersParams) => void; - -export interface SetValidationCacheParams { - cache: { - [key: string]: ValidationCache; - }; - serviceFieldName: string; -} - -export type SetValidationCacheFunction< - FormValues = object, - InitialFormValues = Partial, -> = ( - args: [SetValidationCacheParams], - state: MutableState, - tools: Tools, -) => void; - -export type SetValidationCacheMutator = (params: SetValidationCacheParams) => void; diff --git a/src/lib/unstable/core/mutators/index.ts b/src/lib/unstable/core/mutators/index.ts deleted file mode 100644 index f2d321e6..00000000 --- a/src/lib/unstable/core/mutators/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {setValidationCache, setValidationWaiters} from './async-validation'; -import {removeErrors, setErrors} from './set-errors'; - -export * from './async-validation/types'; -export * from './set-errors/types'; - -export const schemaRendererMutators = { - removeErrors, - setErrors, - setValidationCache, - setValidationWaiters, -}; diff --git a/src/lib/unstable/core/mutators/set-errors/__tests__/set-errors.test.ts b/src/lib/unstable/core/mutators/set-errors/__tests__/set-errors.test.ts deleted file mode 100644 index 56474985..00000000 --- a/src/lib/unstable/core/mutators/set-errors/__tests__/set-errors.test.ts +++ /dev/null @@ -1,307 +0,0 @@ -import type {InternalFieldState, InternalFormState, MutableState, Tools} from 'final-form'; -import omit from 'lodash/omit'; - -import {removeErrors, setErrors} from '../set-errors'; -import type {ErrorsState} from '../types'; - -describe('set-errors', () => { - describe('setErrors', () => { - const fieldName = 'fieldName'; - const serviceFieldName = 'serviceFieldName'; - const priorityErrors: Exclude = { - [fieldName]: 'priorityError', - }; - const regularErrors: Exclude = { - [fieldName]: 'regularErrors', - }; - const tools = {} as Tools<{}, {}>; - - let serviceField: InternalFieldState; - let mutableState: MutableState<{}, {}>; - - beforeEach(() => { - serviceField = {data: {}} as InternalFieldState; - mutableState = { - fields: {}, - formState: {} as InternalFormState, - fieldSubscribers: {}, - }; - }); - - it('should not modify state if service field does not exist', () => { - setErrors([{serviceFieldName, priorityErrors, regularErrors}], mutableState, tools); - - expect(mutableState.fields).toEqual({}); - }); - - it('should not modify state if errors are not provided', () => { - mutableState.fields = {[serviceFieldName]: serviceField}; - - setErrors( - [{serviceFieldName, priorityErrors: undefined, regularErrors: undefined}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data).toEqual({}); - }); - - it('should add priority errors to the errors state', () => { - const anotherFieldName = 'anotherFieldName'; - const existingErrorsState = { - priorityErrors: {[anotherFieldName]: 'anotherPriorityError'}, - regularErrors: {[anotherFieldName]: 'anotherRegularError'}, - }; - - serviceField.data = existingErrorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - setErrors([{serviceFieldName, priorityErrors}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual({ - ...existingErrorsState.priorityErrors, - ...priorityErrors, - }); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual( - existingErrorsState.regularErrors, - ); - }); - - it('should add regular errors to the errors state', () => { - const anotherFieldName = 'anotherFieldName'; - const existingErrorsState = { - priorityErrors: {[anotherFieldName]: 'anotherPriorityError'}, - regularErrors: {[anotherFieldName]: 'anotherRegularError'}, - }; - - serviceField.data = existingErrorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - setErrors([{serviceFieldName, regularErrors}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual( - existingErrorsState.priorityErrors, - ); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual({ - ...existingErrorsState.regularErrors, - ...regularErrors, - }); - }); - - it('should add both priority and regular errors to the errors state', () => { - const anotherFieldName = 'anotherFieldName'; - const existingErrorsState = { - priorityErrors: {[anotherFieldName]: 'anotherPriorityError'}, - regularErrors: {[anotherFieldName]: 'anotherRegularError'}, - }; - - serviceField.data = existingErrorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - setErrors([{serviceFieldName, priorityErrors, regularErrors}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual({ - ...existingErrorsState.priorityErrors, - ...priorityErrors, - }); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual({ - ...existingErrorsState.regularErrors, - ...regularErrors, - }); - }); - - it('should override existing errors with the same field name', () => { - const existingErrorsState = { - priorityErrors: {[fieldName]: 'anotherPriorityError'}, - regularErrors: {[fieldName]: 'anotherRegularError'}, - }; - - serviceField.data = existingErrorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - setErrors([{serviceFieldName, priorityErrors, regularErrors}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual( - priorityErrors, - ); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual(regularErrors); - }); - }); - - describe('removeErrors', () => { - const fieldName = 'fieldName'; - const serviceFieldName = 'serviceFieldName'; - const tools = {} as Tools<{}, {}>; - - let serviceField: InternalFieldState; - let mutableState: MutableState<{}, {}>; - let errorsState: ErrorsState; - - beforeEach(() => { - serviceField = {data: {}} as InternalFieldState; - mutableState = { - fields: {}, - formState: {} as InternalFormState, - fieldSubscribers: {}, - }; - errorsState = { - priorityErrors: {[fieldName]: 'priorityError'}, - regularErrors: {[fieldName]: 'regularError'}, - }; - }); - - it('should not modify state if service field does not exist', () => { - const anotherFieldName = 'anotherFieldName'; - - removeErrors( - [{serviceFieldName, removeFunctionOrNames: [fieldName, anotherFieldName]}], - mutableState, - tools, - ); - - expect(mutableState.fields).toEqual({}); - }); - - it('should not modify state if removeFunctionOrNames is not provided', () => { - serviceField.data = errorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - removeErrors( - [{serviceFieldName, removeFunctionOrNames: undefined as any}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data).toEqual(errorsState); - }); - - it('should not modify state if field names array is empty', () => { - serviceField.data = errorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - removeErrors([{serviceFieldName, removeFunctionOrNames: []}], mutableState, tools); - - expect(mutableState.fields[serviceFieldName].data).toEqual(errorsState); - }); - - it('should remove errors by field names', () => { - const anotherFieldName1 = 'anotherFieldName1'; - const anotherPriorityErrors1 = {[anotherFieldName1]: 'anotherPriorityError1'}; - const anotherRegularErrors1 = {[anotherFieldName1]: 'anotherRegularError1'}; - const anotherFieldName2 = 'anotherFieldName2'; - const anotherPriorityErrors2 = {[anotherFieldName2]: 'anotherPriorityError2'}; - const anotherRegularErrors2 = {[anotherFieldName2]: 'anotherRegularError2'}; - const anotherFieldName3 = 'anotherFieldName3'; - const anotherPriorityErrors3 = {[anotherFieldName3]: 'anotherPriorityError3'}; - const anotherRegularErrors3 = {[anotherFieldName3]: 'anotherRegularError3'}; - - serviceField.data = { - priorityErrors: { - ...anotherPriorityErrors1, - ...anotherPriorityErrors2, - ...anotherPriorityErrors3, - }, - regularErrors: { - ...anotherRegularErrors1, - ...anotherRegularErrors2, - ...anotherRegularErrors3, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - removeErrors( - [{serviceFieldName, removeFunctionOrNames: [anotherFieldName1, anotherFieldName2]}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual( - anotherPriorityErrors3, - ); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual( - anotherRegularErrors3, - ); - }); - - it('should remove errors using a custom function', () => { - const anotherFieldName1 = 'anotherFieldName1'; - const anotherPriorityErrors1 = {[anotherFieldName1]: 'anotherPriorityError1'}; - const anotherRegularErrors1 = {[anotherFieldName1]: 'anotherRegularError1'}; - const anotherFieldName2 = 'anotherFieldName2'; - const anotherPriorityErrors2 = {[anotherFieldName2]: 'anotherPriorityError2'}; - const anotherRegularErrors2 = {[anotherFieldName2]: 'anotherRegularError2'}; - const anotherFieldName3 = 'anotherFieldName3'; - const anotherPriorityErrors3 = {[anotherFieldName3]: 'anotherPriorityError3'}; - const anotherRegularErrors3 = {[anotherFieldName3]: 'anotherRegularError3'}; - - serviceField.data = { - priorityErrors: { - ...anotherPriorityErrors1, - ...anotherPriorityErrors2, - ...anotherPriorityErrors3, - }, - regularErrors: { - ...anotherRegularErrors1, - ...anotherRegularErrors2, - ...anotherRegularErrors3, - }, - }; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - const customRemoveFunction = (state: ErrorsState): ErrorsState => { - return { - priorityErrors: omit(state.priorityErrors, [anotherFieldName1]), - regularErrors: omit(state.regularErrors, [anotherFieldName2]), - }; - }; - - removeErrors( - [{serviceFieldName, removeFunctionOrNames: customRemoveFunction}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data.priorityErrors).toEqual({ - ...anotherPriorityErrors2, - ...anotherPriorityErrors3, - }); - expect(mutableState.fields[serviceFieldName].data.regularErrors).toEqual({ - ...anotherRegularErrors1, - ...anotherRegularErrors3, - }); - }); - - it('should handle non-existent field names', () => { - const anotherFieldName = 'anotherFieldName'; - - serviceField.data = errorsState; - mutableState.fields = { - [serviceFieldName]: serviceField, - }; - - removeErrors( - [{serviceFieldName, removeFunctionOrNames: [anotherFieldName]}], - mutableState, - tools, - ); - - expect(mutableState.fields[serviceFieldName].data).toEqual(errorsState); - }); - }); -}); diff --git a/src/lib/unstable/core/mutators/set-errors/index.ts b/src/lib/unstable/core/mutators/set-errors/index.ts deleted file mode 100644 index 8e4fa797..00000000 --- a/src/lib/unstable/core/mutators/set-errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './set-errors'; -export * from './types'; diff --git a/src/lib/unstable/core/mutators/set-errors/set-errors.ts b/src/lib/unstable/core/mutators/set-errors/set-errors.ts deleted file mode 100644 index 0818ecea..00000000 --- a/src/lib/unstable/core/mutators/set-errors/set-errors.ts +++ /dev/null @@ -1,40 +0,0 @@ -import omit from 'lodash/omit'; - -import type {ErrorsState, RemoveErrorsFunction, SetErrorsFunction} from './types'; - -export const setErrors: SetErrorsFunction = ( - [{serviceFieldName, priorityErrors, regularErrors}], - mutableState, -) => { - const errorsState = mutableState.fields[serviceFieldName]?.data as ErrorsState | undefined; - - if (errorsState && (priorityErrors || regularErrors)) { - errorsState.priorityErrors = { - ...errorsState.priorityErrors, - ...(priorityErrors || {}), - }; - errorsState.regularErrors = { - ...errorsState.regularErrors, - ...(regularErrors || {}), - }; - } -}; - -export const removeErrors: RemoveErrorsFunction = ( - [{removeFunctionOrNames, serviceFieldName}], - mutableState, -) => { - const errorsState = mutableState.fields[serviceFieldName]?.data as ErrorsState | undefined; - - if (errorsState && removeFunctionOrNames) { - const {priorityErrors, regularErrors} = Array.isArray(removeFunctionOrNames) - ? { - priorityErrors: omit(errorsState.priorityErrors, removeFunctionOrNames), - regularErrors: omit(errorsState.regularErrors, removeFunctionOrNames), - } - : removeFunctionOrNames(errorsState); - - errorsState.priorityErrors = priorityErrors; - errorsState.regularErrors = regularErrors; - } -}; diff --git a/src/lib/unstable/core/mutators/set-errors/types.ts b/src/lib/unstable/core/mutators/set-errors/types.ts deleted file mode 100644 index 1f4d7e49..00000000 --- a/src/lib/unstable/core/mutators/set-errors/types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type {MutableState, Tools} from 'final-form'; - -import type {SyncValidateError} from '../../types'; - -export interface ErrorsState { - priorityErrors?: { - [key: string]: SyncValidateError; - }; - regularErrors?: { - [key: string]: SyncValidateError; - }; -} - -export interface SetErrorsParams { - priorityErrors?: { - [key: string]: SyncValidateError; - }; - serviceFieldName: string; - regularErrors?: { - [key: string]: SyncValidateError; - }; -} - -export type SetErrorsFunction> = ( - args: [SetErrorsParams], - state: MutableState, - tools: Tools, -) => void; - -export type SetErrorsMutator = (params: SetErrorsParams) => void; - -export interface RemoveErrorsParams { - removeFunctionOrNames: string[] | ((state: ErrorsState) => ErrorsState); - serviceFieldName: string; -} - -export type RemoveErrorsFunction> = ( - args: [RemoveErrorsParams], - state: MutableState, - tools: Tools, -) => void; - -export type RemoveErrorsMutator = (params: RemoveErrorsParams) => void; diff --git a/src/lib/unstable/core/types/components.ts b/src/lib/unstable/core/types/components.ts index c9c474d1..2ddd87bf 100644 --- a/src/lib/unstable/core/types/components.ts +++ b/src/lib/unstable/core/types/components.ts @@ -5,45 +5,54 @@ import type {FieldRenderProps} from 'react-final-form'; import type {SchemaToValueType} from './helpers'; import type {JsonSchema} from './schema'; -export interface SimpleViewProps< +export interface ControlProps< Schema extends JsonSchema, - ViewComponentProps extends Record = {}, + ControlComponentProps extends Record = {}, + WrapperComponentProps extends Record = {}, > extends FieldRenderProps | null | undefined> { schema: Schema; - viewProps: Partial; + controlProps: Partial; + Wrapper?: Wrapper; + wrapperProps?: Partial; } -export type SimpleView< +export type Control< Schema extends JsonSchema, - ViewComponentProps extends Record = {}, -> = React.ComponentType>; + ControlComponentProps extends Record = {}, + WrapperComponentProps extends Record = {}, +> = React.ComponentType>; -export interface WrapperProps< +export interface ViewProps< Schema extends JsonSchema, + ViewComponentProps extends Record = {}, WrapperComponentProps extends Record = {}, > extends FieldRenderProps | null | undefined> { schema: Schema; - wrapperProps: Partial; + viewProps: Partial; + Wrapper?: Wrapper; + wrapperProps?: Partial; } -export type Wrapper< +export type View< Schema extends JsonSchema, + ViewComponentProps extends Record = {}, WrapperComponentProps extends Record = {}, -> = React.ComponentType>; +> = React.ComponentType>; -export interface IndependentViewProps< +export interface WrapperProps< Schema extends JsonSchema, - ComponentProps extends Record = {}, WrapperComponentProps extends Record = {}, -> extends SimpleViewProps { - Wrapper?: Wrapper; - wrapperProps: Partial; +> extends FieldRenderProps | null | undefined> { + schema: Schema; + wrapperProps: { + open?: boolean; + hidden?: boolean; + copy?: boolean; + required?: boolean; + } & Partial; } -export type IndependentView< +export type Wrapper< Schema extends JsonSchema, - ComponentProps extends Record = {}, WrapperComponentProps extends Record = {}, -> = React.ComponentType>; - -export type View = SimpleView | IndependentView; +> = React.ComponentType>; diff --git a/src/lib/unstable/core/types/config.ts b/src/lib/unstable/core/types/config.ts index 236fa348..5a3e77e6 100644 --- a/src/lib/unstable/core/types/config.ts +++ b/src/lib/unstable/core/types/config.ts @@ -1,8 +1,9 @@ -import type {JsonSchemaType, SchemaRendererMode} from '../constants'; +import type {JsonSchemaType} from '../constants'; -import type {IndependentView, SimpleView, Wrapper} from './components'; +import type {Control, View, Wrapper} from './components'; import type { JsonSchema, + JsonSchemaAny, JsonSchemaArray, JsonSchemaBoolean, JsonSchemaNumber, @@ -11,27 +12,12 @@ import type { } from './schema'; import type {Validator} from './validation'; -export interface SimpleViewComponentConfig { - Component: SimpleView | null; - independent?: false; -} - -export interface IndependentViewComponentConfig { - Component: IndependentView; - independent: true; -} - -export type ViewComponentConfig = - | SimpleViewComponentConfig - | IndependentViewComponentConfig; - -export interface ViewConfig { - [SchemaRendererMode.Form]: ViewComponentConfig; - [SchemaRendererMode.Overview]: ViewComponentConfig; +export interface ControlsConfig { + [key: string]: {Component?: Control; independent?: boolean} | undefined; } export interface ViewsConfig { - [key: string]: ViewConfig | undefined; + [key: string]: {Component?: View; independent?: boolean} | undefined; } export interface WrappersConfig { @@ -43,12 +29,14 @@ export interface ValidatorsConfig { } export interface TypeConfig { + controls: ControlsConfig; views: ViewsConfig; wrappers: WrappersConfig; validators: ValidatorsConfig; } export interface SchemaRendererConfig { + [JsonSchemaType.Any]: TypeConfig; [JsonSchemaType.Array]: TypeConfig; [JsonSchemaType.Boolean]: TypeConfig; [JsonSchemaType.Number]: TypeConfig; diff --git a/src/lib/unstable/core/types/helpers.ts b/src/lib/unstable/core/types/helpers.ts index 352452cd..fff1d957 100644 --- a/src/lib/unstable/core/types/helpers.ts +++ b/src/lib/unstable/core/types/helpers.ts @@ -1,8 +1,7 @@ import type React from 'react'; -import type {JsonSchemaType, SchemaRendererMode} from '../constants'; +import type {JsonSchemaType} from '../constants'; -import type {SchemaRendererConfig} from './config'; import type { JsonSchema, JsonSchemaArray, @@ -35,51 +34,24 @@ export type SchemaToSchemaType = Schema extends JsonS ? JsonSchemaType.Object : Schema extends JsonSchemaString ? JsonSchemaType.String - : JsonSchemaType; + : JsonSchemaType.Any; -export type SchemaTypeToSchema = - SchemaType extends JsonSchemaType.Array - ? JsonSchemaArray - : SchemaType extends JsonSchemaType.Boolean - ? JsonSchemaBoolean - : SchemaType extends JsonSchemaType.Number - ? JsonSchemaNumber - : SchemaType extends JsonSchemaType.Object - ? JsonSchemaObject - : SchemaType extends JsonSchemaType.String - ? JsonSchemaString - : JsonSchema; - -type ExtractViewProps = ViewConfig extends { - [SchemaRendererMode.Form]: infer FormComponentConfig; - [SchemaRendererMode.Overview]: infer OverviewComponentConfig; +export type ExtractControlProps = Control extends { + Component: React.ComponentType; } - ? (FormComponentConfig extends { - Component: React.ComponentType; - } - ? FormViewComponentProps extends {viewProps: infer ViewComponentProps} - ? ViewComponentProps - : Record - : Record) & - (OverviewComponentConfig extends { - Component: React.ComponentType; - } - ? OverivewViewComponentProps extends {viewProps: infer ViewComponentProps} - ? ViewComponentProps - : Record - : Record) + ? ControlProps extends {controlProps: infer ControlComponentProps} + ? ControlComponentProps + : Record : Record; -export type ViewComponentPropsByConfig = { - [Key in keyof Config['views']]: ExtractViewProps; -}; +export type ExtractViewProps = View extends {Component: React.ComponentType} + ? ViewProps extends {viewProps: infer ViewComponentProps} + ? ViewComponentProps + : Record + : Record; -type ExtractWrapperProps = Wrapper extends React.ComponentType +export type ExtractWrapperProps = Wrapper extends React.ComponentType ? WrapperProps extends {wrapperProps: infer WrapperComponentProps} ? WrapperComponentProps : Record : Record; - -export type WrapperComponentPropsByConfig = { - [Key in keyof Config['wrappers']]: ExtractWrapperProps; -}; diff --git a/src/lib/unstable/core/types/index.ts b/src/lib/unstable/core/types/index.ts index 686dd49d..1eba2868 100644 --- a/src/lib/unstable/core/types/index.ts +++ b/src/lib/unstable/core/types/index.ts @@ -1,6 +1,5 @@ export * from './components'; export * from './config'; -export * from './helpers'; export * from './schema'; export * from './validation'; export * from './values'; diff --git a/src/lib/unstable/core/types/schema.ts b/src/lib/unstable/core/types/schema.ts index b1ff0725..cc37e5ba 100644 --- a/src/lib/unstable/core/types/schema.ts +++ b/src/lib/unstable/core/types/schema.ts @@ -1,18 +1,24 @@ import type {JsonSchemaType} from '../constants'; import type {SchemaRendererConfig} from './config'; -import type {ViewComponentPropsByConfig, WrapperComponentPropsByConfig} from './helpers'; +import type { + ExtractControlProps, + ExtractViewProps, + ExtractWrapperProps, + SchemaToSchemaType, +} from './helpers'; import type {ErrorMessages} from './validation'; import type {ArrayValue, FieldValue, ObjectValue} from './values'; interface EntityParameters< Config extends SchemaRendererConfig[JsonSchemaType] = SchemaRendererConfig[JsonSchemaType], - ViewKey extends Exclude, number> = Exclude< - keyof ViewComponentPropsByConfig, + ControlKey extends Exclude = Exclude< + keyof Config['controls'], number >, - WrapperKey extends Exclude, number> = Exclude< - keyof WrapperComponentPropsByConfig, + ViewKey extends Exclude = Exclude, + WrapperKey extends Exclude = Exclude< + keyof Config['wrappers'], number >, > { @@ -24,97 +30,861 @@ interface EntityParameters< dependencies?: string | Record; required?: string | Record; }; // todo - validatorType?: string; + controlType?: ControlKey; + controlProps?: ExtractControlProps; + controlWrapperType?: WrapperKey; + controlWrapperProps?: ExtractWrapperProps; viewType?: ViewKey; - viewProps?: ViewComponentPropsByConfig[ViewKey]; - wrapperType?: WrapperKey; - wrapperProps?: { - open?: boolean; - hidden?: boolean; - copy?: boolean; - required?: boolean; - } & WrapperComponentPropsByConfig[WrapperKey]; + viewProps?: ExtractViewProps; + viewWrapperType?: WrapperKey; + viewWrapperProps?: ExtractWrapperProps; + validatorType?: string; }; } -interface JsonSchemaBase { +/** + * Common JSON Schema keywords inherited by every concrete schema variant. + */ +interface JsonSchemaBase< + Schema extends JsonSchema, + Value extends FieldValue, + Config extends SchemaRendererConfig = SchemaRendererConfig, +> extends EntityParameters]> { + /** + * Unique URI identifier for the schema; serves as the base URI when resolving relative `$ref`s in nested sub-schemas. + * + * @example + * { $id: 'https://example.com/schemas/person', type: JsonSchemaType.Object } + */ + $id?: string; + + /** + * URI or JSON Pointer to another schema; when resolved, the current schema is replaced by the referenced one. + * + * @example + * { $ref: '#/$defs/positiveInt' } + * + * @example + * { $ref: 'https://example.com/schemas/address' } + */ + $ref?: string; + + /** + * URI of the JSON Schema dialect (draft) this schema conforms to. + * + * @example + * { $schema: 'https://json-schema.org/draft/2020-12/schema', type: JsonSchemaType.Object } + */ + $schema?: string; + + /** + * Free-form note for schema authors; not surfaced to end users by the renderer. + * + * @example + * { type: JsonSchemaType.String, $comment: 'TODO: tighten validation once backend lands' } + */ + $comment?: string; + + /** + * Map of named, reusable sub-schemas referenced via `$ref` (draft-07 keyword; prefer `$defs` in newer drafts). + * + * @example + * { definitions: { positiveInt: { type: JsonSchemaType.Number, minimum: 1 } } } + */ + definitions?: {[key: string]: JsonSchema}; + + /** + * Map of named, reusable sub-schemas referenced via `$ref` (draft 2019-09+; replaces `definitions`). + * + * @example + * { $defs: { name: { type: JsonSchemaType.String, minLength: 1 } } } + */ + $defs?: {[key: string]: JsonSchema}; + + /** + * Short, human-readable label for the field; typically rendered as the form-control label. + * + * @example + * { type: JsonSchemaType.String, title: 'First name' } + */ + title?: string; + + /** + * Longer, human-readable explanation of the field; typically rendered as helper text below the control. + * + * @example + * { type: JsonSchemaType.String, description: 'As shown on your government-issued ID' } + */ + description?: string; + + /** + * Default value used when the field has not been supplied a value. + * + * @example + * { type: JsonSchemaType.String, default: 'guest' } + * + * @example + * { type: JsonSchemaType.Number, default: 0 } + */ + default?: Value; + + /** + * Sample values illustrating typical inputs; used for documentation and tooling, not for validation. + * + * @example + * { type: JsonSchemaType.String, examples: ['alice@example.com', 'bob@example.com'] } + */ + examples?: Value[]; + + /** + * Marks the field as read-only; the renderer displays the value but disables editing. + * + * @example + * { type: JsonSchemaType.String, readOnly: true } + */ + readOnly?: boolean; + + /** + * Marks the field as write-only; the value is accepted on submission but never echoed back (e.g. passwords). + * + * @example + * { type: JsonSchemaType.String, writeOnly: true } + */ + writeOnly?: boolean; + + /** + * Indicates the field is deprecated; the renderer may warn or hide it accordingly. + * + * @example + * { type: JsonSchemaType.String, deprecated: true } + */ + deprecated?: boolean; + + /** + * Restricts the value to exactly one of the listed entries. + * + * @example + * { type: JsonSchemaType.String, enum: ['draft', 'published', 'archived'] } + * + * @example + * { type: JsonSchemaType.Number, enum: [1, 2, 3] } + */ + enum?: Value[]; + + /** + * Restricts the value to exactly this single constant. + * + * @example + * { type: JsonSchemaType.String, const: 'v1' } + */ + const?: Value; + + /** + * The value must satisfy every sub-schema in this list (logical AND). + * + * @example + * { type: JsonSchemaType.String, allOf: [{ type: JsonSchemaType.String, minLength: 3 }, { type: JsonSchemaType.String, pattern: '^[a-z]+$' }] } + */ allOf?: Schema[]; + + /** + * The value must satisfy at least one sub-schema in this list (logical OR). + * + * @example + * { anyOf: [{ type: JsonSchemaType.String }, { type: JsonSchemaType.Number }] } + */ anyOf?: Schema[]; - const?: ValueType; - default?: ValueType; - description?: string; - else?: Schema; - enum?: ValueType[]; - examples?: ValueType[]; - if?: Schema; - not?: Schema; + + /** + * The value must satisfy exactly one sub-schema in this list (logical XOR). + * + * @example + * { type: JsonSchemaType.String, oneOf: [{ type: JsonSchemaType.String, maxLength: 5 }, { type: JsonSchemaType.String, minLength: 10 }] } + */ oneOf?: Schema[]; - readOnly?: boolean; + + /** + * The value must NOT satisfy this sub-schema (logical NOT). + * + * @example + * { type: JsonSchemaType.String, not: { type: JsonSchemaType.String, const: 'forbidden' } } + */ + not?: Schema; + + /** + * Conditional sub-schema; when the value satisfies it, `then` is applied, otherwise `else`. + * + * @example + * { type: JsonSchemaType.String, if: { type: JsonSchemaType.String, const: 'ja' }, then: { type: JsonSchemaType.String, minLength: 5 }, else: { type: JsonSchemaType.String, minLength: 10 } } + */ + if?: Schema; + + /** + * Sub-schema applied when the `if` condition is satisfied. + * + * @example + * { type: JsonSchemaType.String, if: { type: JsonSchemaType.String, const: 'ja' }, then: { type: JsonSchemaType.String, minLength: 5 } } + */ then?: Schema; - title?: string; - writeOnly?: boolean; + + /** + * Sub-schema applied when the `if` condition is not satisfied. + * + * @example + * { type: JsonSchemaType.String, if: { type: JsonSchemaType.String, const: 'ja' }, else: { type: JsonSchemaType.String, minLength: 10 } } + */ + else?: Schema; } -export interface JsonSchemaArray - extends JsonSchemaBase, - EntityParameters { - contains?: boolean | JsonSchema; +/** + * Schema variant without an explicit `type` keyword. Matches values of any JSON type and accepts every keyword from the typed variants. + */ +export interface JsonSchemaAny + extends JsonSchemaBase { + /** + * Distinguishes this variant from typed schemas: when `type` is omitted, the schema matches values of any JSON type. + * + * @example + * { enum: ['yes', 1, true] } + */ + type?: undefined; + + /** + * Shortest allowed length (in characters) when the value is a string; ignored for non-string values. + * + * @example + * { minLength: 5 } + */ + minLength?: number; + + /** + * Longest allowed length (in characters) when the value is a string; ignored for non-string values. + * + * @example + * { maxLength: 100 } + */ + maxLength?: number; + + /** + * Regular expression the value must match when it is a string; ignored for non-string values. + * + * @example + * { pattern: '^[A-Z][a-z]+$' } + */ + pattern?: string; + + /** + * Semantic format the value must conform to when it is a string (e.g. `email`, `date`, `uri`). + * + * @example + * { format: 'email' } + * + * @example + * { format: 'date-time' } + */ + format?: string; + + /** + * Encoding used by the string value (e.g. `base64`); enables the parsed content to be validated by `contentSchema`. + * + * @example + * { contentEncoding: 'base64', contentMediaType: 'image/png' } + */ + contentEncoding?: string; + + /** + * MIME type of the parsed string content; pairs with `contentEncoding` and `contentSchema`. + * + * @example + * { contentMediaType: 'application/json' } + */ + contentMediaType?: string; + + /** + * Sub-schema validating the parsed content of a string described by `contentMediaType` / `contentEncoding`. + * + * @example + * { contentMediaType: 'application/json', contentSchema: { type: JsonSchemaType.Object } } + */ + contentSchema?: JsonSchema; + + /** + * Smallest allowed numeric value (inclusive); ignored for non-numeric values. + * + * @example + * { minimum: 0 } + */ + minimum?: number; + + /** + * Largest allowed numeric value (inclusive); ignored for non-numeric values. + * + * @example + * { maximum: 100 } + */ + maximum?: number; + + /** + * Strict lower bound for numeric values (value must be greater than this); ignored for non-numeric values. + * + * @example + * { exclusiveMinimum: 0 } + */ + exclusiveMinimum?: number; + + /** + * Strict upper bound for numeric values (value must be less than this); ignored for non-numeric values. + * + * @example + * { exclusiveMaximum: 100 } + */ + exclusiveMaximum?: number; + + /** + * Numeric values must be an exact multiple of this number (supports fractional steppers); ignored for non-numeric values. + * + * @example + * { multipleOf: 0.25 } + */ + multipleOf?: number; + + /** + * Schema (or positional tuple of schemas) that every array item must satisfy; ignored for non-array values. + * + * @example + * { items: { type: JsonSchemaType.String } } + * + * @example + * { items: [{ type: JsonSchemaType.String }, { type: JsonSchemaType.Number }] } + */ items?: JsonSchema | JsonSchema[]; - maxItems?: number; + + /** + * Schema applied to array items beyond the positional tuple defined by `items`; ignored for non-array values. + * + * @example + * { items: [{ type: JsonSchemaType.String }], additionalItems: { type: JsonSchemaType.Number } } + */ + additionalItems?: JsonSchema; + + /** + * Positional tuple of schemas validating the first N array items (draft 2020-12 replacement for tuple `items`). + * + * @example + * { prefixItems: [{ type: JsonSchemaType.String }, { type: JsonSchemaType.Number }] } + */ + prefixItems?: JsonSchema[]; + + /** + * Schema applied to array items not validated by `items` or `prefixItems` (draft 2019-09+). + * + * @example + * { prefixItems: [{ type: JsonSchemaType.String }], unevaluatedItems: { type: JsonSchemaType.Number } } + */ + unevaluatedItems?: JsonSchema; + + /** + * Sub-schema that at least one array item must satisfy; ignored for non-array values. + * + * @example + * { contains: { type: JsonSchemaType.Number, minimum: 5 } } + */ + contains?: JsonSchema; + + /** + * Minimum number of array items that must satisfy `contains`. + * + * @example + * { contains: { type: JsonSchemaType.Number }, minContains: 2 } + */ + minContains?: number; + + /** + * Maximum number of array items that may satisfy `contains`. + * + * @example + * { contains: { type: JsonSchemaType.Number }, maxContains: 3 } + */ + maxContains?: number; + + /** + * Minimum number of items the array must contain; ignored for non-array values. + * + * @example + * { minItems: 1 } + */ minItems?: number; + + /** + * Maximum number of items the array may contain; ignored for non-array values. + * + * @example + * { maxItems: 10 } + */ + maxItems?: number; + + /** + * When true, every array item must be unique; ignored for non-array values. + * + * @example + * { uniqueItems: true } + */ + uniqueItems?: boolean; + + /** + * Map of property name to schema; defines the validated sub-schema for each named property of an object value. + * + * @example + * { properties: { name: { type: JsonSchemaType.String } } } + */ + properties?: {[key: string]: JsonSchema}; + + /** + * Map of regular expression to schema; properties whose name matches a pattern must satisfy the corresponding schema. + * + * @example + * { patternProperties: { '^x-': { type: JsonSchemaType.String } } } + */ + patternProperties?: {[key: string]: JsonSchema}; + + /** + * Schema applied to object properties not listed in `properties` and not matched by `patternProperties`. + * + * @example + * { properties: { name: { type: JsonSchemaType.String } }, additionalProperties: { type: JsonSchemaType.Number } } + */ + additionalProperties?: JsonSchema; + + /** + * Schema applied to object properties not already validated by `properties`, `patternProperties`, or `additionalProperties` (draft 2019-09+). + * + * @example + * { properties: { name: { type: JsonSchemaType.String } }, unevaluatedProperties: { type: JsonSchemaType.Number } } + */ + unevaluatedProperties?: JsonSchema; + + /** + * Schema each property name must satisfy (typically a `type: string` schema constraining length or pattern). + * + * @example + * { propertyNames: { type: JsonSchemaType.String, pattern: '^[a-z][a-zA-Z0-9]*$' } } + */ + propertyNames?: JsonSchema; + + /** + * Names of properties that must be present on the object value. + * + * @example + * { properties: { name: { type: JsonSchemaType.String } }, required: ['name'] } + */ + required?: string[]; + + /** + * Minimum number of properties the object must declare; ignored for non-object values. + * + * @example + * { minProperties: 1 } + */ + minProperties?: number; + + /** + * Maximum number of properties the object may declare; ignored for non-object values. + * + * @example + * { maxProperties: 5 } + */ + maxProperties?: number; + + /** + * Property-level dependencies: when a key is present, either the listed property names must also be present, or the given sub-schema must hold for the whole object (draft-07 keyword). + * + * @example + * { dependencies: { credit_card: ['billing_address'] } } + * + * @example + * { dependencies: { credit_card: { type: JsonSchemaType.Object, required: ['billing_address'] } } } + */ + dependencies?: {[key: string]: string[] | JsonSchema}; + + /** + * When the keyed property is present, the listed property names are also required (draft 2019-09+; replaces the array form of `dependencies`). + * + * @example + * { dependentRequired: { credit_card: ['billing_address'] } } + */ + dependentRequired?: {[key: string]: string[]}; + + /** + * When the keyed property is present, the additional sub-schema must hold for the whole object (draft 2019-09+; replaces the schema form of `dependencies`). + * + * @example + * { dependentSchemas: { credit_card: { type: JsonSchemaType.Object, required: ['billing_address'] } } } + */ + dependentSchemas?: {[key: string]: JsonSchema}; +} + +/** + * Schema for array values (`type: JsonSchemaType.Array`). + */ +export interface JsonSchemaArray + extends JsonSchemaBase { + /** + * Marks this schema as describing an array value. + * + * @example + * { type: JsonSchemaType.Array, items: { type: JsonSchemaType.String } } + */ type: JsonSchemaType.Array; + + /** + * Schema (or positional tuple of schemas) that every array item must satisfy. + * + * @example + * { type: JsonSchemaType.Array, items: { type: JsonSchemaType.String } } + * + * @example + * { type: JsonSchemaType.Array, items: [{ type: JsonSchemaType.String }, { type: JsonSchemaType.Number }] } + */ + items?: JsonSchema | JsonSchema[]; + + /** + * Schema applied to array items beyond the positional tuple defined by `items`. + * + * @example + * { type: JsonSchemaType.Array, items: [{ type: JsonSchemaType.String }], additionalItems: { type: JsonSchemaType.Number } } + */ + additionalItems?: JsonSchema; + + /** + * Positional tuple of schemas validating the first N array items (draft 2020-12 replacement for tuple `items`). + * + * @example + * { type: JsonSchemaType.Array, prefixItems: [{ type: JsonSchemaType.String }, { type: JsonSchemaType.Number }] } + */ + prefixItems?: JsonSchema[]; + + /** + * Schema applied to array items not already validated by `items` or `prefixItems` (draft 2019-09+). + * + * @example + * { type: JsonSchemaType.Array, prefixItems: [{ type: JsonSchemaType.String }], unevaluatedItems: { type: JsonSchemaType.Number } } + */ + unevaluatedItems?: JsonSchema; + + /** + * Sub-schema that at least one array item must satisfy. + * + * @example + * { type: JsonSchemaType.Array, contains: { type: JsonSchemaType.Number, minimum: 5 } } + */ + contains?: JsonSchema; + + /** + * Minimum number of array items that must satisfy `contains`. + * + * @example + * { type: JsonSchemaType.Array, contains: { type: JsonSchemaType.Number }, minContains: 2 } + */ + minContains?: number; + + /** + * Maximum number of array items that may satisfy `contains`. + * + * @example + * { type: JsonSchemaType.Array, contains: { type: JsonSchemaType.Number }, maxContains: 3 } + */ + maxContains?: number; + + /** + * Minimum number of items the array must contain. + * + * @example + * { type: JsonSchemaType.Array, minItems: 1 } + */ + minItems?: number; + + /** + * Maximum number of items the array may contain. + * + * @example + * { type: JsonSchemaType.Array, maxItems: 10 } + */ + maxItems?: number; + + /** + * When true, every array item must be unique. + * + * @example + * { type: JsonSchemaType.Array, items: { type: JsonSchemaType.String }, uniqueItems: true } + */ uniqueItems?: boolean; } +/** + * Schema for boolean values (`type: JsonSchemaType.Boolean`). + */ export interface JsonSchemaBoolean - extends JsonSchemaBase, - EntityParameters { + extends JsonSchemaBase { + /** + * Marks this schema as describing a boolean value. + * + * @example + * { type: JsonSchemaType.Boolean, default: false } + */ type: JsonSchemaType.Boolean; } +/** + * Schema for numeric values (`type: JsonSchemaType.Number`). + */ export interface JsonSchemaNumber - extends JsonSchemaBase, - EntityParameters { - exclusiveMaximum?: number; - exclusiveMinimum?: number; - maximum?: number; + extends JsonSchemaBase { + /** + * Marks this schema as describing a numeric value. + * + * @example + * { type: JsonSchemaType.Number, minimum: 0, maximum: 100 } + */ + type: JsonSchemaType.Number; + + /** + * Smallest allowed value (inclusive). + * + * @example + * { type: JsonSchemaType.Number, minimum: 0 } + */ minimum?: number; + + /** + * Largest allowed value (inclusive). + * + * @example + * { type: JsonSchemaType.Number, maximum: 100 } + */ + maximum?: number; + + /** + * Strict lower bound (value must be greater than this). + * + * @example + * { type: JsonSchemaType.Number, exclusiveMinimum: 0 } + */ + exclusiveMinimum?: number; + + /** + * Strict upper bound (value must be less than this). + * + * @example + * { type: JsonSchemaType.Number, exclusiveMaximum: 100 } + */ + exclusiveMaximum?: number; + + /** + * Value must be an exact multiple of this number (supports fractional steppers like `0.25`). + * + * @example + * { type: JsonSchemaType.Number, multipleOf: 0.5 } + * + * @example + * { type: JsonSchemaType.Number, multipleOf: 10 } + */ multipleOf?: number; - type: JsonSchemaType.Number; } +/** + * Schema for object values (`type: JsonSchemaType.Object`). + */ export interface JsonSchemaObject - extends JsonSchemaBase, - EntityParameters { - additionalProperties?: boolean | JsonSchema; - dependencies?: { - [key: string]: string[] | JsonSchemaObject; - }; - maxProperties?: number; - minProperties?: number; - patternProperties?: { - [key: string]: JsonSchema; - }; - properties?: { - [key: string]: JsonSchema; - }; + extends JsonSchemaBase { + /** + * Marks this schema as describing an object value. + * + * @example + * { type: JsonSchemaType.Object, properties: { name: { type: JsonSchemaType.String } } } + */ + type: JsonSchemaType.Object; + + /** + * Map of property name to schema; defines the validated sub-schema for each named property. + * + * @example + * { type: JsonSchemaType.Object, properties: { name: { type: JsonSchemaType.String }, age: { type: JsonSchemaType.Number } } } + */ + properties?: {[key: string]: JsonSchema}; + + /** + * Map of regular expression to schema; properties whose name matches a pattern must satisfy the corresponding schema. + * + * @example + * { type: JsonSchemaType.Object, patternProperties: { '^x-': { type: JsonSchemaType.String } } } + */ + patternProperties?: {[key: string]: JsonSchema}; + + /** + * Schema applied to properties not listed in `properties` and not matched by `patternProperties`. + * + * @example + * { type: JsonSchemaType.Object, properties: { name: { type: JsonSchemaType.String } }, additionalProperties: { type: JsonSchemaType.Number } } + */ + additionalProperties?: JsonSchema; + + /** + * Schema applied to properties not already validated by `properties`, `patternProperties`, or `additionalProperties` (draft 2019-09+). + * + * @example + * { type: JsonSchemaType.Object, properties: { name: { type: JsonSchemaType.String } }, unevaluatedProperties: { type: JsonSchemaType.Number } } + */ + unevaluatedProperties?: JsonSchema; + + /** + * Schema each property name must satisfy (typically a `type: string` schema constraining length or pattern). + * + * @example + * { type: JsonSchemaType.Object, propertyNames: { type: JsonSchemaType.String, pattern: '^[a-z][a-zA-Z0-9]*$' } } + */ propertyNames?: JsonSchema; + + /** + * Names of properties that must be present on the object value. + * + * @example + * { type: JsonSchemaType.Object, properties: { name: { type: JsonSchemaType.String } }, required: ['name'] } + */ required?: string[]; - type: JsonSchemaType.Object; + + /** + * Minimum number of properties the object must declare. + * + * @example + * { type: JsonSchemaType.Object, minProperties: 1 } + */ + minProperties?: number; + + /** + * Maximum number of properties the object may declare. + * + * @example + * { type: JsonSchemaType.Object, maxProperties: 5 } + */ + maxProperties?: number; + + /** + * Property-level dependencies: when a key is present, either the listed property names must also be present, or the given sub-schema must hold for the whole object (draft-07 keyword). + * + * @example + * { type: JsonSchemaType.Object, dependencies: { credit_card: ['billing_address'] } } + * + * @example + * { type: JsonSchemaType.Object, dependencies: { credit_card: { type: JsonSchemaType.Object, required: ['billing_address'] } } } + */ + dependencies?: {[key: string]: string[] | JsonSchema}; + + /** + * When the keyed property is present, the listed property names are also required (draft 2019-09+; replaces the array form of `dependencies`). + * + * @example + * { type: JsonSchemaType.Object, dependentRequired: { credit_card: ['billing_address'] } } + */ + dependentRequired?: {[key: string]: string[]}; + + /** + * When the keyed property is present, the additional sub-schema must hold for the whole object (draft 2019-09+; replaces the schema form of `dependencies`). + * + * @example + * { type: JsonSchemaType.Object, dependentSchemas: { credit_card: { type: JsonSchemaType.Object, required: ['billing_address'] } } } + */ + dependentSchemas?: {[key: string]: JsonSchema}; } +/** + * Schema for string values (`type: JsonSchemaType.String`). + */ export interface JsonSchemaString - extends JsonSchemaBase, - EntityParameters { - maxLength?: number; + extends JsonSchemaBase { + /** + * Marks this schema as describing a string value. + * + * @example + * { type: JsonSchemaType.String, minLength: 1 } + */ + type: JsonSchemaType.String; + + /** + * Shortest allowed length (in characters) of the string value. + * + * @example + * { type: JsonSchemaType.String, minLength: 5 } + */ minLength?: number; + + /** + * Longest allowed length (in characters) of the string value. + * + * @example + * { type: JsonSchemaType.String, maxLength: 100 } + */ + maxLength?: number; + + /** + * Regular expression the string value must match. + * + * @example + * { type: JsonSchemaType.String, pattern: '^[A-Z][a-z]+$' } + * + * @example + * { type: JsonSchemaType.String, pattern: '[0-9]' } + */ pattern?: string; - type: JsonSchemaType.String; + + /** + * Semantic format the string value must conform to (e.g. `email`, `date`, `uri`); enforcement depends on the configured validator. + * + * @example + * { type: JsonSchemaType.String, format: 'email' } + * + * @example + * { type: JsonSchemaType.String, format: 'date-time' } + */ + format?: string; + + /** + * Encoding used by the string value (e.g. `base64`); enables the parsed content to be validated by `contentSchema`. + * + * @example + * { type: JsonSchemaType.String, contentEncoding: 'base64', contentMediaType: 'image/png' } + */ + contentEncoding?: string; + + /** + * MIME type of the parsed string content; pairs with `contentEncoding` and `contentSchema`. + * + * @example + * { type: JsonSchemaType.String, contentMediaType: 'application/json' } + */ + contentMediaType?: string; + + /** + * Sub-schema validating the parsed content of a string described by `contentMediaType` / `contentEncoding`. + * + * @example + * { type: JsonSchemaType.String, contentMediaType: 'application/json', contentSchema: { type: JsonSchemaType.Object } } + */ + contentSchema?: JsonSchema; } -export type JsonSchema = - | JsonSchemaArray - | JsonSchemaBoolean - | JsonSchemaNumber - | JsonSchemaObject - | JsonSchemaString; +/** + * Discriminated union of every supported JSON Schema variant. Narrow with the `type` keyword to access type-specific fields. + * + * @example + * const schema: JsonSchema = { type: JsonSchemaType.String, minLength: 1 }; + * + * @example + * const schema: JsonSchema = { type: JsonSchemaType.Object, properties: { id: { type: JsonSchemaType.Number } } }; + */ +export type JsonSchema = + | JsonSchemaAny + | JsonSchemaArray + | JsonSchemaBoolean + | JsonSchemaNumber + | JsonSchemaObject + | JsonSchemaString; diff --git a/src/lib/unstable/core/useSchemaRenderer/constants.ts b/src/lib/unstable/core/useSchemaRenderer/constants.ts new file mode 100644 index 00000000..0215fc69 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/constants.ts @@ -0,0 +1 @@ +export const ENTITY_SERVICE_FIELD = 'ENTITY_SERVICE_FIELD'; diff --git a/src/lib/unstable/core/useSchemaRenderer/hooks/index.ts b/src/lib/unstable/core/useSchemaRenderer/hooks/index.ts new file mode 100644 index 00000000..3e46dbbb --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/hooks/index.ts @@ -0,0 +1,2 @@ +export {useEntityState} from './useEntityState'; +export {useSchemaRendererMutators} from './useSchemaRendererMutators'; diff --git a/src/lib/unstable/core/useSchemaRenderer/hooks/useEntityState.ts b/src/lib/unstable/core/useSchemaRenderer/hooks/useEntityState.ts new file mode 100644 index 00000000..a6d0b210 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/hooks/useEntityState.ts @@ -0,0 +1,24 @@ +import React from 'react'; + +import {useField} from 'react-final-form'; + +import {ENTITY_SERVICE_FIELD} from '../constants'; +import type {EntityState} from '../types'; + +export const useEntityState = (name: string) => { + const field = useField(ENTITY_SERVICE_FIELD, {subscription: {data: true}}); + + const {arrayAndObjectErrors, config, mode} = (field.meta.data || {}) as EntityState; + const error = arrayAndObjectErrors?.[name]; + + const state = React.useMemo( + () => ({ + config, + error, + mode, + }), + [config, error, mode], + ); + + return state; +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/hooks/useSchemaRendererMutators.ts b/src/lib/unstable/core/useSchemaRenderer/hooks/useSchemaRendererMutators.ts new file mode 100644 index 00000000..265d179c --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/hooks/useSchemaRendererMutators.ts @@ -0,0 +1,41 @@ +import {useForm} from 'react-final-form'; + +import type { + RemoveExternalErrorsMutator, + RemoveSchemaMutatorsMutator, + SetArrayObjectErrorsMutator, + SetAsyncValidationCacheMutator, + SetAsyncValidationWaitersMutator, + SetExternalErrorsMutator, + SetSchemaMutatorsMutator, +} from '../mutators'; + +export const useSchemaRendererMutators = () => { + const { + removeExternalErrors, + removeSchemaMutators, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, + setExternalErrors, + setSchemaMutators, + } = useForm().mutators as { + removeExternalErrors: RemoveExternalErrorsMutator | undefined; + removeSchemaMutators: RemoveSchemaMutatorsMutator | undefined; + setArrayObjectErrors: SetArrayObjectErrorsMutator | undefined; + setAsyncValidationCache: SetAsyncValidationCacheMutator | undefined; + setAsyncValidationWaiters: SetAsyncValidationWaitersMutator | undefined; + setExternalErrors: SetExternalErrorsMutator | undefined; + setSchemaMutators: SetSchemaMutatorsMutator | undefined; + }; + + return { + removeExternalErrors, + removeSchemaMutators, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, + setExternalErrors, + setSchemaMutators, + }; +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/index.ts b/src/lib/unstable/core/useSchemaRenderer/index.ts new file mode 100644 index 00000000..2b48ebf7 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/index.ts @@ -0,0 +1,7 @@ +export {useEntityState, useSchemaRendererMutators} from './hooks'; +export {schemaRendererMutators} from './mutators'; +export { + type UseSchemaRendererParams, + type UseSchemaRendererReturn, + useSchemaRenderer, +} from './useSchemaRenderer'; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/array-object-errors.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/array-object-errors.ts new file mode 100644 index 00000000..539b67aa --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/array-object-errors.ts @@ -0,0 +1,22 @@ +import {ENTITY_SERVICE_FIELD} from '../../constants'; + +import type {SetArrayObjectErrorsFunction} from './types'; + +export const setArrayObjectErrors: SetArrayObjectErrorsFunction = ( + [{headName, arrayAndObjectErrors}], + mutableState, +) => { + const entityField = mutableState.fields[ENTITY_SERVICE_FIELD]; + const field = mutableState.fields[headName]; + + if (field && entityField) { + field.data = { + ...field.data, + arrayAndObjectErrors, + }; + entityField.data = { + ...entityField.data, + arrayAndObjectErrors, + }; + } +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/index.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/index.ts new file mode 100644 index 00000000..a263b5f1 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/index.ts @@ -0,0 +1,2 @@ +export * from './array-object-errors'; +export * from './types'; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/types.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/types.ts new file mode 100644 index 00000000..39324790 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/array-object-errors/types.ts @@ -0,0 +1,23 @@ +import type {MutableState, Tools} from 'final-form'; + +import type {SyncValidateError} from '../../../types'; + +export interface ArrayObjectErrorsState { + arrayAndObjectErrors?: {[key: string]: SyncValidateError}; +} + +export interface SetArrayObjectErrorsParams { + headName: string; + arrayAndObjectErrors: {[key: string]: SyncValidateError}; +} + +export type SetArrayObjectErrorsFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetArrayObjectErrorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetArrayObjectErrorsMutator = (params: SetArrayObjectErrorsParams) => void; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/async-validation.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/async-validation.ts new file mode 100644 index 00000000..69879478 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/async-validation.ts @@ -0,0 +1,56 @@ +import isEqual from 'lodash/isEqual'; +import omit from 'lodash/omit'; + +import type {SetAsyncValidationCacheFunction, SetAsyncValidationWaitersFunction} from './types'; + +export const setAsyncValidationWaiters: SetAsyncValidationWaitersFunction = ( + [{headName, waiters}], + mutableState, +) => { + const field = mutableState.fields[headName]; + + if (field && waiters) { + Object.keys(waiters).forEach((waiterName) => { + field.data = { + ...field.data, + waiters: { + ...field.data.waiters, + [waiterName]: waiters[waiterName], + }, + }; + + const waiterField = mutableState.fields[waiterName]; + + if (waiterField) { + waiterField.validating = true; + } + }); + } +}; + +export const setAsyncValidationCache: SetAsyncValidationCacheFunction = ( + [{headName, cache}], + mutableState, +) => { + const field = mutableState.fields[headName]; + + if (field && cache) { + Object.keys(cache).forEach((cacheName) => { + field.data = { + ...field.data, + cache: { + ...field.data.cache, + [cacheName]: [...(field.data.cache?.[cacheName] || []), cache[cacheName]], + }, + }; + + const cacheField = mutableState.fields[cacheName]; + const waiter = field.data.waiters?.[cacheName]; + + if (cacheField && waiter && isEqual(waiter, omit(cache[cacheName], 'result'))) { + field.data.waiters = omit(field.data.waiters, cacheName); + cacheField.validating = false; + } + }); + } +}; diff --git a/src/lib/unstable/core/mutators/async-validation/index.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/index.ts similarity index 100% rename from src/lib/unstable/core/mutators/async-validation/index.ts rename to src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/index.ts diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/types.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/types.ts new file mode 100644 index 00000000..fc42461f --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/async-validation/types.ts @@ -0,0 +1,58 @@ +import type {MutableState, Tools} from 'final-form'; + +import type {FieldValue, JsonSchema, SyncValidateError, Validator} from '../../../types'; + +export interface AsyncValidationWaiter { + schema: JsonSchema; + validator: Validator; + value: FieldValue | null | undefined; +} + +export interface AsyncValidationCache extends AsyncValidationWaiter { + result: SyncValidateError; +} + +export interface AsyncValidationState { + cache?: { + [key: string]: AsyncValidationCache[] | undefined; + }; + waiters?: { + [key: string]: AsyncValidationWaiter | undefined; + }; +} + +export interface SetAsyncValidationWaitersParams { + headName: string; + waiters: { + [key: string]: AsyncValidationWaiter; + }; +} + +export type SetAsyncValidationWaitersFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetAsyncValidationWaitersParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetAsyncValidationWaitersMutator = (params: SetAsyncValidationWaitersParams) => void; + +export interface SetAsyncValidationCacheParams { + cache: { + [key: string]: AsyncValidationCache; + }; + headName: string; +} + +export type SetAsyncValidationCacheFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetAsyncValidationCacheParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetAsyncValidationCacheMutator = (params: SetAsyncValidationCacheParams) => void; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/external-errors.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/external-errors.ts new file mode 100644 index 00000000..a36bd1ed --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/external-errors.ts @@ -0,0 +1,49 @@ +import omit from 'lodash/omit'; + +import type {RemoveExternalErrorsFunction, SetExternalErrorsFunction} from './types'; + +export const setExternalErrors: SetExternalErrorsFunction = ( + [{headName, priorityErrors, regularErrors}], + mutableState, +) => { + const field = mutableState.fields[headName]; + + if (field && (priorityErrors || regularErrors)) { + field.data = { + ...field.data, + priorityErrors: { + ...field.data.priorityErrors, + ...(priorityErrors || {}), + }, + regularErrors: { + ...field.data.regularErrors, + ...(regularErrors || {}), + }, + }; + } +}; + +export const removeExternalErrors: RemoveExternalErrorsFunction = ( + [{headName, removeFunctionOrNames}], + mutableState, +) => { + const field = mutableState.fields[headName]; + + if (field && removeFunctionOrNames) { + const {priorityErrors, regularErrors} = Array.isArray(removeFunctionOrNames) + ? { + priorityErrors: omit({...field.data.priorityErrors}, removeFunctionOrNames), + regularErrors: omit({...field.data.regularErrors}, removeFunctionOrNames), + } + : removeFunctionOrNames({ + priorityErrors: field.data.priorityErrors, + regularErrors: field.data.regularErrors, + }); + + field.data = { + ...field.data, + priorityErrors, + regularErrors, + }; + } +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/index.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/index.ts new file mode 100644 index 00000000..d7634bad --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/index.ts @@ -0,0 +1,2 @@ +export * from './external-errors'; +export * from './types'; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/types.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/types.ts new file mode 100644 index 00000000..66d3bb6e --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/external-errors/types.ts @@ -0,0 +1,49 @@ +import type {MutableState, Tools} from 'final-form'; + +import type {SyncValidateError} from '../../../types'; + +export interface ExternalErrorsState { + priorityErrors?: { + [key: string]: SyncValidateError; + }; + regularErrors?: { + [key: string]: SyncValidateError; + }; +} + +export interface SetExternalErrorsParams { + headName: string; + priorityErrors?: { + [key: string]: SyncValidateError; + }; + regularErrors?: { + [key: string]: SyncValidateError; + }; +} + +export type SetExternalErrorsFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetExternalErrorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetExternalErrorsMutator = (params: SetExternalErrorsParams) => void; + +export interface RemoveExternalErrorsParams { + headName: string; + removeFunctionOrNames: string[] | ((state: ExternalErrorsState) => ExternalErrorsState); +} + +export type RemoveExternalErrorsFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [RemoveExternalErrorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type RemoveExternalErrorsMutator = (params: RemoveExternalErrorsParams) => void; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/index.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/index.ts new file mode 100644 index 00000000..7e166528 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/index.ts @@ -0,0 +1,19 @@ +import {setArrayObjectErrors} from './array-object-errors'; +import {setAsyncValidationCache, setAsyncValidationWaiters} from './async-validation'; +import {removeExternalErrors, setExternalErrors} from './external-errors'; +import {removeSchemaMutators, setSchemaMutators} from './schema-mutators'; + +export type * from './array-object-errors'; +export type * from './async-validation'; +export type * from './external-errors'; +export type * from './schema-mutators'; + +export const schemaRendererMutators = { + removeExternalErrors, + removeSchemaMutators, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, + setExternalErrors, + setSchemaMutators, +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/index.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/index.ts new file mode 100644 index 00000000..bfe39df4 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/index.ts @@ -0,0 +1,2 @@ +export * from './schema-mutators'; +export * from './types'; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/schema-mutators.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/schema-mutators.ts new file mode 100644 index 00000000..822aed17 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/schema-mutators.ts @@ -0,0 +1,120 @@ +import cloneDeep from 'lodash/cloneDeep'; +import get from 'lodash/get'; +import isEqual from 'lodash/isEqual'; +import merge from 'lodash/merge'; +import set from 'lodash/set'; + +import {getSchemaPath} from '../../utils'; + +import type { + RemoveSchemaMutatorsFunction, + SchemaMutatorsState, + SetSchemaMutatorsFunction, +} from './types'; + +export const setSchemaMutators: SetSchemaMutatorsFunction = ( + [{headName, mutators: mutatorsParams}], + mutableState, +) => { + const field = mutableState.fields[headName]; + const data = field?.data as SchemaMutatorsState | undefined; + + if (field && data?.schema && Object.keys(mutatorsParams).length) { + const mutatedSchema = {...data.schema}; + const mutators = [...(data.mutators || []), ...mutatorsParams]; + + mutatorsParams.forEach(({name, schema}) => { + const schemaPath = getSchemaPath(name, headName, mutatedSchema); + + if (schemaPath) { + set(mutatedSchema, schemaPath, merge({...get(mutatedSchema, schemaPath)}, schema)); + + const schemaField = mutableState.fields[name]; + + if (schemaField) { + schemaField.data = { + ...schemaField.data, + schema: get(mutatedSchema, schemaPath), + }; + } + } + }); + + field.data = { + ...field.data, + mutators, + schema: mutatedSchema, + }; + } +}; + +export const removeSchemaMutators: RemoveSchemaMutatorsFunction = ( + [{headName, mutatorsToRemove}], + mutableState, +) => { + const field = mutableState.fields[headName]; + const data = field?.data as SchemaMutatorsState | undefined; + + if ( + field && + data?.schema && + data?.originalSchema && + data.mutators && + Object.keys(mutatorsToRemove).length + ) { + const originalSchema = data.originalSchema; + const mutatedSchema = {...data.schema}; + const mutators = (data.mutators || []).filter((m) => { + if ( + mutatorsToRemove.some( + ({name, schema}) => + name === m.name && (isEqual(m.schema, schema) || schema === true), + ) + ) { + return false; + } + + return true; + }); + + mutatorsToRemove.forEach(({name}) => { + const schemaPath = getSchemaPath(name, headName, originalSchema); + + if (schemaPath) { + set(mutatedSchema, schemaPath, cloneDeep(get(originalSchema, schemaPath))); + + const schemaField = mutableState.fields[name]; + + if (schemaField) { + schemaField.data = { + ...schemaField.data, + schema: get(mutatedSchema, schemaPath), + }; + } + } + }); + + mutators.forEach(({name, schema}) => { + const schemaPath = getSchemaPath(name, headName, mutatedSchema); + + if (schemaPath) { + set(mutatedSchema, schemaPath, merge({...get(mutatedSchema, schemaPath)}, schema)); + + const schemaField = mutableState.fields[name]; + + if (schemaField) { + schemaField.data = { + ...schemaField.data, + schema: get(mutatedSchema, schemaPath), + }; + } + } + }); + + field.data = { + ...field.data, + mutators, + schema: mutatedSchema, + }; + } +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/types.ts b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/types.ts new file mode 100644 index 00000000..64cf3dd9 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/mutators/schema-mutators/types.ts @@ -0,0 +1,41 @@ +import type {MutableState, Tools} from 'final-form'; + +import type {JsonSchema} from '../../../types'; + +export interface SchemaMutatorsState { + mutators?: {name: string; schema: JsonSchema}[]; + originalSchema?: JsonSchema; + schema?: JsonSchema; +} + +export interface SetSchemaMutatorsParams { + headName: string; + mutators: {name: string; schema: JsonSchema}[]; +} + +export type SetSchemaMutatorsFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [SetSchemaMutatorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type SetSchemaMutatorsMutator = (params: SetSchemaMutatorsParams) => void; + +export interface RemoveSchemaMutatorsParams { + headName: string; + mutatorsToRemove: {name: string; schema: JsonSchema | true}[]; +} + +export type RemoveSchemaMutatorsFunction< + FormValues = object, + InitialFormValues = Partial, +> = ( + args: [RemoveSchemaMutatorsParams], + state: MutableState, + tools: Tools, +) => void; + +export type RemoveSchemaMutatorsMutator = (params: RemoveSchemaMutatorsParams) => void; diff --git a/src/lib/unstable/core/useSchemaRenderer/types.ts b/src/lib/unstable/core/useSchemaRenderer/types.ts new file mode 100644 index 00000000..f4817a57 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/types.ts @@ -0,0 +1,42 @@ +import type {ErrorObject} from 'ajv'; + +import type {SchemaRendererMode} from '../constants'; +import type { + FieldValue, + JsonSchema, + SchemaRendererConfig, + SyncValidateError, + Validator, +} from '../types'; + +import type { + ArrayObjectErrorsState, + AsyncValidationState, + ExternalErrorsState, + SchemaMutatorsState, +} from './mutators'; + +export type EntityParametersError = ErrorObject< + 'entityParameters', + { + schema: JsonSchema; + validator: Validator; + value: FieldValue | null | undefined; + } +>; + +export interface ValidateErrorItem { + error: SyncValidateError; + path: string[]; +} + +export interface EntityState extends ArrayObjectErrorsState { + config?: SchemaRendererConfig; + mode?: SchemaRendererMode; +} + +export interface SchemaRendererState + extends ArrayObjectErrorsState, + AsyncValidationState, + ExternalErrorsState, + SchemaMutatorsState {} diff --git a/src/lib/unstable/core/useSchemaRenderer/useSchemaRenderer.ts b/src/lib/unstable/core/useSchemaRenderer/useSchemaRenderer.ts new file mode 100644 index 00000000..6f0a4ed5 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/useSchemaRenderer.ts @@ -0,0 +1,121 @@ +import React from 'react'; + +import type {FieldSubscriber, FieldValidator} from 'final-form'; +import cloneDeep from 'lodash/cloneDeep'; +import {useForm} from 'react-final-form'; + +import {EMPTY_OBJECT, type SchemaRendererMode} from '../constants'; +import type {ErrorMessages, FieldValue, JsonSchema, SchemaRendererConfig} from '../types'; + +import {ENTITY_SERVICE_FIELD} from './constants'; +import {useSchemaRendererMutators} from './hooks'; +import type {SchemaRendererState} from './types'; +import {getValidate} from './utils'; + +export interface UseSchemaRendererParams { + config: SchemaRendererConfig; + connectValidate?: boolean; + errorMessages?: ErrorMessages; + mode: SchemaRendererMode; + /** + * The `name` prop must be a non-empty string. + * + * In `final-form` and `react-final-form`, the `name` is used as a key to register + * the field within the form. If you pass an empty string (`name=""`), the field will + * not be registered, its value will not be tracked, and validation will not work. + * + * This can lead to: + * - the field being missing from the form `values`; + * - the `validate` function not being called; + * - no error or touched state updates; + * - inconsistent or broken form behavior. + * + * Always provide a unique, non-empty string for the field name. + */ + name: string; + schema: JsonSchema; +} + +export type UseSchemaRendererReturn = { + schema: JsonSchema | undefined; + validate: FieldValidator | undefined; +}; + +export const useSchemaRenderer = ({ + config, + connectValidate = true, + errorMessages = EMPTY_OBJECT, + mode, + name, + schema: originalSchema, +}: UseSchemaRendererParams) => { + const form = useForm(); + const {setArrayObjectErrors, setAsyncValidationCache, setAsyncValidationWaiters} = + useSchemaRendererMutators(); + + const schemaRef = React.useRef(undefined); + const [schema, setSchema] = React.useState(undefined); + + const validate = React.useMemo( + () => + getValidate({ + config, + errorMessages, + name, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, + }), + [ + config, + errorMessages, + name, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, + ], + ); + + const returnValue = React.useMemo(() => { + if (connectValidate) { + return {schema}; + } + + return {schema, validate}; + }, [connectValidate, schema, validate]); + + React.useEffect(() => { + const subscriber: FieldSubscriber = (fieldState) => { + const data = fieldState.data as SchemaRendererState; + + if (data.schema !== schemaRef.current) { + schemaRef.current = data.schema; + setSchema(data.schema); + } + }; + const data = {originalSchema, schema: cloneDeep(originalSchema)}; + const getValidator = connectValidate ? () => validate : undefined; + + const unsubscribe = form.registerField( + name, + subscriber, + {data: true}, + {data, getValidator}, + ); + + return () => unsubscribe(); + }, [connectValidate, name, originalSchema, validate]); + + React.useEffect(() => { + const unsubscribe = form.registerField( + ENTITY_SERVICE_FIELD, + () => {}, + {}, + {data: {config, mode}}, + ); + + return () => unsubscribe(); + }, [config, mode]); + + return returnValue; +}; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/common.ts b/src/lib/unstable/core/useSchemaRenderer/utils/common.ts similarity index 53% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/common.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/common.ts index e5c89125..96acaea6 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/common.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/common.ts @@ -1,6 +1,5 @@ import get from 'lodash/get'; -import {JsonSchemaType} from '../../constants'; import type {JsonSchema} from '../../types'; /** @@ -32,6 +31,70 @@ export const parseInstancePath = (instancePath: string): string[] => { .map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~')); }; +export const parseFinalFormName = (finalFormName: string): string[] => { + const result: string[] = []; + const regex = /([^[.\]]+)|\[(\d+)\]/g; + let match; + + while ((match = regex.exec(finalFormName)) !== null) { + if (match[1] !== undefined) { + result.push(match[1]); + } else if (match[2] !== undefined) { + result.push(match[2]); + } + } + + return result; +}; + +export const formatFinalFormPath = (finalFormPath: string[]): string => { + return finalFormPath.reduce((result, segment) => { + if (/^\d+$/.test(segment)) { + return `${result}[${segment}]`; + } + + return result ? `${result}.${segment}` : segment; + }, ''); +}; + +export const getSchemaPath = ( + finalFormNameOrPath: string | string[], + finalFormHeadName: string, + schema: JsonSchema, +): string[] | undefined => { + const finalFormPath = Array.isArray(finalFormNameOrPath) + ? finalFormNameOrPath + : parseFinalFormName(finalFormNameOrPath); + + const schemaPath = finalFormPath + .slice(parseFinalFormName(finalFormHeadName).length) + .reduce((path: string[] | undefined, segment, index) => { + if (path === undefined) { + return path; + } + + const schemaByPath: JsonSchema | undefined = index === 0 ? schema : get(schema, path); + + if (get(schemaByPath, ['properties', segment])) { + return [...path, 'properties', segment]; + } + + const items = get(schemaByPath, 'items'); + + if (items) { + if (Array.isArray(items)) { + return [...path, 'items', segment]; + } + + return [...path, 'items']; + } + + return undefined; + }, []); + + return schemaPath; +}; + /** * Retrieves the sub-schema from the main schema based on the given schema path. * Assumes that the last segment in the `schemaPath` is a validation keyword @@ -39,7 +102,7 @@ export const parseInstancePath = (instancePath: string): string[] => { * * @param schemaPath - A JSON Pointer-style string representing the schema path, * e.g., "#/properties/name/minLength". - * @param mainSchema - The root JSON schema object. + * @param schema - The root JSON schema object. * * @example * const nameSchema = { @@ -58,42 +121,42 @@ export const parseInstancePath = (instancePath: string): string[] => { */ export const getSchemaBySchemaPath = ( schemaPath: string, - mainSchema: JsonSchema, + schema: JsonSchema, ): JsonSchema | undefined => { const pathArr = parseSchemaPath(schemaPath); if (!pathArr.length) { - return mainSchema; + return schema; } - return get(mainSchema, pathArr); + return get(schema, pathArr); }; export const getSchemaByInstancePath = ( instancePath: string, - mainSchema: JsonSchema, + schema: JsonSchema, ): JsonSchema | undefined => { - if (instancePath.length) { - return parseInstancePath(instancePath).reduce((acc: JsonSchema | undefined, segment) => { - const type = get(acc, 'type'); + const schemaPath = getSchemaPath(instancePath, '', schema); - if (type === JsonSchemaType.Object) { - return get(acc, `properties.${segment}`); - } else if (type === JsonSchemaType.Array) { - const items = get(acc, 'items'); + if (schemaPath) { + return schemaPath.length ? get(schema, schemaPath) : schema; + } - if (Array.isArray(items)) { - return get(items, `[${segment}]`); - } + return undefined; +}; - return items; - } +export const getSchemaByFinalFormPath = ( + finalFormNameOrPath: string | string[], + finalFormHeadName: string, + schema: JsonSchema, +): JsonSchema | undefined => { + const schemaPath = getSchemaPath(finalFormNameOrPath, finalFormHeadName, schema); - return undefined; - }, mainSchema); + if (schemaPath) { + return schemaPath.length ? get(schema, schemaPath) : schema; } - return mainSchema; + return undefined; }; export const getValuePaths = (value: unknown, path: string[] = []) => { diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/get-ajv-validate.ts b/src/lib/unstable/core/useSchemaRenderer/utils/get-ajv-validate.ts similarity index 66% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/get-ajv-validate.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/get-ajv-validate.ts index 33311f89..9674dd71 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/get-ajv-validate.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/get-ajv-validate.ts @@ -7,28 +7,28 @@ import type { } from 'ajv'; import get from 'lodash/get'; +import type {JsonSchemaType} from '../../constants'; import type {FieldValue, JsonSchema, SchemaRendererConfig, Validator} from '../../types'; +import {getSchemaType} from '../../utils'; import type {EntityParametersError} from '../types'; -interface GetAjvValidateParams { +export interface GetAjvValidateParams { config: SchemaRendererConfig; - mainSchema: JsonSchema; + schema: JsonSchema; } -interface GetAjvValidateReturn extends ValidateFunction { +export interface GetAjvValidateReturn extends ValidateFunction { errors?: (ErrorObject | EntityParametersError)[]; } -export const getAjvValidate = ({ - config, - mainSchema, -}: GetAjvValidateParams): GetAjvValidateReturn => { +export const getAjvValidate = ({config, schema}: GetAjvValidateParams): GetAjvValidateReturn => { function entityParametersValidate(_: unknown, value: FieldValue, schema?: JsonSchema) { - if (schema) { - const validatorType: string | undefined = get(schema, 'entityParameters.validatorType'); + if (schema && schema.entityParameters) { + const schemaType: JsonSchemaType = getSchemaType(schema); + const validatorType: string | undefined = schema.entityParameters.validatorType; const validator: Validator | undefined = get( config, - `${schema.type}.validators.${validatorType}`, + `${schemaType}.validators.${validatorType}`, ); if (validator) { @@ -50,6 +50,7 @@ export const getAjvValidate = ({ const ajv = new Ajv({ allErrors: true, allowMatchingProperties: true, + coerceTypes: true, keywords: [ { errors: true, @@ -58,7 +59,7 @@ export const getAjvValidate = ({ }, ], }); - const ajvValidate = ajv.compile(mainSchema) as GetAjvValidateReturn; + const ajvValidate = ajv.compile(schema) as GetAjvValidateReturn; return ajvValidate; }; diff --git a/src/lib/unstable/core/useSchemaRenderer/utils/get-validate.ts b/src/lib/unstable/core/useSchemaRenderer/utils/get-validate.ts new file mode 100644 index 00000000..3e01e29a --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/utils/get-validate.ts @@ -0,0 +1,143 @@ +import {type FieldValidator} from 'final-form'; +import get from 'lodash/get'; +import isBoolean from 'lodash/isBoolean'; +import isEqual from 'lodash/isEqual'; +import isObjectLike from 'lodash/isObjectLike'; +import isString from 'lodash/isString'; +import set from 'lodash/set'; + +import {JsonSchemaType} from '../../constants'; +import type { + ErrorMessages, + FieldValue, + JsonSchema, + ObjectValue, + SchemaRendererConfig, + SyncValidateError, +} from '../../types'; +import type { + SetArrayObjectErrorsMutator, + SetAsyncValidationCacheMutator, + SetAsyncValidationWaitersMutator, +} from '../mutators'; +import type {SchemaRendererState} from '../types'; + +import { + formatFinalFormPath, + getSchemaByFinalFormPath, + getValuePaths, + parseFinalFormName, +} from './common'; +import {type GetAjvValidateReturn, getAjvValidate} from './get-ajv-validate'; +import {processAjvValidateErrors} from './process-ajv-validate-errors'; +import {processErrorsState} from './process-errors-state'; + +type GetValidateParams = { + config: SchemaRendererConfig; + errorMessages: ErrorMessages; + name: string; + setArrayObjectErrors?: SetArrayObjectErrorsMutator; + setAsyncValidationCache?: SetAsyncValidationCacheMutator; + setAsyncValidationWaiters?: SetAsyncValidationWaitersMutator; +}; + +type GetValidateReturn = FieldValidator; + +export const getValidate = ({ + config, + errorMessages, + name, + setArrayObjectErrors, + setAsyncValidationCache, + setAsyncValidationWaiters, +}: GetValidateParams): GetValidateReturn => { + let schema: JsonSchema; + let ajvValidate: GetAjvValidateReturn; + + return (value, allValues, meta) => { + const data = meta?.data as SchemaRendererState | undefined; + + if (!data?.schema) { + return false; + } + + if (schema !== data.schema) { + schema = data.schema; + ajvValidate = getAjvValidate({config, schema}); + } + + ajvValidate(value); + + const {ajvErrorItems, entityParametersErrorItems, waiters} = processAjvValidateErrors({ + ajvValidateErrors: ajvValidate.errors || [], + allValues: allValues as ObjectValue, + errorMessages, + name, + schema, + setAsyncValidationCache, + validationState: data, + }); + const {externalPriorityErrorItems, externalRegularErrorItems} = processErrorsState({ + errorsState: data, + name, + }); + + if (Object.keys(waiters).length) { + setAsyncValidationWaiters?.({headName: name, waiters}); + } + + const result: { + error: SyncValidateError; + arrayAndObjectErrors: Record; + } = {error: false, arrayAndObjectErrors: {}}; + + [ + ...externalRegularErrorItems, + ...ajvErrorItems, + ...entityParametersErrorItems, + ...externalPriorityErrorItems, + ].forEach((item) => { + if (!item.error) { + return; + } + + const setError = (path: string[], error: SyncValidateError) => { + const itemSchema = getSchemaByFinalFormPath(path, '', schema); + + if ( + itemSchema?.type === JsonSchemaType.Array || + itemSchema?.type === JsonSchemaType.Object + ) { + result.arrayAndObjectErrors[ + formatFinalFormPath([...parseFinalFormName(name), ...path]) + ] = error; + } else { + set(result, ['error', ...path], error); + } + }; + + if (isObjectLike(item.error)) { + getValuePaths(item.error).forEach((childPath) => { + setError([...item.path, ...childPath], get(item.error, childPath)); + }); + + return; + } + + if (isBoolean(item.error) || isString(item.error)) { + setError(item.path, item.error); + + return; + } + }); + + if (!isEqual(result.arrayAndObjectErrors, data.arrayAndObjectErrors)) { + setArrayObjectErrors?.({ + headName: name, + arrayAndObjectErrors: result.arrayAndObjectErrors, + }); + } + + return result.error; + }; +}; diff --git a/src/lib/unstable/core/useSchemaRenderer/utils/index.ts b/src/lib/unstable/core/useSchemaRenderer/utils/index.ts new file mode 100644 index 00000000..c0b3fbe9 --- /dev/null +++ b/src/lib/unstable/core/useSchemaRenderer/utils/index.ts @@ -0,0 +1,2 @@ +export {getValidate} from './get-validate'; +export {getSchemaPath} from './common'; diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-error.ts b/src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-error.ts similarity index 81% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-error.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-error.ts index 8c90ba38..dd0f16a4 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-error.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-error.ts @@ -2,26 +2,22 @@ import type {ErrorObject} from 'ajv'; import get from 'lodash/get'; import isString from 'lodash/isString'; -import {EMPTY_OBJECT} from '../../constants'; import type {ErrorMessages, JsonSchema} from '../../types'; -import {parseFinalFormPath} from '../../utils'; import type {ValidateErrorItem} from '../types'; import {getSchemaByInstancePath, getSchemaBySchemaPath, parseInstancePath} from './common'; interface ProcessAjvErrorParams { error: ErrorObject; - errorMessages: ErrorMessages | undefined; - headName: string; - mainSchema: Schema; + errorMessages: ErrorMessages; + schema: Schema; onError: (error: ValidateErrorItem) => void; } export const processAjvError = ({ error, - errorMessages = EMPTY_OBJECT, - headName, - mainSchema, + errorMessages, + schema, onError, }: ProcessAjvErrorParams) => { let instancePath = error.instancePath; @@ -50,10 +46,10 @@ export const processAjvError = ({ }; onError({ - path: [...parseFinalFormPath(headName), ...parseInstancePath(instancePath)], + path: parseInstancePath(instancePath), error: - getErrorMessageBySchema(getSchemaBySchemaPath(schemaPath, mainSchema)) || - getErrorMessageBySchema(getSchemaByInstancePath(instancePath, mainSchema)) || + getErrorMessageBySchema(getSchemaBySchemaPath(schemaPath, schema)) || + getErrorMessageBySchema(getSchemaByInstancePath(instancePath, schema)) || errorMessages[keyword as keyof typeof errorMessages] || error.message, }); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-validate-errors.ts b/src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-validate-errors.ts similarity index 65% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-validate-errors.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-validate-errors.ts index ae29ac66..5f767a2f 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-ajv-validate-errors.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/process-ajv-validate-errors.ts @@ -1,7 +1,11 @@ import type {ErrorObject} from 'ajv'; -import type {SetValidationCacheMutator, ValidationState, ValidationWaiter} from '../../mutators'; import type {ErrorMessages, JsonSchema, ObjectValue} from '../../types'; +import type { + AsyncValidationState, + AsyncValidationWaiter, + SetAsyncValidationCacheMutator, +} from '../mutators'; import type {EntityParametersError, ValidateErrorItem} from '../types'; import {processAjvError} from './process-ajv-error'; @@ -10,65 +14,65 @@ import {processEntityParametersError} from './process-entity-parameters-error'; interface ProcessAjvValidateErrorsParams { ajvValidateErrors: (ErrorObject | EntityParametersError)[]; allValues: ObjectValue; - errorMessages: ErrorMessages | undefined; - headName: string; - mainSchema: Schema; - serviceFieldName: string; - setValidationCache: SetValidationCacheMutator; - validationState: ValidationState | undefined; + errorMessages: ErrorMessages; + name: string; + schema: Schema; + setAsyncValidationCache?: SetAsyncValidationCacheMutator; + validationState: AsyncValidationState | undefined; } interface ProcessAjvValidateErrorsReturn { ajvErrorItems: ValidateErrorItem[]; entityParametersErrorItems: ValidateErrorItem[]; - waiters: Record; + waiters: Record; } export const processAjvValidateErrors = ({ ajvValidateErrors, allValues, errorMessages, - headName, - mainSchema, - serviceFieldName, - setValidationCache, + name, + schema, + setAsyncValidationCache, validationState, }: ProcessAjvValidateErrorsParams): ProcessAjvValidateErrorsReturn => { - const waiters: Record = {}; + const waiters: Record = {}; const ajvErrorItems: ValidateErrorItem[] = []; const entityParametersErrorItems: ValidateErrorItem[] = []; ajvValidateErrors.forEach((ajvOrEntityParametersError) => { if (ajvOrEntityParametersError.keyword === 'entityParameters') { + const error = ajvOrEntityParametersError as EntityParametersError; + processEntityParametersError({ - allValues: allValues, - error: ajvOrEntityParametersError as EntityParametersError, - headName, + allValues, + error, onAsyncError: (w) => { waiters[w.instancePath] = w.params; w.promise.then((result) => { - setValidationCache({ + setAsyncValidationCache?.({ + headName: name, cache: { [w.instancePath]: { ...w.params, result, }, }, - serviceFieldName, }); }); }, - onError: (err) => entityParametersErrorItems.push(err), + onError: (e) => entityParametersErrorItems.push(e), validationState, }); } else { + const error = ajvOrEntityParametersError as ErrorObject; + processAjvError({ - error: ajvOrEntityParametersError as ErrorObject, + error, errorMessages, - headName, - mainSchema, - onError: (err) => ajvErrorItems.push(err), + schema, + onError: (e) => ajvErrorItems.push(e), }); } }); diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-entity-parameters-error.ts b/src/lib/unstable/core/useSchemaRenderer/utils/process-entity-parameters-error.ts similarity index 80% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/process-entity-parameters-error.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/process-entity-parameters-error.ts index 7de625e1..8b0f815d 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-entity-parameters-error.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/process-entity-parameters-error.ts @@ -1,9 +1,8 @@ import isEqual from 'lodash/isEqual'; import omit from 'lodash/omit'; -import type {ValidationState} from '../../mutators'; import type {AsyncValidateError, ObjectValue} from '../../types'; -import {parseFinalFormPath} from '../../utils'; +import type {AsyncValidationState} from '../mutators'; import type {EntityParametersError, ValidateErrorItem} from '../types'; import {parseInstancePath} from './common'; @@ -11,20 +10,18 @@ import {parseInstancePath} from './common'; interface ProcessEntityParametersErrorParams { allValues: ObjectValue; error: EntityParametersError; - headName: string; onAsyncError: (waiter: { instancePath: string; params: EntityParametersError['params']; promise: AsyncValidateError; }) => void; onError: (error: ValidateErrorItem) => void; - validationState: ValidationState | undefined; + validationState: AsyncValidationState | undefined; } export const processEntityParametersError = ({ allValues, error, - headName, onAsyncError, onError, validationState, @@ -36,7 +33,7 @@ export const processEntityParametersError = ({ if (cacheItem?.result) { onError({ error: cacheItem.result, - path: [...parseFinalFormPath(headName), ...parseInstancePath(error.instancePath)], + path: parseInstancePath(error.instancePath), }); } else if (!waiter || !isEqual(error.params, waiter)) { const errorOrPromise = error.params.validator(error.params.value, allValues as ObjectValue); @@ -50,7 +47,7 @@ export const processEntityParametersError = ({ } else { onError({ error: errorOrPromise, - path: [...parseFinalFormPath(headName), ...parseInstancePath(error.instancePath)], + path: parseInstancePath(error.instancePath), }); } } diff --git a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-errors-state.ts b/src/lib/unstable/core/useSchemaRenderer/utils/process-errors-state.ts similarity index 61% rename from src/lib/unstable/core/SchemaRendererServiceField/utils/process-errors-state.ts rename to src/lib/unstable/core/useSchemaRenderer/utils/process-errors-state.ts index 1e17fac3..087fc1b5 100644 --- a/src/lib/unstable/core/SchemaRendererServiceField/utils/process-errors-state.ts +++ b/src/lib/unstable/core/useSchemaRenderer/utils/process-errors-state.ts @@ -1,11 +1,13 @@ import mapValues from 'lodash/mapValues'; -import type {ErrorsState} from '../../mutators'; -import {parseFinalFormPath} from '../../utils'; +import type {ExternalErrorsState} from '../mutators'; import type {ValidateErrorItem} from '../types'; +import {parseFinalFormName} from './common'; + interface ProcessErrorsStateParams { - errorsState: ErrorsState | undefined; + errorsState: ExternalErrorsState | undefined; + name: string; } interface ProcessErrorsStateReturn { @@ -15,11 +17,17 @@ interface ProcessErrorsStateReturn { export const processErrorsState = ({ errorsState, + name, }: ProcessErrorsStateParams): ProcessErrorsStateReturn => { - const getErrorItems = (errors: ErrorsState['priorityErrors'] | ErrorsState['regularErrors']) => + const getErrorItems = ( + errors: + | ExternalErrorsState['priorityErrors'] + | ExternalErrorsState['regularErrors'] + | undefined, + ) => Object.values( mapValues(errors, (value, key) => ({ - path: parseFinalFormPath(key), + path: parseFinalFormName(key).slice(parseFinalFormName(name).length), error: value, })), ); diff --git a/src/lib/unstable/core/useSchemaRendererField/index.ts b/src/lib/unstable/core/useSchemaRendererField/index.ts deleted file mode 100644 index 4a3e8504..00000000 --- a/src/lib/unstable/core/useSchemaRendererField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useSchemaRendererField'; diff --git a/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts b/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts deleted file mode 100644 index b1087fb1..00000000 --- a/src/lib/unstable/core/useSchemaRendererField/useSchemaRendererField.ts +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; - -import get from 'lodash/get'; -import {type FieldRenderProps, type UseFieldConfig, useField} from 'react-final-form'; - -import {SchemaRendererContext} from '../SchemaRendererContext'; -import {ARRAY_AND_OBJECT_ERRORS, JsonSchemaType} from '../constants'; -import {getSchemaByFinalFormPath} from '../utils'; - -export const useSchemaRendererField = < - FieldValue = any, - T extends HTMLElement = HTMLElement, - InputValue = FieldValue, ->( - name: string, - config?: UseFieldConfig, -): FieldRenderProps => { - const { - headName, - schema: mainSchema, - serviceFieldName, - } = React.useContext(SchemaRendererContext); - - const field = useField(name, config); - const serviceField = useField(serviceFieldName, {subscription: {error: true}}); - - const arrayOrObjectSchema = React.useMemo(() => { - const type = getSchemaByFinalFormPath(name, headName, mainSchema)?.type; - - return type === JsonSchemaType.Array || type === JsonSchemaType.Object; - }, [headName, name, mainSchema]); - - const error = arrayOrObjectSchema - ? get(serviceField.meta.error, [ARRAY_AND_OBJECT_ERRORS, name]) - : get(serviceField.meta.error, name); - - return error ? {...field, meta: {...field.meta, error}} : field; -}; diff --git a/src/lib/unstable/core/useSetErrors/index.ts b/src/lib/unstable/core/useSetErrors/index.ts deleted file mode 100644 index 1285d98c..00000000 --- a/src/lib/unstable/core/useSetErrors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './types'; -export * from './useSetErrors'; diff --git a/src/lib/unstable/core/useSetErrors/types.ts b/src/lib/unstable/core/useSetErrors/types.ts deleted file mode 100644 index 92ef4659..00000000 --- a/src/lib/unstable/core/useSetErrors/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type {RemoveErrorsParams, SetErrorsParams} from '../mutators'; - -export type SetErrors = (params: Omit) => void; - -export type RemoveErrors = (params: Omit) => void; - -export interface UseSetErrorsReturn { - removeErrors: RemoveErrors; - setErrors: SetErrors; -} diff --git a/src/lib/unstable/core/useSetErrors/useSetErrors.tsx b/src/lib/unstable/core/useSetErrors/useSetErrors.tsx deleted file mode 100644 index d6283235..00000000 --- a/src/lib/unstable/core/useSetErrors/useSetErrors.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; - -import {useForm} from 'react-final-form'; - -import {SchemaRendererContext} from '../SchemaRendererContext'; - -import type {RemoveErrors, SetErrors, UseSetErrorsReturn} from './types'; - -export const useSetErrors = (): UseSetErrorsReturn => { - const {serviceFieldName} = React.useContext(SchemaRendererContext); - const mutators = useForm().mutators; - - const result = React.useMemo(() => { - const setErrors: SetErrors = (params) => { - mutators.setErrors({ - ...params, - serviceFieldName, - }); - }; - - const removeErrors: RemoveErrors = (params) => { - mutators.removeErrors({ - ...params, - serviceFieldName, - }); - }; - - return {removeErrors, setErrors}; - }, [mutators.removeErrors, mutators.setErrors, serviceFieldName]); - - return result; -}; diff --git a/src/lib/unstable/core/utils.ts b/src/lib/unstable/core/utils.ts new file mode 100644 index 00000000..91dcc719 --- /dev/null +++ b/src/lib/unstable/core/utils.ts @@ -0,0 +1,6 @@ +import {JsonSchemaType} from './constants'; +import type {JsonSchema} from './types'; + +export const getSchemaType = (schema: JsonSchema): JsonSchemaType => { + return schema.type || JsonSchemaType.Any; +}; diff --git a/src/lib/unstable/core/utils/__tests__/common.test.ts b/src/lib/unstable/core/utils/__tests__/common.test.ts deleted file mode 100644 index 12e2acd3..00000000 --- a/src/lib/unstable/core/utils/__tests__/common.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import {JsonSchemaType} from '../../constants'; -import type { - JsonSchema, - JsonSchemaArray, - JsonSchemaNumber, - JsonSchemaObject, - JsonSchemaString, -} from '../../types'; -import {getSchemaByFinalFormPath, parseFinalFormPath} from '../common'; - -const nameSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const surnameSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const hobbySchema: JsonSchemaString = {type: JsonSchemaType.String}; -const hobbiesSchema: JsonSchemaArray = {type: JsonSchemaType.Array, items: hobbySchema}; -const personalDataSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: {name: nameSchema, surname: surnameSchema, hobbies: hobbiesSchema}, -}; -const aboutSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const friendsSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - items: personalDataSchema, -}; -const stringTagSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const numberTagSchema: JsonSchemaNumber = {type: JsonSchemaType.Number}; -const tagsSchema: JsonSchemaArray = { - type: JsonSchemaType.Array, - items: [stringTagSchema, numberTagSchema], -}; -const labelSchema: JsonSchemaString = {type: JsonSchemaType.String}; -const labelsSchema: JsonSchemaArray = {type: JsonSchemaType.Array, items: labelSchema}; - -const mainSchema: JsonSchemaObject = { - type: JsonSchemaType.Object, - properties: { - personalData: personalDataSchema, - about: aboutSchema, - friends: friendsSchema, - tags: tagsSchema, - labels: labelsSchema, - }, -}; - -describe('core/utils/common', () => { - describe('parseFinalFormPath', () => { - it('should parse a simple path', () => { - const result = parseFinalFormPath('foo.bar'); - - expect(result).toEqual(['foo', 'bar']); - }); - - it('should parse a path with array indixes', () => { - const result = parseFinalFormPath('foo[0].bar'); - - expect(result).toEqual(['foo', '0', 'bar']); - }); - - it('should parse a path with multiple array indixes', () => { - const result = parseFinalFormPath('foo[0].bar[1].baz'); - - expect(result).toEqual(['foo', '0', 'bar', '1', 'baz']); - }); - - it('should parse a path with only array indixes', () => { - const result = parseFinalFormPath('[0][1][2]'); - - expect(result).toEqual(['0', '1', '2']); - }); - - it('should handle empty path', () => { - const result = parseFinalFormPath(''); - - expect(result).toEqual([]); - }); - - it('should handle path with special characters', () => { - const result = parseFinalFormPath('foo-bar.baz_qux'); - - expect(result).toEqual(['foo-bar', 'baz_qux']); - }); - }); - - describe('getSchemaByFinalFormPath', () => { - it('should return the main schema when path is empty', () => { - const headPath = ''; - const path = ''; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(mainSchema); - }); - - it('should navigate through object properties', () => { - const headPath = ''; - const path = 'personalData.name'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(nameSchema); - }); - - it('should navigate through array items (when items is a single schema)', () => { - const headPath = ''; - const path = 'labels[0]'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(labelSchema); - }); - - it('should navigate through array items (when items is an array of schemas)', () => { - const headPath = ''; - const path = 'tags[1]'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(numberTagSchema); - }); - - it('should handle complex nested paths', () => { - const headPath = ''; - const path = 'friends[0].hobbies[0]'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(hobbySchema); - }); - - it('should handle finalFormHeadPath correctly', () => { - const headPath = 'headPath'; - const path = `${headPath}.personalData.name`; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(nameSchema); - }); - - it('should return undefined for invalid path', () => { - const headPath = 'foo'; - const path = 'personalData.name'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBeUndefined(); - }); - - it('should handle array path with string schema', () => { - const headPath = ''; - const path = 'personalData[0]'; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBeUndefined(); - }); - - it('should accept path as array of segments', () => { - const headPath = ''; - const path = ['personalData', 'name']; - - const result = getSchemaByFinalFormPath(path, headPath, mainSchema); - - expect(result).toBe(nameSchema); - }); - - it('should handle undefined schema gracefully', () => { - const headPath = ''; - const path = 'personalData.name'; - - const result = getSchemaByFinalFormPath( - path, - headPath, - undefined as unknown as JsonSchema, - ); - - expect(result).toBeUndefined(); - }); - - it('should handle schema without type gracefully', () => { - const headPath = ''; - const path = 'personalData.name'; - const invalidSchema = {} as JsonSchema; - - const result = getSchemaByFinalFormPath(path, headPath, invalidSchema); - - expect(result).toBeUndefined(); - }); - }); -}); diff --git a/src/lib/unstable/core/utils/common.ts b/src/lib/unstable/core/utils/common.ts deleted file mode 100644 index 91393112..00000000 --- a/src/lib/unstable/core/utils/common.ts +++ /dev/null @@ -1,50 +0,0 @@ -import get from 'lodash/get'; - -import {JsonSchemaType} from '../constants'; -import type {JsonSchema} from '../types'; - -export const parseFinalFormPath = (finalFormPath: string): string[] => { - const result: string[] = []; - const regex = /([^[.\]]+)|\[(\d+)\]/g; - let match; - - while ((match = regex.exec(finalFormPath)) !== null) { - if (match[1] !== undefined) { - result.push(match[1]); - } else if (match[2] !== undefined) { - result.push(match[2]); - } - } - - return result; -}; - -export const getSchemaByFinalFormPath = ( - finalFormPath: string | string[], - finalFormHeadPath: string, - mainSchema: JsonSchema, -): JsonSchema | undefined => { - if (finalFormPath.length) { - return (Array.isArray(finalFormPath) ? finalFormPath : parseFinalFormPath(finalFormPath)) - .slice(parseFinalFormPath(finalFormHeadPath).length) - .reduce((acc: JsonSchema | undefined, segment) => { - const type = get(acc, 'type'); - - if (type === JsonSchemaType.Object) { - return get(acc, `properties.${segment}`); - } else if (type === JsonSchemaType.Array) { - const items = get(acc, 'items'); - - if (Array.isArray(items)) { - return get(items, `[${segment}]`); - } - - return items; - } - - return undefined; - }, mainSchema); - } - - return mainSchema; -}; diff --git a/src/lib/unstable/core/utils/index.ts b/src/lib/unstable/core/utils/index.ts deleted file mode 100644 index d0b93236..00000000 --- a/src/lib/unstable/core/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './common'; diff --git a/src/lib/unstable/kit/Accordeon.tsx b/src/lib/unstable/kit/Accordeon.tsx index c67248ff..af864afb 100644 --- a/src/lib/unstable/kit/Accordeon.tsx +++ b/src/lib/unstable/kit/Accordeon.tsx @@ -12,8 +12,9 @@ export const Accordeon = ({ input, meta, children, + wrapperProps, }: WrapperProps): React.ReactNode => { - const [open, setOpen] = React.useState(Boolean(schema.entityParameters?.wrapperProps?.open)); + const [open, setOpen] = React.useState(Boolean(wrapperProps?.open)); // const onDrop = React.useCallback(() => { // setOpen(false); diff --git a/src/lib/unstable/kit/Any.tsx b/src/lib/unstable/kit/Any.tsx new file mode 100644 index 00000000..a3f7e247 --- /dev/null +++ b/src/lib/unstable/kit/Any.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import {Button} from '@gravity-ui/uikit'; + +import {Entity} from '../core'; +import type {Control, JsonSchemaAny} from '../core/types'; + +export const AnyInput: Control = ({input, schema}) => { + const [state, setState] = React.useState(true); + + // React.useEffect(() => { + // input.onChange(undefined); + // }, []); + + return ( + + + + + ); +}; diff --git a/src/lib/unstable/kit/ArrayBase.tsx b/src/lib/unstable/kit/ArrayBase.tsx index 2b2a0c45..42b95f39 100644 --- a/src/lib/unstable/kit/ArrayBase.tsx +++ b/src/lib/unstable/kit/ArrayBase.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Plus} from '@gravity-ui/icons'; -import {Button, Icon} from '@gravity-ui/uikit'; +import {Button, Icon, Text} from '@gravity-ui/uikit'; // import { // ArrayInput, @@ -21,13 +21,13 @@ import {Button, Icon} from '@gravity-ui/uikit'; // } from '../../../../core'; import {block} from '../../kit/utils'; import {Entity} from '../core'; -import type {JsonSchemaArray, SimpleView} from '../core/types'; +import type {Control, JsonSchemaArray} from '../core/types'; import './ArrayBase.scss'; const b = block('array-base'); -export const ArrayBase: SimpleView = ({input, schema}) => { +export const ArrayBase: Control = ({input, schema, meta}) => { // const keys = React.useMemo( // () => // Object.keys(arrayInput.value || {}) @@ -49,7 +49,7 @@ export const ArrayBase: SimpleView = ({input, schema}) => { // @ts-expect-error itemSpec.title = itemSpec.title ? `${itemSpec.title} ${idx + 1}` : `${idx + 1}`; - // @ts-expect-error + return itemSpec; }, [schema.items], @@ -149,6 +149,7 @@ export const ArrayBase: SimpleView = ({input, schema}) => {
{items}
+ {meta.touched && meta.error ? {meta.error} : null}
); }; diff --git a/src/lib/unstable/kit/MultiSelect.tsx b/src/lib/unstable/kit/MultiSelect.tsx index ab421e3a..f6a942d9 100644 --- a/src/lib/unstable/kit/MultiSelect.tsx +++ b/src/lib/unstable/kit/MultiSelect.tsx @@ -5,7 +5,7 @@ import {Select} from '@gravity-ui/uikit'; // import {ArrayInput, FieldArrayValue, transformArrIn, transformArrOut} from '../../../../core'; import {block} from '../../kit/utils'; -import type {JsonSchemaArray, SimpleView} from '../core/types'; +import type {Control, JsonSchemaArray} from '../core/types'; import './MultiSelect.scss'; @@ -26,7 +26,7 @@ export interface MultiSelectProps const b = block('multi-select'); -export const MultiSelect: SimpleView = ({input, schema}) => { +export const MultiSelect: Control = ({input, schema}) => { const {name, value, onBlur, onChange, onFocus} = input; const filterable = React.useMemo(() => (schema.enum?.length || 0) > 9, [schema.enum?.length]); diff --git a/src/lib/unstable/kit/ObjectBase.tsx b/src/lib/unstable/kit/ObjectBase.tsx index d227eba1..4154e434 100644 --- a/src/lib/unstable/kit/ObjectBase.tsx +++ b/src/lib/unstable/kit/ObjectBase.tsx @@ -1,37 +1,43 @@ import React from 'react'; -import {Plus} from '@gravity-ui/icons'; -// import {Button, Icon, Text} from '@gravity-ui/uikit'; -import {Button, Icon} from '@gravity-ui/uikit'; +import {Text} from '@gravity-ui/uikit'; +// import {Button, Icon} from '@gravity-ui/uikit'; import isObjectLike from 'lodash/isObjectLike'; // import {block, filterPropertiesForObjectInline} from '../../../kit/utils'; import {block} from '../../kit/utils'; import {Entity} from '../core'; -import type {IndependentView, IndependentViewProps, JsonSchemaObject} from '../core/types'; +import type {Control, ControlProps, JsonSchemaObject} from '../core/types'; import './ObjectBase.scss'; const b = block('object-base'); -export interface ObjectBaseProps extends IndependentViewProps { +export interface ObjectBaseProps extends ControlProps { inline?: boolean; } -export const ObjectBase: React.FC = ({inline, schema, input, meta, Wrapper}) => { - const addBtn = React.useMemo( - () => ( - - ), - [schema.default, schema.title, input.onChange], - ); +export const ObjectBase: React.FC = ({ + inline, + schema, + input, + meta, + Wrapper, + wrapperProps, +}) => { + // const addBtn = React.useMemo( + // () => ( + // + // ), + // [schema.default, schema.title, input.onChange], + // ); const content = React.useMemo(() => { if ( @@ -42,9 +48,9 @@ export const ObjectBase: React.FC = ({inline, schema, input, me return null; } - if (!inline && !input.value) { - return addBtn; - } + // if (!inline && !input.value) { + // return addBtn; + // } // todo // const specProperties = inline @@ -80,9 +86,9 @@ export const ObjectBase: React.FC = ({inline, schema, input, me schema.properties, // spec.viewSpec.delimiter, // spec.viewSpec.order, - input.value, + // input.value, inline, - addBtn, + // addBtn, input.name, ]); @@ -92,12 +98,13 @@ export const ObjectBase: React.FC = ({inline, schema, input, me return ( // @ts-expect-error - + {content} + {meta.touched && meta.error ? {meta.error} : null} ); }; -export const ObjectInline: IndependentView = (props) => { +export const ObjectInline: Control = (props) => { return ; }; diff --git a/src/lib/unstable/kit/Row.tsx b/src/lib/unstable/kit/Row.tsx index 1e465927..fcb265d6 100644 --- a/src/lib/unstable/kit/Row.tsx +++ b/src/lib/unstable/kit/Row.tsx @@ -29,7 +29,7 @@ const RowBase = ({ input, meta, verboseDescription, - // wrapperProps, + wrapperProps, children, }: WrapperProps & RowProps) => { // const arrayItem = React.useMemo(() => isArrayItem(name), [name]); @@ -41,7 +41,7 @@ const RowBase = ({
{schema.title} diff --git a/src/lib/unstable/kit/Text.tsx b/src/lib/unstable/kit/Text.tsx index 8d712712..62e1ba4f 100644 --- a/src/lib/unstable/kit/Text.tsx +++ b/src/lib/unstable/kit/Text.tsx @@ -7,8 +7,8 @@ import { } from '@gravity-ui/uikit'; import isNil from 'lodash/isNil'; -import {useSetErrors} from '../core'; -import type {JsonSchemaNumber, JsonSchemaString, SimpleViewProps} from '../core/types'; +import {useSchemaRendererMutators} from '../core'; +import type {ControlProps, JsonSchemaNumber, JsonSchemaString} from '../core/types'; export interface TextProps extends Omit< @@ -17,15 +17,15 @@ export interface TextProps > {} const Component = < - T extends - | SimpleViewProps - | SimpleViewProps, + T extends ControlProps | ControlProps, >({ input, meta, schema, }: T) => { - const {setErrors, removeErrors} = useSetErrors(); + // const {setErrors, removeErrors} = useSetErrors(); + const {setSchemaMutators, setExternalErrors, removeSchemaMutators} = + useSchemaRendererMutators(); const props: TextInputBaseProps = { hasClear: true, @@ -39,14 +39,15 @@ const Component = < // placeholder: spec.viewSpec.placeholder, placeholder: `${schema.examples?.[0]}`, qa: input.name, - error: meta.error, + error: meta.touched ? meta.error : undefined, // errorMessage: meta.error, }; React.useEffect(() => { if (input.name === 'qwe.test.jajaja.stringMaxLength') { if (input.value === 'jajaja') { - setErrors({ + setExternalErrors?.({ + headName: 'qwe.test.jajaja', priorityErrors: { [input.name]: 'priorityError', 'qwe.test.jajaja': { @@ -58,16 +59,81 @@ const Component = < 'qwe.test.jajaja.objectPropertyNames': 'priorityError', }, }); - } else { - // setErrors({ - // priorityErrors: { - // [input.name]: undefined, - // }, - // }); - removeErrors({ - removeFunctionOrNames: [input.name], + setSchemaMutators?.({ + headName: 'qwe.test.jajaja', + // mutators: { + // [input.name]: { + // title: 'Aaaaaaaa', + // }, + // 'qwe.test.jajaja.stringPattern': { + // title: 'OOOOOOO', + // }, + // 'qwe.test.jajaja.numberMinimum': { + // minimum: 0, + // }, + // }, + mutators: [ + { + name: input.name, + schema: { + title: 'Aaaaaaaa', + }, + }, + { + name: 'qwe.test.jajaja.stringPattern', + schema: { + title: 'OOOOOOO', + }, + }, + + { + name: 'qwe.test.jajaja.numberMinimum', + schema: { + minimum: 0, + }, + }, + ], + }); + } else if (input.value === 'jajajaj') { + setExternalErrors?.({ + headName: 'qwe.test.jajaja', + priorityErrors: { + [input.name]: 'EXTERNAL_ERROR', + }, }); - // removeErrors({ + removeSchemaMutators?.({ + headName: 'qwe.test.jajaja', + // mutatorsToRemove: { + // 'qwe.test.jajaja.numberMinimum': { + // minimum: 0, + // }, + // 'qwe.test.jajaja.stringPattern': true, + // }, + mutatorsToRemove: [ + { + name: input.name, + schema: true, + }, + { + name: 'qwe.test.jajaja.numberMinimum', + schema: { + minimum: 0, + }, + }, + { + name: 'qwe.test.jajaja.stringPattern', + schema: { + title: 'OOOOOOO', + }, + }, + ], + }); + // removeExternalErrors?.({ + // headName: 'qwe.test.jajaja', + // removeFunctionOrNames: [input.name], + // }); + // removeExternalErrors?.({ + // headName: 'qwe.test.jajaja', // removeFunctionOrNames: (params) => ({ // ...params, // priorityErrors: omit(params.priorityErrors, input.name), diff --git a/src/lib/unstable/kit/config.ts b/src/lib/unstable/kit/config.ts index d7c219eb..88e2967b 100644 --- a/src/lib/unstable/kit/config.ts +++ b/src/lib/unstable/kit/config.ts @@ -9,6 +9,7 @@ import type { } from '../core/types/schema'; import {Accordeon} from './Accordeon'; +import {AnyInput} from './Any'; import {ArrayBase} from './ArrayBase'; import {MultiSelect} from './MultiSelect'; import {ObjectBase} from './ObjectBase'; @@ -16,44 +17,29 @@ import {Row} from './Row'; import {Text} from './Text'; export const untypedConfig = { - [JsonSchemaType.Array]: { - views: { - select: { - form: { - Component: MultiSelect, - }, - overview: { - Component: null, - }, - }, - base: { - form: { - Component: ArrayBase, - }, - overview: { - Component: null, - }, - }, + [JsonSchemaType.Any]: { + controls: { + base: {Component: AnyInput}, }, + views: {}, + wrappers: {}, + validators: {}, + }, + [JsonSchemaType.Array]: { + controls: {select: {Component: MultiSelect}, base: {Component: ArrayBase}}, + views: {}, wrappers: {accordeon: Accordeon, row: Row}, validators: {base: (() => false) as SyncValidator}, }, [JsonSchemaType.Boolean]: { + controls: {}, views: {}, wrappers: {}, validators: {base: (() => false) as SyncValidator}, }, [JsonSchemaType.Number]: { - views: { - base: { - form: { - Component: Text, - }, - overview: { - Component: null, - }, - }, - }, + controls: {base: {Component: Text}}, + views: {}, wrappers: {row: Row}, validators: { base: () => false, @@ -66,40 +52,24 @@ export const untypedConfig = { }, }, [JsonSchemaType.Object]: { - views: { - base: { - form: { - Component: ObjectBase, - independent: true, - }, - overview: { - Component: null, - }, - }, - }, + controls: {base: {Component: ObjectBase, independent: true}}, + views: {}, wrappers: {accordeon: Accordeon}, validators: {base: (() => false) as SyncValidator}, }, [JsonSchemaType.String]: { - views: { - base: { - form: { - Component: Text, - }, - overview: { - Component: null, - }, - }, - }, + controls: {base: {Component: Text}}, + views: {}, wrappers: {row: Row}, validators: { base: (() => 'jajaja') as SyncValidator, - async: (() => - new Promise((resolve) => { - setTimeout(() => { - resolve('bocembocembocem'); - }, 2000); - })) as AsyncValidator, + // async: (() => + // new Promise((resolve) => { + // setTimeout(() => { + // resolve('bocembocembocem'); + // }, 2000); + // })) as AsyncValidator, + ccc: (() => 'ccc') as SyncValidator, }, }, } as const; diff --git a/src/stories/Unstable.stories.tsx b/src/stories/Unstable.stories.tsx index 6811cfa3..f628b6a5 100644 --- a/src/stories/Unstable.stories.tsx +++ b/src/stories/Unstable.stories.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import {Button} from '@gravity-ui/uikit'; import type {StoryFn} from '@storybook/react'; import {noop} from 'lodash'; import {Form} from 'react-final-form'; @@ -8,6 +9,7 @@ import {ObjectBase} from '../lib'; import {SchemaRenderer, schemaRendererMutators} from '../lib/unstable/core'; import {JsonSchemaType, SchemaRendererMode} from '../lib/unstable/core/constants'; import type { + JsonSchemaAny, JsonSchemaArray, JsonSchemaNumber, JsonSchemaObject, @@ -27,18 +29,30 @@ const stringMaxLength: JsonSchemaString = { default: 'jaja', title: 'stringMaxLength', entityParameters: { + controlType: 'base', + controlProps: { + // name: true, + }, validatorType: 'base', - viewType: 'base', - wrapperType: 'row', + // controlType: 'base', + controlWrapperType: 'row', errorMessages: { maxLength: 'stringMaxLength', }, - // wrapperProps: { - // qtest: 'q', - // }, + controlWrapperProps: { + // qtest: 'q', + // qwa, + }, // viewProps: { - // qw: 'q', + // form: { + // // qw: 'q', + // // clearable: true, + // name: true, + // }, // }, + // formProps: { + // name: true, + // } }, }; @@ -49,8 +63,10 @@ const stringMinLength: JsonSchemaString = { title: 'stringMinLength', entityParameters: { validatorType: 'async', - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlProps: {}, + controlWrapperType: 'row', + controlWrapperProps: {}, errorMessages: { minLength: 'stringMinLength', }, @@ -63,8 +79,9 @@ const stringPattern: JsonSchemaString = { default: 'jaja', title: 'stringPattern', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', + controlWrapperProps: {}, errorMessages: { pattern: 'stringPattern', }, @@ -96,8 +113,8 @@ const stringAllOf: JsonSchemaString = { }, ], entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { minLength: 'stringAllOf/minLength from item', const: 'stringAllOf/const from item', @@ -130,8 +147,8 @@ const stringAnyOf: JsonSchemaString = { }, ], entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { minLength: 'stringAnyOf/minLength from item', const: 'stringAnyOf/const from item', @@ -146,8 +163,8 @@ const stringConst: JsonSchemaString = { default: 'jaja', title: 'stringConst', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { const: 'stringConst', }, @@ -160,11 +177,12 @@ const stringEnum: JsonSchemaString = { default: 'jaja', title: 'stringEnum', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { enum: 'stringEnum', }, + validatorType: 'ccc', }, }; @@ -184,8 +202,8 @@ const stringThen: JsonSchemaString = { minLength: 5, }, entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { then: 'stringThen', }, @@ -208,8 +226,8 @@ const stringElse: JsonSchemaString = { type: JsonSchemaType.String, }, entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { else: 'stringElse', }, @@ -225,8 +243,8 @@ const stringNot: JsonSchemaString = { const: 'jaja', }, entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { not: 'stringNot', }, @@ -258,8 +276,8 @@ const stringOneOf: JsonSchemaString = { }, ], entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { minLength: 'stringOneOf from item', const: 'stringOneOf from item', @@ -274,8 +292,8 @@ const numberExclusiveMaximum: JsonSchemaNumber = { default: 2, title: 'numberExclusiveMaximum', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { exclusiveMaximum: 'numberExclusiveMaximum', }, @@ -288,8 +306,8 @@ const numberExclusiveMinimum: JsonSchemaNumber = { default: 2, title: 'numberExclusiveMinimum', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { exclusiveMinimum: 'numberExclusiveMinimum', }, @@ -302,8 +320,8 @@ const numberMaximum: JsonSchemaNumber = { default: 3, title: 'numberMaximum', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { maximum: 'numberMaximum', }, @@ -316,8 +334,8 @@ const numberMinimum: JsonSchemaNumber = { default: 2, title: 'numberMinimum', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { minimum: 'numberMinimum', }, @@ -330,8 +348,8 @@ const numberMultipleOf: JsonSchemaNumber = { default: 2, title: 'numberMultipleOf', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { multipleOf: 'numberMultipleOf', }, @@ -345,18 +363,18 @@ const objectAdditionalProperties: JsonSchemaObject = { }, default: {extra: 'test'}, title: 'objectAdditionalProperties', - additionalProperties: false, - // additionalProperties: { - // type: JsonSchemaType.String, - // const: 'jaja', - // entityParameters: { - // errorMessages: {const: 'objectAdditionalProperties/const'}, - // }, - // }, + // additionalProperties: false, + additionalProperties: { + type: JsonSchemaType.String, + const: 'jaja', + entityParameters: { + errorMessages: {const: 'objectAdditionalProperties/const'}, + }, + }, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, errorMessages: { @@ -365,7 +383,7 @@ const objectAdditionalProperties: JsonSchemaObject = { }, }; -const objectDependencies: JsonSchemaObject = { +const objectDependencies: JsonSchemaObject = { type: JsonSchemaType.Object, properties: { stringConst, @@ -393,9 +411,9 @@ const objectDependencies: JsonSchemaObject = { // }, }, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, errorMessages: { @@ -418,9 +436,9 @@ const objectMaxProperties: JsonSchemaObject = { maxProperties: 1, default: {}, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, errorMessages: { @@ -439,9 +457,9 @@ const objectMinProperties: JsonSchemaObject = { minProperties: 3, default: {}, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, errorMessages: { @@ -465,9 +483,9 @@ const objectPatternProperties: JsonSchemaObject = { }, }, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, }, @@ -486,9 +504,9 @@ const objectPropertyNames: JsonSchemaObject = { maxLength: 5, }, entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, errorMessages: { @@ -503,21 +521,21 @@ const arrayContains: JsonSchemaArray = { type: JsonSchemaType.String, title: 'item', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', }, }, - contains: true, - // contains: { - // type: JsonSchemaType.String, - // const: 'test', - // }, + // contains: true, + contains: { + type: JsonSchemaType.String, + const: 'test', + }, title: 'arrayContains', default: [], entityParameters: { - viewType: 'base', - wrapperType: 'row', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'row', + controlWrapperProps: { open: true, }, errorMessages: { @@ -533,8 +551,8 @@ const arrayMaxItems: JsonSchemaArray = { title: 'item', minLength: 2, entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', errorMessages: { minLength: 'stringMinLength', }, @@ -544,9 +562,9 @@ const arrayMaxItems: JsonSchemaArray = { title: 'arrayMaxItems', default: ['1', '2'], entityParameters: { - viewType: 'base', - wrapperType: 'row', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'row', + controlWrapperProps: { open: true, }, errorMessages: { @@ -561,17 +579,17 @@ const arrayMinItems: JsonSchemaArray = { type: JsonSchemaType.String, title: 'item', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', }, }, minItems: 3, title: 'arrayMinItems', default: ['1', '2'], entityParameters: { - viewType: 'base', - wrapperType: 'row', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'row', + controlWrapperProps: { open: true, }, errorMessages: { @@ -586,17 +604,17 @@ const arrayUniqueItems: JsonSchemaArray = { type: JsonSchemaType.String, title: 'item', entityParameters: { - viewType: 'base', - wrapperType: 'row', + controlType: 'base', + controlWrapperType: 'row', }, }, uniqueItems: true, title: 'arrayUniqueItems', default: ['1', '1'], entityParameters: { - viewType: 'base', - wrapperType: 'row', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'row', + controlWrapperProps: { open: true, }, errorMessages: { @@ -605,9 +623,62 @@ const arrayUniqueItems: JsonSchemaArray = { }, }; +const anySpec: JsonSchemaAny = { + title: 'anySpec', + entityParameters: { + controlType: 'base', + controlWrapperType: 'row', + validatorType: 'ccc', + }, + oneOf: [stringEnum, arrayUniqueItems], +}; + const baseSpec: JsonSchemaObject = { type: JsonSchemaType.Object, properties: { + anySpec, + arrayContains, + arrayMaxItems, + arrayMinItems, + arrayUniqueItems, + stringMaxLength, + stringMinLength, + stringPattern, + stringAllOf, + stringAnyOf, + stringConst, + stringEnum, + stringThen, + stringElse, + stringNot, + stringOneOf, + numberExclusiveMaximum, + numberExclusiveMinimum, + numberMaximum, + numberMinimum, + numberMultipleOf, + objectAdditionalProperties, + objectDependencies, + objectMaxProperties, + objectMinProperties, + objectPatternProperties, + objectPropertyNames, + }, + title: 'Candidate', + entityParameters: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { + open: true, + }, + }, + const: {}, +}; + +const baseSpec2: JsonSchemaObject = { + type: JsonSchemaType.Object, + properties: { + anySpec, arrayContains, arrayMaxItems, arrayMinItems, @@ -634,12 +705,16 @@ const baseSpec: JsonSchemaObject = { objectMinProperties, objectPatternProperties, objectPropertyNames, + baseSpec, + s: baseSpec, + ss: baseSpec, + sss: baseSpec, }, title: 'Candidate', entityParameters: { - viewType: 'base', - wrapperType: 'accordeon', - wrapperProps: { + controlType: 'base', + controlWrapperType: 'accordeon', + controlWrapperProps: { open: true, }, }, @@ -654,6 +729,10 @@ const value = { age: 13, nameQ: 'jaja', obj: {name: 'bocemb', age: 13}, + baseSpec: {}, + s: {}, + ss: {}, + sss: {}, }, }, }, @@ -664,8 +743,8 @@ const value = { // type: JsonSchemaType.String, // title: 'Name', // entityParameters: { -// viewType: 'base', -// wrapperType: 'row', +// controlType: 'base', +// controlWrapperType: 'row', // }, // // minLength: 10, // // pattern: '^[0-9]', @@ -681,13 +760,16 @@ const value = { const template = () => { const Template: StoryFn = (__) => (
- {() => ( - + {(form) => ( + + + + )} );