#カスタムエラー
Result.fail() でエラーを作成する際、単純な文字列やジェネリックなErrorオブジェクトではなく、カスタムエラーの使用を強くお勧めします。
このガイドでは、カスタムエラーとは何か、なぜ有益なのか、そして効果的に作成する方法を説明します。
#カスタムエラーとは?
カスタムエラーは、ジェネリックなErrorオブジェクトよりも多くの構造とコンテキストを提供する特殊化されたエラークラスです。 一般的に採用されるカスタムエラーには主に2つの形式があります。
#1. カスタムエラークラス(推奨)
カスタムエラークラスは組み込みの Error クラスを継承し、追加の機能を提供する方法です。
import { import Result Result } from '@praha/byethrow';
import { import z z } from 'zod';
class class ValidationError ValidationError extends var Error: ErrorConstructor Error {
public override readonly ValidationError.name: "ValidationError" name = 'ValidationError';
constructor(message: string message : string, options: ErrorOptions | undefined options ?: ErrorOptions) {
super(message: string message , options: ErrorOptions | undefined options );
}
}
// Result.fail()での使用
const const validateEmail: (email: string) => Result.Result<string, ValidationError> validateEmail = (email: string email : string): import Result Result .type Result<T, E> = Result.Success<T> | Result.Failure<E>A union type representing either a success or a failure.
@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@exampleimport { Result } from '@praha/byethrow';
const doSomething = (): Result.Result<number, string> => {
return Math.random() > 0.5
? { type: 'Success', value: 10 }
: { type: 'Failure', error: 'Oops' };
};
@categoryCore Types Result <string, class ValidationError ValidationError > => {
return import Result Result .const pipe: <string, Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, Result.Result<string, ValidationError>>(a: string, ab: (a: string) => Result.Result<string, readonly StandardSchemaV1.Issue[]>, bc: (b: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, ValidationError>) => Result.Result<string, ValidationError> (+25 overloads) pipe (
email: string email ,
import Result Result .const parse: <z.ZodString>(schema: z.ZodString) => (value: unknown) => Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]> (+1 overload) parse (import z z .function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload) string ().ZodString.email(params?: string | z.core.$ZodCheckEmailParams): z.ZodString@deprecatedUse z.email() instead. email ()),
import Result Result .const mapError: <Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, ValidationError>(fn: (a: readonly StandardSchemaV1.Issue[]) => ValidationError) => (result: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, ValidationError> (+1 overload) mapError ((error: readonly StandardSchemaV1.Issue[] error ) => new constructor ValidationError(message: string, options?: ErrorOptions): ValidationError ValidationError ('Invalid email format', { ErrorOptions.cause?: unknown cause : error: readonly StandardSchemaV1.Issue[] error })),
);
};#2. 識別可能なタグを持つオブジェクト
プレーンなオブジェクトに識別可能なプロパティを持たせる事でエラー型として使用することもできます。
import { import Result Result } from '@praha/byethrow';
import { import z z } from 'zod';
type type ValidationError = {
type: "ValidationError";
message: string;
value: string;
}
ValidationError = {
type: "ValidationError" type : 'ValidationError';
message: string message : string;
value: string value : string;
};
const const validateEmail: (email: string) => Result.Result<string, ValidationError> validateEmail = (email: string email : string): import Result Result .type Result<T, E> = Result.Success<T> | Result.Failure<E>A union type representing either a success or a failure.
@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@exampleimport { Result } from '@praha/byethrow';
const doSomething = (): Result.Result<number, string> => {
return Math.random() > 0.5
? { type: 'Success', value: 10 }
: { type: 'Failure', error: 'Oops' };
};
@categoryCore Types Result <string, type ValidationError = {
type: "ValidationError";
message: string;
value: string;
}
ValidationError > => {
return import Result Result .const pipe: <string, Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, Result.Result<string, {
readonly type: "ValidationError";
readonly message: "Invalid email format";
readonly value: string;
}>>(a: string, ab: (a: string) => Result.Result<string, readonly StandardSchemaV1.Issue[]>, bc: (b: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, {
readonly type: "ValidationError";
readonly message: "Invalid email format";
readonly value: string;
}>) => Result.Result<...> (+25 overloads)
pipe (
email: string email ,
import Result Result .const parse: <z.ZodString>(schema: z.ZodString) => (value: unknown) => Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]> (+1 overload) parse (import z z .function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload) string ().ZodString.email(params?: string | z.core.$ZodCheckEmailParams): z.ZodString@deprecatedUse z.email() instead. email ()),
import Result Result .const mapError: <Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, {
readonly type: "ValidationError";
readonly message: "Invalid email format";
readonly value: string;
}>(fn: (a: readonly StandardSchemaV1.Issue[]) => {
readonly type: "ValidationError";
readonly message: "Invalid email format";
readonly value: string;
}) => (result: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, {
readonly type: "ValidationError";
readonly message: "Invalid email format";
readonly value: string;
}> (+1 overload)
mapError ((error: readonly StandardSchemaV1.Issue[] error ) => ({
type: "ValidationError" type : 'ValidationError',
message: "Invalid email format" message : 'Invalid email format',
value: string value : email: string email ,
})),
);
};#なぜカスタムエラークラスを使うのか?
どちらのアプローチでも機能しますが、以下の理由からカスタムErrorクラスの使用をお勧めします。
#スタックトレースの利用可能性
カスタムErrorクラスは自動的にスタックトレースをキャプチャするため、デバッグが非常に容易になります。
import { import Result Result } from '@praha/byethrow';
class class DatabaseError DatabaseError extends var Error: ErrorConstructor Error {
public override readonly DatabaseError.name: "DatabaseError" name = 'DatabaseError';
constructor(message: string message : string, options: ErrorOptions | undefined options ?: ErrorOptions) {
super(message: string message , options: ErrorOptions | undefined options );
}
}
const const fetchUser: (id: string) => Result.Result<User, DatabaseError> fetchUser = (id: string id : string): import Result Result .type Result<T, E> = Result.Success<T> | Result.Failure<E>A union type representing either a success or a failure.
@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@exampleimport { Result } from '@praha/byethrow';
const doSomething = (): Result.Result<number, string> => {
return Math.random() > 0.5
? { type: 'Success', value: 10 }
: { type: 'Failure', error: 'Oops' };
};
@categoryCore Types Result <type User = {
id: string;
name: string;
}
User , class DatabaseError DatabaseError > => {
try {
// データベース操作...
} catch (function (local var) error: unknown error ) {
// スタックトレースはエラーが発生した正確な場所を示します
return import Result Result .const fail: <DatabaseError>(error: DatabaseError) => Result.Result<never, DatabaseError> (+1 overload) fail (new constructor DatabaseError(message: string, options?: ErrorOptions): DatabaseError DatabaseError ('Failed to fetch user'));
}
};#cause オプションによるエラーチェイン
カスタムErrorクラスは cause オプションをサポートしており、元のエラーコンテキストを保持できます。
import { import Result Result } from '@praha/byethrow';
class class DatabaseError DatabaseError extends var Error: ErrorConstructor Error {
public override readonly DatabaseError.name: "DatabaseError" name = 'DatabaseError';
constructor(message: string message : string, options: ErrorOptions | undefined options ?: ErrorOptions) {
super(message: string message , options: ErrorOptions | undefined options );
}
}
const const fetchUser: (id: string) => Result.Result<User, DatabaseError> fetchUser = (id: string id : string): import Result Result .type Result<T, E> = Result.Success<T> | Result.Failure<E>A union type representing either a success or a failure.
@typeParamT - The type of the Success value.@typeParamE - The type of the Failure value.@exampleimport { Result } from '@praha/byethrow';
const doSomething = (): Result.Result<number, string> => {
return Math.random() > 0.5
? { type: 'Success', value: 10 }
: { type: 'Failure', error: 'Oops' };
};
@categoryCore Types Result <type User = {
id: string;
name: string;
}
User , class DatabaseError DatabaseError > => {
try {
// データベース操作...
} catch (function (local var) error: unknown error ) {
// 元のエラーをcauseとして保持
return import Result Result .const fail: <DatabaseError>(error: DatabaseError) => Result.Result<never, DatabaseError> (+1 overload) fail (new constructor DatabaseError(message: string, options?: ErrorOptions): DatabaseError DatabaseError ('Failed to fetch user', {
ErrorOptions.cause?: unknown cause : function (local var) error: unknown error
}));
}
};#推奨:@praha/error-factory を使用する
カスタムエラークラスを効率的に作成するために、@praha/error-factory の使用をお勧めします。
このライブラリはボイラープレートコードを削減し、一貫したエラー構造を保証します。
#インストール
npm install @praha/error-factoryyarn add @praha/error-factorypnpm add @praha/error-factorybun add @praha/error-factorydeno add npm:@praha/error-factory#Resultとの基本的な使い方
まず、必要なカスタムエラーを定義します。
import { const ErrorFactory: {
<Name extends string = string, Message extends string = string, Fields extends ErrorFields = ErrorFields>(props: {
name?: Name;
message: Message | ((fields: Fields) => Message);
fields?: Fields;
}): ErrorConstructor<Name, Message, Fields>;
fields<Fields extends ErrorFields>(): Fields;
}
A factory function that creates a base class for custom error types.
Extend the returned class to define a custom error with a consistent structure,
reducing boilerplate and ensuring type safety across your application.
@typeParamName - Inferred as a string literal type from props.name when provided,
or defaults to string when name is omitted.@typeParamMessage - Inferred as a string literal type from props.message when it is a string,
or defaults to string when message is a function.@typeParamFields - Inferred from props.fields (via ErrorFactory.fields).
Defaults to the base ErrorFields constraint when fields is omitted.@paramprops - Configuration for the error class.@paramprops .name - The value set as the name property on both the class and each instance.
When omitted, name is inferred as string and set to new.target.name at construction time,
which resolves to the name of the concrete subclass. Note that omitting name disables
type narrowing via the name property; use name explicitly or instanceof for narrowing.@paramprops .message - The error message. Can be a static string or a function that receives
the custom fields and returns a string, enabling dynamic message generation.@paramprops .fields - A type-level placeholder that declares the additional fields the error
instance will carry. Use ErrorFactory.fields to create this value.
When omitted, no additional fields are added to the instance.@returnsAn abstract base class typed as ErrorConstructor that should be extended
to produce a concrete custom error class.@exampleBasic usage
class NotFoundError extends ErrorFactory({
name: 'NotFoundError',
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError"
console.error(error.message); // "Resource not found"
@exampleOmitting name
class NotFoundError extends ErrorFactory({
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError" (resolved from new.target.name)
@exampleWith cause
class DatabaseError extends ErrorFactory({
name: 'DatabaseError',
message: 'A database error occurred',
}) {}
const error = new DatabaseError({ cause: new Error('Connection failed') });
console.error(error.cause); // Error: Connection failed
@exampleWith additional fields
class QueryError extends ErrorFactory({
name: 'QueryError',
message: 'An error occurred while executing a query',
fields: ErrorFactory.fields<{ query: string }>(),
}) {}
const error = new QueryError({ query: 'SELECT * FROM users' });
console.error(error.query); // "SELECT * FROM users"
@exampleDynamic message
class ValidationError extends ErrorFactory({
name: 'ValidationError',
message: ({ field }) => `Validation failed for field '${field}'`,
fields: ErrorFactory.fields<{ field: string }>(),
}) {}
const error = new ValidationError({ field: 'email' });
console.error(error.message); // "Validation failed for field 'email'"
ErrorFactory } from '@praha/error-factory';
class class ValidationError ValidationError extends ErrorFactory<"ValidationError", "Invalid input provided", ErrorFields>(props: {
name?: "ValidationError" | undefined;
message: "Invalid input provided" | ((fields: ErrorFields) => "Invalid input provided");
fields?: ErrorFields | undefined;
}): (new (options?: ErrorOptions) => Error & Readonly<{
name: "ValidationError";
message: "Invalid input provided";
}>) & {
name: "ValidationError";
}
A factory function that creates a base class for custom error types.
Extend the returned class to define a custom error with a consistent structure,
reducing boilerplate and ensuring type safety across your application.
@typeParamName - Inferred as a string literal type from props.name when provided,
or defaults to string when name is omitted.@typeParamMessage - Inferred as a string literal type from props.message when it is a string,
or defaults to string when message is a function.@typeParamFields - Inferred from props.fields (via ErrorFactory.fields).
Defaults to the base ErrorFields constraint when fields is omitted.@paramprops - Configuration for the error class.@paramprops .name - The value set as the name property on both the class and each instance.
When omitted, name is inferred as string and set to new.target.name at construction time,
which resolves to the name of the concrete subclass. Note that omitting name disables
type narrowing via the name property; use name explicitly or instanceof for narrowing.@paramprops .message - The error message. Can be a static string or a function that receives
the custom fields and returns a string, enabling dynamic message generation.@paramprops .fields - A type-level placeholder that declares the additional fields the error
instance will carry. Use ErrorFactory.fields to create this value.
When omitted, no additional fields are added to the instance.@returnsAn abstract base class typed as ErrorConstructor that should be extended
to produce a concrete custom error class.@exampleBasic usage
class NotFoundError extends ErrorFactory({
name: 'NotFoundError',
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError"
console.error(error.message); // "Resource not found"
@exampleOmitting name
class NotFoundError extends ErrorFactory({
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError" (resolved from new.target.name)
@exampleWith cause
class DatabaseError extends ErrorFactory({
name: 'DatabaseError',
message: 'A database error occurred',
}) {}
const error = new DatabaseError({ cause: new Error('Connection failed') });
console.error(error.cause); // Error: Connection failed
@exampleWith additional fields
class QueryError extends ErrorFactory({
name: 'QueryError',
message: 'An error occurred while executing a query',
fields: ErrorFactory.fields<{ query: string }>(),
}) {}
const error = new QueryError({ query: 'SELECT * FROM users' });
console.error(error.query); // "SELECT * FROM users"
@exampleDynamic message
class ValidationError extends ErrorFactory({
name: 'ValidationError',
message: ({ field }) => `Validation failed for field '${field}'`,
fields: ErrorFactory.fields<{ field: string }>(),
}) {}
const error = new ValidationError({ field: 'email' });
console.error(error.message); // "Validation failed for field 'email'"
ErrorFactory ({
name?: "ValidationError" | undefined name : 'ValidationError',
message: "Invalid input provided" | ((fields: ErrorFields) => "Invalid input provided") message : 'Invalid input provided',
}) {}
class class QueryError QueryError extends ErrorFactory<"QueryError", "An error occurred while executing a query", {
query: string;
}>(props: {
name?: "QueryError" | undefined;
message: "An error occurred while executing a query" | ((fields: {
query: string;
}) => "An error occurred while executing a query");
fields?: {
query: string;
} | undefined;
}): (new (options: ErrorOptions & {
query: string;
}) => Error & Readonly<{
name: "QueryError";
message: "An error occurred while executing a query";
}> & Readonly<{
query: string;
}>) & {
name: "QueryError";
}
A factory function that creates a base class for custom error types.
Extend the returned class to define a custom error with a consistent structure,
reducing boilerplate and ensuring type safety across your application.
@typeParamName - Inferred as a string literal type from props.name when provided,
or defaults to string when name is omitted.@typeParamMessage - Inferred as a string literal type from props.message when it is a string,
or defaults to string when message is a function.@typeParamFields - Inferred from props.fields (via ErrorFactory.fields).
Defaults to the base ErrorFields constraint when fields is omitted.@paramprops - Configuration for the error class.@paramprops .name - The value set as the name property on both the class and each instance.
When omitted, name is inferred as string and set to new.target.name at construction time,
which resolves to the name of the concrete subclass. Note that omitting name disables
type narrowing via the name property; use name explicitly or instanceof for narrowing.@paramprops .message - The error message. Can be a static string or a function that receives
the custom fields and returns a string, enabling dynamic message generation.@paramprops .fields - A type-level placeholder that declares the additional fields the error
instance will carry. Use ErrorFactory.fields to create this value.
When omitted, no additional fields are added to the instance.@returnsAn abstract base class typed as ErrorConstructor that should be extended
to produce a concrete custom error class.@exampleBasic usage
class NotFoundError extends ErrorFactory({
name: 'NotFoundError',
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError"
console.error(error.message); // "Resource not found"
@exampleOmitting name
class NotFoundError extends ErrorFactory({
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError" (resolved from new.target.name)
@exampleWith cause
class DatabaseError extends ErrorFactory({
name: 'DatabaseError',
message: 'A database error occurred',
}) {}
const error = new DatabaseError({ cause: new Error('Connection failed') });
console.error(error.cause); // Error: Connection failed
@exampleWith additional fields
class QueryError extends ErrorFactory({
name: 'QueryError',
message: 'An error occurred while executing a query',
fields: ErrorFactory.fields<{ query: string }>(),
}) {}
const error = new QueryError({ query: 'SELECT * FROM users' });
console.error(error.query); // "SELECT * FROM users"
@exampleDynamic message
class ValidationError extends ErrorFactory({
name: 'ValidationError',
message: ({ field }) => `Validation failed for field '${field}'`,
fields: ErrorFactory.fields<{ field: string }>(),
}) {}
const error = new ValidationError({ field: 'email' });
console.error(error.message); // "Validation failed for field 'email'"
ErrorFactory ({
name?: "QueryError" | undefined name : 'QueryError',
message: "An error occurred while executing a query" | ((fields: {
query: string;
}) => "An error occurred while executing a query")
message : 'An error occurred while executing a query',
fields?: {
query: string;
} | undefined
fields : const ErrorFactory: {
<Name extends string = string, Message extends string = string, Fields extends ErrorFields = ErrorFields>(props: {
name?: Name;
message: Message | ((fields: Fields) => Message);
fields?: Fields;
}): ErrorConstructor<Name, Message, Fields>;
fields<Fields extends ErrorFields>(): Fields;
}
A factory function that creates a base class for custom error types.
Extend the returned class to define a custom error with a consistent structure,
reducing boilerplate and ensuring type safety across your application.
@typeParamName - Inferred as a string literal type from props.name when provided,
or defaults to string when name is omitted.@typeParamMessage - Inferred as a string literal type from props.message when it is a string,
or defaults to string when message is a function.@typeParamFields - Inferred from props.fields (via ErrorFactory.fields).
Defaults to the base ErrorFields constraint when fields is omitted.@paramprops - Configuration for the error class.@paramprops .name - The value set as the name property on both the class and each instance.
When omitted, name is inferred as string and set to new.target.name at construction time,
which resolves to the name of the concrete subclass. Note that omitting name disables
type narrowing via the name property; use name explicitly or instanceof for narrowing.@paramprops .message - The error message. Can be a static string or a function that receives
the custom fields and returns a string, enabling dynamic message generation.@paramprops .fields - A type-level placeholder that declares the additional fields the error
instance will carry. Use ErrorFactory.fields to create this value.
When omitted, no additional fields are added to the instance.@returnsAn abstract base class typed as ErrorConstructor that should be extended
to produce a concrete custom error class.@exampleBasic usage
class NotFoundError extends ErrorFactory({
name: 'NotFoundError',
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError"
console.error(error.message); // "Resource not found"
@exampleOmitting name
class NotFoundError extends ErrorFactory({
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError" (resolved from new.target.name)
@exampleWith cause
class DatabaseError extends ErrorFactory({
name: 'DatabaseError',
message: 'A database error occurred',
}) {}
const error = new DatabaseError({ cause: new Error('Connection failed') });
console.error(error.cause); // Error: Connection failed
@exampleWith additional fields
class QueryError extends ErrorFactory({
name: 'QueryError',
message: 'An error occurred while executing a query',
fields: ErrorFactory.fields<{ query: string }>(),
}) {}
const error = new QueryError({ query: 'SELECT * FROM users' });
console.error(error.query); // "SELECT * FROM users"
@exampleDynamic message
class ValidationError extends ErrorFactory({
name: 'ValidationError',
message: ({ field }) => `Validation failed for field '${field}'`,
fields: ErrorFactory.fields<{ field: string }>(),
}) {}
const error = new ValidationError({ field: 'email' });
console.error(error.message); // "Validation failed for field 'email'"
ErrorFactory .fields<{
query: string;
}>(): {
query: string;
}
fields <{ query: string query : string }>(),
}) {}
class class NotFoundError NotFoundError extends ErrorFactory<"NotFoundError", "Resource not found", ErrorFields>(props: {
name?: "NotFoundError" | undefined;
message: "Resource not found" | ((fields: ErrorFields) => "Resource not found");
fields?: ErrorFields | undefined;
}): (new (options?: ErrorOptions) => Error & Readonly<{
name: "NotFoundError";
message: "Resource not found";
}>) & {
name: "NotFoundError";
}
A factory function that creates a base class for custom error types.
Extend the returned class to define a custom error with a consistent structure,
reducing boilerplate and ensuring type safety across your application.
@typeParamName - Inferred as a string literal type from props.name when provided,
or defaults to string when name is omitted.@typeParamMessage - Inferred as a string literal type from props.message when it is a string,
or defaults to string when message is a function.@typeParamFields - Inferred from props.fields (via ErrorFactory.fields).
Defaults to the base ErrorFields constraint when fields is omitted.@paramprops - Configuration for the error class.@paramprops .name - The value set as the name property on both the class and each instance.
When omitted, name is inferred as string and set to new.target.name at construction time,
which resolves to the name of the concrete subclass. Note that omitting name disables
type narrowing via the name property; use name explicitly or instanceof for narrowing.@paramprops .message - The error message. Can be a static string or a function that receives
the custom fields and returns a string, enabling dynamic message generation.@paramprops .fields - A type-level placeholder that declares the additional fields the error
instance will carry. Use ErrorFactory.fields to create this value.
When omitted, no additional fields are added to the instance.@returnsAn abstract base class typed as ErrorConstructor that should be extended
to produce a concrete custom error class.@exampleBasic usage
class NotFoundError extends ErrorFactory({
name: 'NotFoundError',
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError"
console.error(error.message); // "Resource not found"
@exampleOmitting name
class NotFoundError extends ErrorFactory({
message: 'Resource not found',
}) {}
const error = new NotFoundError();
console.error(error.name); // "NotFoundError" (resolved from new.target.name)
@exampleWith cause
class DatabaseError extends ErrorFactory({
name: 'DatabaseError',
message: 'A database error occurred',
}) {}
const error = new DatabaseError({ cause: new Error('Connection failed') });
console.error(error.cause); // Error: Connection failed
@exampleWith additional fields
class QueryError extends ErrorFactory({
name: 'QueryError',
message: 'An error occurred while executing a query',
fields: ErrorFactory.fields<{ query: string }>(),
}) {}
const error = new QueryError({ query: 'SELECT * FROM users' });
console.error(error.query); // "SELECT * FROM users"
@exampleDynamic message
class ValidationError extends ErrorFactory({
name: 'ValidationError',
message: ({ field }) => `Validation failed for field '${field}'`,
fields: ErrorFactory.fields<{ field: string }>(),
}) {}
const error = new ValidationError({ field: 'email' });
console.error(error.message); // "Validation failed for field 'email'"
ErrorFactory ({
name?: "NotFoundError" | undefined name : 'NotFoundError',
message: "Resource not found" | ((fields: ErrorFields) => "Resource not found") message : 'Resource not found',
}) {}次に、先ほど定義したカスタムエラーと Result を一緒に返す関数を作成します。
import { import Result Result } from '@praha/byethrow';
import { import z z } from 'zod';
// Result操作でカスタムエラーを使用
const const validateId: (id: string) => Result.Result<string, ValidationError> validateId = (id: string id : string) => {
return import Result Result .const pipe: <string, Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, Result.Result<string, ValidationError>>(a: string, ab: (a: string) => Result.Result<string, readonly StandardSchemaV1.Issue[]>, bc: (b: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, ValidationError>) => Result.Result<string, ValidationError> (+25 overloads) pipe (
id: string id ,
import Result Result .const parse: <z.ZodString>(schema: z.ZodString) => (value: unknown) => Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]> (+1 overload) parse (import z z .function string(params?: string | z.core.$ZodStringParams): z.ZodString (+1 overload) string ()._ZodString<$ZodStringInternals<string>>.startsWith(value: string, params?: string | z.core.$ZodCheckStartsWithParams): z.ZodString startsWith ('u')),
import Result Result .const mapError: <Result.Result<string, readonly StandardSchemaV1<Input = unknown, Output = Input>.Issue[]>, ValidationError>(fn: (a: readonly StandardSchemaV1.Issue[]) => ValidationError) => (result: Result.Result<string, readonly StandardSchemaV1.Issue[]>) => Result.Result<string, ValidationError> (+1 overload) mapError ((error: readonly StandardSchemaV1.Issue[] error ) => new constructor ValidationError(options?: ErrorOptions): ValidationError ValidationError ({ ErrorOptions.cause?: unknown cause : error: readonly StandardSchemaV1.Issue[] error })),
);
};
const const executeQuery: (sql: string) => Result.ResultAsync<QueryResult, QueryError> executeQuery = (sql: string sql : string) => {
return import Result Result .try<() => Promise<QueryResult>, QueryError>(options: {
try: () => Promise<QueryResult>;
catch: (error: unknown) => QueryError;
}): Result.ResultAsync<QueryResult, QueryError> (+3 overloads)
export try
Executes a function that may throw and wraps the result in a
Result
or
ResultAsync
.
You can use either a custom catch handler or rely on the safe: true option
to assume the function cannot throw.
@function@typeParamT - The function type to execute (sync or async) or a Promise type.@typeParamE - The error type to return if catch is used.@returnsA Result or ResultAsync wrapping the function's return value or the caught error.@exampleSync try-catch
import { Result } from '@praha/byethrow';
const result = Result.try({
try: () => {
const x = Math.random() * 10 - 5;
if (x < 0) throw new Error('Negative!');
return x * 2;
},
catch: (error) => new Error('Oops!', { cause: error }),
});
// result is Result<number, Error>
@exampleSync safe
import { Result } from '@praha/byethrow';
const result = Result.try({
safe: true,
try: () => Math.random() + 1,
});
// result is Result<number, never>
@exampleAsync try-catch
import { Result } from '@praha/byethrow';
const result = Result.try({
try: () => fetch('/api/data'),
catch: (error) => new Error('Fetch failed', { cause: error }),
});
// result is ResultAsync<Response, Error>
@exampleAsync safe
import { Result } from '@praha/byethrow';
const result = Result.try({
safe: true,
try: () => Promise.resolve('ok'),
});
// result is ResultAsync<string, never>
@categoryCreators try ({
try: () => Promise<QueryResult> try : () => const database: {
query: (sql: string) => Promise<QueryResult>;
}
database .query: (sql: string) => Promise<QueryResult> query (sql: string sql ),
catch: (error: unknown) => QueryError catch : (error: unknown error ) => new constructor QueryError(options: ErrorOptions & {
query: string;
}): QueryError
QueryError ({ query: string query : sql: string sql , ErrorOptions.cause?: unknown cause : error: unknown error }),
});
};
// すべてを組み合わせる
const const findUser: (id: string) => Result.ResultAsync<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>
findUser = (id: string id : string) => {
return import Result Result .const pipe: <Result.Result<string, ValidationError>, Result.ResultAsync<QueryResult, ValidationError | QueryError>, Result.ResultAsync<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>>(a: Result.Result<string, ValidationError>, ab: (a: Result.Result<string, ValidationError>) => Result.ResultAsync<QueryResult, ValidationError | QueryError>, bc: (b: Result.ResultAsync<...>) => Result.ResultAsync<...>) => Result.ResultAsync<...> (+25 overloads)
pipe (
const validateId: (id: string) => Result.Result<string, ValidationError> validateId (id: string id ),
import Result Result .const andThen: <Result.Result<string, ValidationError>, Result.ResultAsync<QueryResult, QueryError>>(fn: (a: string) => Result.ResultAsync<QueryResult, QueryError>) => (result: Result.Result<string, ValidationError>) => Result.ResultAsync<QueryResult, ValidationError | QueryError> (+1 overload) andThen ((id: string id ) => const executeQuery: (sql: string) => Result.ResultAsync<QueryResult, QueryError> executeQuery (`SELECT * FROM users WHERE id = '${id: string id }'`)),
import Result Result .const andThen: <Result.ResultAsync<QueryResult, ValidationError | QueryError>, Result.Failure<NotFoundError> | Result.Success<{
readonly id: string;
readonly name: string;
}>>(fn: (a: QueryResult) => Result.Failure<NotFoundError> | Result.Success<{
readonly id: string;
readonly name: string;
}>) => (result: Result.ResultAsync<QueryResult, ValidationError | QueryError>) => Result.ResultAsync<...> (+1 overload)
andThen ((row: QueryResult row ) => {
if (!row: QueryResult row ) {
return import Result Result .const fail: <NotFoundError>(error: NotFoundError) => Result.Result<never, NotFoundError> (+1 overload) fail (new constructor NotFoundError(options?: ErrorOptions): NotFoundError NotFoundError ());
}
return import Result Result .const succeed: <{
readonly id: string;
readonly name: string;
}>(value: {
readonly id: string;
readonly name: string;
}) => Result.Result<{
readonly id: string;
readonly name: string;
}, never> (+1 overload)
succeed ({ id: string id : row: QueryResult row .string id , name: string name : row: QueryResult row .string name });
}),
);
};最後に、関数を実行して適切にハンドリングします。
// 実行してエラーをハンドリング
const const result: Result.Result<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>
result = await const findUser: (id: string) => Result.ResultAsync<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>
findUser ('u123');
if (import Result Result .const isSuccess: <Result.Result<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>>(result: Result.Result<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>) => result is Result.Success<{
readonly id: string;
readonly name: string;
}>
Type guard to check if a
Result
is a
Success
.
@function@typeParamR - The type of the result to check.@paramresult - The Result to check.@returnstrue if the result is a Success , otherwise false.@exampleimport { Result } from '@praha/byethrow';
const result: Result.Result<number, string> = { type: 'Success', value: 10 };
if (Result.isSuccess(result)) {
console.log(result.value); // Safe access to value
}
@categoryType Guards isSuccess (const result: Result.Result<{
readonly id: string;
readonly name: string;
}, ValidationError | QueryError | NotFoundError>
result )) {
var console: Console console .Console.log(...data: any[]): voidThe console.log() static method outputs a message to the console.
log (const result: Result.Success<{
readonly id: string;
readonly name: string;
}>
result .value: {
readonly id: string;
readonly name: string;
}
value );
} else {
// 各エラータイプを個別にハンドリング
switch (const result: Result.Failure<ValidationError | QueryError | NotFoundError> result .error: ValidationError | QueryError | NotFoundError error .Error.name: "ValidationError" | "QueryError" | "NotFoundError" name ) {
case 'ValidationError':
var console: Console console .Console.error(...data: any[]): voidThe console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
error ('Validation error:', const result: Result.Failure<ValidationError | QueryError | NotFoundError> result .error: ValidationError error .Error.message: "Invalid input provided" message );
break;
case 'QueryError':
var console: Console console .Console.error(...data: any[]): voidThe console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
error ('Query error:', const result: Result.Failure<ValidationError | QueryError | NotFoundError> result .error: QueryError error .Error.message: "An error occurred while executing a query" message , 'Query:', const result: Result.Failure<ValidationError | QueryError | NotFoundError> result .error: QueryError error .query: string query );
break;
case 'NotFoundError':
var console: Console console .Console.error(...data: any[]): voidThe console.error() static method outputs a message to the console at the "error" log level. The message is only displayed to the user if the console is configured to display error output. In most cases, the log level is configured within the console UI. The message may be formatted as an error, with red colors and call stack information.
error ('Not found error:', const result: Result.Failure<ValidationError | QueryError | NotFoundError> result .error: NotFoundError error .Error.message: "Resource not found" message );
break;
}
}