Skip to content

Commit 46dbd18

Browse files
leonsenftkirjs
authored andcommitted
refactor(forms): remove customError()
Remove the `customError` function and `CustomValidationError` type. These were made obsolete by support for returning plain object literals as custom errors. This also catches few `field` properties that were missed in the renaming to `fieldTree`.
1 parent 9e043de commit 46dbd18

24 files changed

Lines changed: 249 additions & 485 deletions

goldens/public-api/forms/signals/index.api.md

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,6 @@ export function createMetadataKey<TWrite>(): MetadataKey<Signal<TWrite | undefin
8989
// @public
9090
export function createMetadataKey<TWrite, TAcc>(reducer: MetadataReducer<TAcc, TWrite>): MetadataKey<Signal<TAcc>, TWrite, TAcc>;
9191

92-
// @public
93-
export function customError<E extends Partial<ValidationError.WithField>>(obj: WithField<E>): CustomValidationError;
94-
95-
// @public
96-
export function customError<E extends Partial<ValidationError.WithField>>(obj?: E): WithoutField<CustomValidationError>;
97-
98-
// @public
99-
export class CustomValidationError implements ValidationError {
100-
constructor(options?: ValidationErrorOptions);
101-
[key: PropertyKey]: unknown;
102-
readonly fieldTree: FieldTree<unknown>;
103-
readonly kind: string;
104-
readonly message?: string;
105-
}
106-
10792
// @public
10893
export function debounce<TValue, TPathKind extends PathKind = PathKind.Root>(path: SchemaPath<TValue, SchemaPathRules.Supported, TPathKind>, durationOrDebouncer: number | Debouncer<TValue, TPathKind>): void;
10994

@@ -613,7 +598,7 @@ export namespace ValidationError {
613598
readonly fieldTree?: FieldTree<unknown>;
614599
}
615600
export interface WithoutField extends ValidationError {
616-
readonly field?: never;
601+
readonly fieldTree?: never;
617602
}
618603
}
619604

packages/forms/signals/src/api/rules/validation/util.ts

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {isArray} from '../../../util/type_guards';
10-
import {LogicFn, OneOrMany, PathKind, ValidationResult, type FieldContext} from '../../types';
11-
import {customError, ValidationError} from './validation_errors';
9+
import {LogicFn, OneOrMany, PathKind, type FieldContext} from '../../types';
10+
import {ValidationError} from './validation_errors';
1211

1312
/** Represents a value that has a length or size, such as an array or string, or set. */
1413
export type ValueWithLengthOrSize = {length: number} | {size: number};
@@ -59,43 +58,3 @@ export function isEmpty(value: unknown): boolean {
5958
}
6059
return value === '' || value === false || value == null;
6160
}
62-
63-
/**
64-
* Whether the value is a plain object, as opposed to being an instance of Validation error.
65-
* @param error An error that could be a plain object, or an instance of a class implementing ValidationError.
66-
*/
67-
function isPlainError(error: ValidationError) {
68-
return (
69-
typeof error === 'object' &&
70-
(Object.getPrototypeOf(error) === Object.prototype || Object.getPrototypeOf(error) === null)
71-
);
72-
}
73-
74-
/**
75-
* If the value provided is a plain object, it wraps it into a custom error.
76-
* @param error An error that could be a plain object, or an instance of a class implementing ValidationError.
77-
*/
78-
function ensureCustomValidationError(error: ValidationError.WithField): ValidationError.WithField {
79-
if (isPlainError(error)) {
80-
return customError(error);
81-
}
82-
return error;
83-
}
84-
85-
/**
86-
* Makes sure every provided error is wrapped as a custom error.
87-
* @param result Validation result with a field.
88-
*/
89-
export function ensureCustomValidationResult(
90-
result: ValidationResult<ValidationError.WithField>,
91-
): ValidationResult<ValidationError.WithField> {
92-
if (result === null || result === undefined) {
93-
return result;
94-
}
95-
96-
if (isArray(result)) {
97-
return result.map(ensureCustomValidationError);
98-
}
99-
100-
return ensureCustomValidationError(result);
101-
}

packages/forms/signals/src/api/rules/validation/validate.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import type {
1616
SchemaPath,
1717
SchemaPathRules,
1818
} from '../../types';
19-
import {ensureCustomValidationResult} from './util';
2019

