Skip to content

Commit

Permalink
feat(types): rename interface of ValidationError
Browse files Browse the repository at this point in the history
rename to `ValidationFailure` from `ValidationError`
  • Loading branch information
TomokiMiyauci committed May 23, 2023
1 parent 9e46ffd commit a121e4f
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 79 deletions.
10 changes: 5 additions & 5 deletions operators/and.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

import { iter } from "../iter_utils.ts";
import {
Assert,
AssertiveValidator,
Validation,
ValidationError,
type Assert,
type AssertiveValidator,
type Validation,
type ValidationFailure,
} from "../types.ts";

export class AndValidator<In, In_ extends Via, Via extends In = In & In_>
Expand All @@ -19,7 +19,7 @@ export class AndValidator<In, In_ extends Via, Via extends In = In & In_>
this.validators = validators;
}

*validate(input: In): Iterable<ValidationError> {
*validate(input: In): Iterable<ValidationFailure> {
for (const validator of this.validators) {
const iterable = validator.validate(input);
const result = iter(iterable);
Expand Down
47 changes: 21 additions & 26 deletions types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { isString } from "./deps.ts";
/** Validator API. */
export interface Validator<in In = unknown> {
/** Validates the input and yield validation errors if exists. */
validate: (input: In) => Iterable<ValidationError>;
validate: (input: In) => Iterable<ValidationFailure>;
}

export type Validation<In = unknown, In_ extends In = In> = In extends In_
Expand All @@ -17,6 +17,23 @@ export interface Display {
toString(): string;
}

/** Validation failure. */
export class ValidationFailure {
/** The validation failure message. */
message: string;

/** The path to a part of the instance. */
instancePath: string[];

constructor(
message?: string,
options?: Readonly<{ instancePath: string[] }>,
) {
this.message = message ?? "";
this.instancePath = options?.instancePath ?? [];
}
}

export interface Transformer<in In = unknown, out Out = In> {
transform: (input: In) => Out;
}
Expand All @@ -33,28 +50,6 @@ export class Assert {
export interface AssertiveValidator<In = unknown, In_ extends In = In>
extends Validator<In>, Assert<In_> {}

/** Validation error options. */
export interface ValidationErrorOptions extends ErrorOptions {
/** Path to instance. */
instancePath?: string[];
}

/** Validation error. */
export class ValidationError extends Error {
/** The path to a part of the instance. */
instancePath: string[];

override name = "ValidationError";

constructor(
message?: string,
options: ValidationErrorOptions = {},
) {
super(message, options);
this.instancePath = options.instancePath ?? [];
}
}

export interface Reporter<T = unknown> {
report(context: T): string;

Expand Down Expand Up @@ -109,10 +104,10 @@ export class Err {
type: "error" = "error";

/** Actual errors. */
errors: readonly ValidationError[];
failures: readonly ValidationFailure[];

constructor(errors: readonly ValidationError[]) {
this.errors = errors;
constructor(failures: readonly ValidationFailure[]) {
this.failures = failures;
}

/** Whether the {@link Result} is {@link Ok} or not. */
Expand Down
76 changes: 39 additions & 37 deletions validation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2023-latest Tomoki Miyauchi. All rights reserved. MIT license.
// This module is browser compatible.

import { isEmpty, isString } from "./deps.ts";
import { isEmpty } from "./deps.ts";
import {
Err,
Ok,
Result,
Validation,
ValidationError,
ValidationFailure,
Validator,
} from "./types.ts";
import { take } from "./iter_utils.ts";
Expand Down Expand Up @@ -81,31 +81,27 @@ export function assert(
if (result.isOk()) return;

if (hasOnce) {
const e = result.errors[0]!;
const failure = result.failures[0]!;
const {
error,
message = makeMsg(e, { rootName }),
error = ValidationError,
message = makeMsg(failure, { rootName }),
cause,
} = options;

if (error) throw captured(new error(message, { cause }));

e.cause = cause;

if (isString(message)) e.message = message;
const e = new error(message, { cause, instancePath: failure.instancePath });

throw captured(e);
}

options.error ??= AggregateError;

throw captured(
new options.error(
result.errors.map((e) => exposePath(e, rootName)).map(captured),
message,
{ cause },
),
);
const errors = result.failures
.map((failure) =>
new ValidationError(makeMsg(failure, { rootName }), {
instancePath: failure.instancePath,
})
).map(captured);

throw captured(new options.error(errors, message, { cause }));

// deno-lint-ignore ban-types
function captured<T extends Object>(error: T): T {
Expand All @@ -117,7 +113,7 @@ export function assert(
}
}

export interface ValidateOptions extends ErrorOptions {
export interface ValidateOptions {
/**
* @default Infinity
*/
Expand All @@ -132,42 +128,48 @@ export function validate<In = unknown, In_ extends In = In>(
input: In,
options: ValidateOptions = {},
): Result<In_> {
const errors = [...take(validation.validate(input), options.maxErrors)]
.map(setCause);
const failures = [...take(validation.validate(input), options.maxErrors)];

if (!errors.length) return new Ok(input as In_);
if (!failures.length) return new Ok(input as In_);

return new Err(errors);
return new Err(failures);
}

function setCause<T extends Error>(error: T): T {
error.cause ??= options.cause;
/** Validation error. */
export class ValidationError extends Error {
/** The path to a part of the instance. */
instancePath: string[];

return error;
override name = "ValidationError";

constructor(
message?: string,
options: ValidationErrorOptions = {},
) {
super(message, options);
this.instancePath = options.instancePath ?? [];
}
}

/** Validation error options. */
export interface ValidationErrorOptions extends ErrorOptions {
/** Path to instance. */
instancePath?: string[];
}

function makeMsg(
error: ValidationError,
failure: ValidationFailure,
options: { rootName?: string } = {},
): string {
const { rootName: name } = options;
const { instancePath, message } = error;
const { instancePath, message } = failure;
const pathSection = instancePath.length
? "\n" + new InstancePath(instancePath, { name })
: "";

return message + pathSection;
}

function exposePath(
error: ValidationError,
rootPathDisplay?: string,
): ValidationError {
error.message = makeMsg(error, { rootName: rootPathDisplay });

return error;
}

interface InstancePathOptions {
name?: string;
}
Expand Down
4 changes: 2 additions & 2 deletions validators/iterable/item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import {
Assert,
AssertiveValidator,
Validation,
ValidationError,
type ValidationFailure,
} from "../../types.ts";

export class ItemValidator<In = unknown, In_ extends In = In>
implements AssertiveValidator<Iterable<In>, Iterable<In_>> {
declare [Assert.symbol]: Iterable<In_>;
constructor(public readonly validator: Validation<In, In_>) {}

*validate(input: Iterable<In>): Iterable<ValidationError> {
*validate(input: Iterable<In>): Iterable<ValidationFailure> {
for (const [i, el] of enumerate(input)) {
const iterable = this.validator.validate(el);
const createError = curryR(fromPath, `${i}`);
Expand Down
6 changes: 3 additions & 3 deletions validators/iterable/unique.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { enumerate } from "../../iter_utils.ts";
import { display, interpolate } from "../../utils.ts";
import { Reporter, ValidationError, Validator } from "../../types.ts";
import { Reporter, ValidationFailure, Validator } from "../../types.ts";
import error from "../error.json" assert { type: "json" };

interface Context {
Expand All @@ -19,9 +19,9 @@ export class UniqueValidator extends Reporter<Context>
super();
super.expect(({ item }) => interpolate(error.unique, [item]));
}
*validate(input: Iterable<unknown>): Iterable<ValidationError> {
*validate(input: Iterable<unknown>): Iterable<ValidationFailure> {
for (const [index, item] of duplicates(input)) {
yield new ValidationError(
yield new ValidationFailure(
this.report({ input, item, index }),
{
instancePath: [index.toString()],
Expand Down
4 changes: 2 additions & 2 deletions validators/object/dictionary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Assert,
AssertiveValidator,
Validation,
ValidationError,
ValidationFailure,
} from "../../types.ts";

export class DictionaryValidator<
Expand All @@ -21,7 +21,7 @@ export class DictionaryValidator<
& { [k in keyof In_]: Validation<In_[k]> },
) {}

*validate(input: In): Iterable<ValidationError> {
*validate(input: In): Iterable<ValidationFailure> {
for (const [key, validator] of Object.entries(this.validators)) {
const value = input?.[key];
const iterable = validator.validate(value as never);
Expand Down
4 changes: 2 additions & 2 deletions validators/object/optional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
Assert,
AssertiveValidator,
Validation,
ValidationError,
ValidationFailure,
} from "../../types.ts";

export class OptionalValidator<
Expand All @@ -21,7 +21,7 @@ export class OptionalValidator<
& { [k in keyof In_]: Validation<In_[k]> },
) {}

*validate(input: Partial<In>): Iterable<ValidationError> {
*validate(input: Partial<In>): Iterable<ValidationFailure> {
const validators = filterKeys(
this.validators,
hasInput,
Expand Down
4 changes: 2 additions & 2 deletions validators/property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Assert,
AssertiveValidator,
Validation,
ValidationError,
type ValidationFailure,
Validator,
} from "../types.ts";

Expand All @@ -21,7 +21,7 @@ export class PropertyValidator<In_ extends string = string>
this.validator = validator as Validator<string>;
}

*validate(input: {}): Iterable<ValidationError> {
*validate(input: {}): Iterable<ValidationFailure> {
for (const key in input) {
const createError = curryR(fromPath, key);

Expand Down

0 comments on commit a121e4f

Please sign in to comment.