2120
/**
2221
* Adds logic to a field to determine if the field has validation errors.
@@ -37,8 +36,6 @@ export function validate<TValue, TPathKind extends PathKind = PathKind.Root>(
3736

3837
const pathNode = FieldPathNode.unwrapFieldPath(path);
3938
pathNode.builder.addSyncErrorRule((ctx) => {
40-
return ensureCustomValidationResult(
41-
addDefaultField(logic(ctx as FieldContext<TValue, TPathKind>), ctx.fieldTree),
42-
);
39+
return addDefaultField(logic(ctx as FieldContext<TValue, TPathKind>), ctx.fieldTree);
4340
});
4441
}

packages/forms/signals/src/api/rules/validation/validation_errors.ts

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -272,32 +272,6 @@ export function standardSchemaError(
272272
return new StandardSchemaValidationError(issue, options);
273273
}
274274

275-
/**
276-
* Create a custom error associated with the target field
277-
* @param obj The object to create an error from
278-
*
279-
* @category validation
280-
* @experimental 21.0.0
281-
*/
282-
export function customError<E extends Partial<ValidationError.WithField>>(
283-
obj: WithField<E>,
284-
): CustomValidationError;
285-
/**
286-
* Create a custom error
287-
* @param obj The object to create an error from
288-
*
289-
* @category validation
290-
* @experimental 21.0.0
291-
*/
292-
export function customError<E extends Partial<ValidationError.WithField>>(
293-
obj?: E,
294-
): WithoutField<CustomValidationError>;
295-
export function customError<E extends Partial<ValidationError.WithField>>(
296-
obj?: E,
297-
): WithOptionalField<CustomValidationError> {
298-
return new CustomValidationError(obj);
299-
}
300-
301275
/**
302276
* Common interface for all validation errors.
303277
*
@@ -348,38 +322,7 @@ export declare namespace ValidationError {
348322
*/
349323
export interface WithoutField extends ValidationError {
350324
/** The field associated with this error. */
351-
readonly field?: never;
352-
}
353-
}
354-
355-
/**
356-
* A custom error that may contain additional properties
357-
*
358-
* @category validation
359-
* @experimental 21.0.0
360-
*/
361-
export class CustomValidationError implements ValidationError {
362-
/** Brand the class to avoid Typescript structural matching */
363-
private __brand = undefined;
364-
365-
/**
366-
* Allow the user to attach arbitrary other properties.
367-
*/
368-
[key: PropertyKey]: unknown;
369-
370-
/** Identifies the kind of error. */
371-
readonly kind: string = '';
372-
373-
/** The field associated with this error. */
374-
readonly fieldTree!: FieldTree<unknown>;
375-
376-
/** Human readable error message. */
377-
readonly message?: string;
378-
379-
constructor(options?: ValidationErrorOptions) {
380-
if (options) {
381-
Object.assign(this, options);
382-
}
325+
readonly fieldTree?: never;
383326
}
384327
}
385328

packages/forms/signals/src/api/structure.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function form<TModel>(
155155
* ```ts
156156
* const nameForm = form(signal({first: '', last: ''}), (name) => {
157157
* required(name.first);
158-
* validate(name.last, ({value}) => !/^[a-z]+$/i.test(value()) ? customError({kind: 'alphabet-only'}) : undefined);
158+
* validate(name.last, ({value}) => !/^[a-z]+$/i.test(value()) ? {kind: 'alphabet-only'} : undefined);
159159
* });
160160
* nameForm().valid(); // false
161161
* nameForm().value.set({first: 'John', last: 'Doe'});

packages/forms/signals/test/node/api/hidden.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Injector, signal} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
11-
import {customError, form, hidden, validate} from '@angular/forms/signals';
11+
import {form, hidden, validate} from '@angular/forms/signals';
1212

1313
describe('hidden', () => {
1414
it('should initially be false', () => {
@@ -70,7 +70,7 @@ describe('hidden', () => {
7070
});
7171

7272
validate(p.name, () => {
73-
return customError({kind: 'dog'});
73+
return {kind: 'dog'};
7474
});
7575
},
7676
{injector: TestBed.inject(Injector)},

packages/forms/signals/test/node/api/validators/email.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {Injector, signal} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
1111
import {email, form} from '../../../../public_api';
12-
import {customError, emailError} from '../../../../src/api/rules/validation/validation_errors';
12+
import {emailError} from '../../../../src/api/rules/validation/validation_errors';
1313

1414
describe('email validator', () => {
1515
it('returns requiredTrue error when the value is false', () => {
@@ -35,7 +35,7 @@ describe('email validator', () => {
3535
cat,
3636
(p) => {
3737
email(p.email, {
38-
error: (ctx) => customError({kind: `special-email-${ctx.valueOf(p.name)}`}),
38+
error: (ctx) => ({kind: `special-email-${ctx.valueOf(p.name)}`}),
3939
});
4040
},
4141
{
@@ -44,10 +44,10 @@ describe('email validator', () => {
4444
);
4545

4646
expect(f.email().errors()).toEqual([
47-
customError({
47+
{
4848
kind: 'special-email-pirojok-the-cat',
4949
fieldTree: f.email,
50-
}),
50+
},
5151
]);
5252
});
5353

packages/forms/signals/test/node/api/validators/max.spec.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Injector, signal} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
11-
import {customError, form, max, maxError} from '../../../../public_api';
11+
import {form, max, maxError} from '../../../../public_api';
1212

1313
describe('max validator', () => {
1414
it('returns max error when the value is larger', () => {
@@ -58,19 +58,19 @@ describe('max validator', () => {
5858
(p) => {
5959
max(p.age, 5, {
6060
error: ({value}) => {
61-
return customError({kind: 'special-max', message: value()?.toString()});
61+
return {kind: 'special-max', message: value()?.toString()};
6262
},
6363
});
6464
},
6565
{injector: TestBed.inject(Injector)},
6666
);
6767

6868
expect(f.age().errors()).toEqual([
69-
customError({
69+
{
7070
kind: 'special-max',
7171
message: '6',
7272
fieldTree: f.age,
73-
}),
73+
},
7474
]);
7575
});
7676

@@ -103,16 +103,14 @@ describe('max validator', () => {
103103
error: ({value, valueOf}) => {
104104
return valueOf(p.name) === 'disabled'
105105
? []
106-
: customError({kind: 'special-max', message: value()?.toString()});
106+
: {kind: 'special-max', message: value()?.toString()};
107107
},
108108
});
109109
},
110110
{injector: TestBed.inject(Injector)},
111111
);
112112

113-
expect(f.age().errors()).toEqual([
114-
customError({kind: 'special-max', message: '6', fieldTree: f.age}),
115-
]);
113+
expect(f.age().errors()).toEqual([{kind: 'special-max', message: '6', fieldTree: f.age}]);
116114
f.name().value.set('disabled');
117115
expect(f.age().errors()).toEqual([]);
118116
});
@@ -154,7 +152,7 @@ describe('max validator', () => {
154152
(p) => {
155153
max(p.age, 5, {
156154
error: ({value}) => {
157-
return customError({kind: 'special-max', message: value()?.toString()});
155+
return {kind: 'special-max', message: value()?.toString()};
158156
},
159157
});
160158
},

packages/forms/signals/test/node/api/validators/max_length.spec.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Injector, signal} from '@angular/core';
1010
import {TestBed} from '@angular/core/testing';
11-
import {customError, form, maxLength, maxLengthError} from '../../../../public_api';
11+
import {form, maxLength, maxLengthError} from '../../../../public_api';
1212

1313
describe('maxLength validator', () => {
1414
it('returns maxLength error when the length is larger for strings', () => {
@@ -70,22 +70,22 @@ describe('maxLength validator', () => {
7070
(p) => {
7171
maxLength(p.text, 5, {
7272
error: ({value}) => {
73-
return customError({
73+
return {
7474
kind: 'special-maxLength',
7575
message: `Length is ${value().length}`,
76-
});
76+
};
7777
},
7878
});
7979
},
8080
{injector: TestBed.inject(Injector)},
8181
);
8282

8383
expect(f.text().errors()).toEqual([
84-
customError({
84+
{
8585
kind: 'special-maxLength',
8686
message: 'Length is 6',
8787
fieldTree: f.text,
88-
}),
88+
},
8989
]);
9090
});
9191

@@ -145,10 +145,10 @@ describe('maxLength validator', () => {
145145
(p) => {
146146
maxLength(p.text, 5, {
147147
error: ({value}) => {
148-
return customError({
148+
return {
149149
kind: 'special-maxLength',
150150
message: `Length is ${value().length}`,
151-
});
151+
};
152152
},
153153
});
154154
},

0 commit comments

Comments
 (0)