@ deliverart/ sdk-js- error- 手持器
交付艺术 JavaScript SDK 的处理插件出错 。 自动处理 API 出错,并将其转换为已键入的例外.
安装
npm install @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
# or
pnpm add @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
# or
yarn add @deliverart/sdk-js-error-handler @deliverart/sdk-js-core
导出类型
错误汉德勒普吕金
class ErrorHandlerPlugin implements ApiClientPlugin<{}>
用于截取 API 响应并转换出出出错响应的插件 .
构造器 :
constructor()- 不需要配置
痛苦
class ApiError extends Error {
readonly name = 'ApiError'
readonly code?: string
readonly request?: RequestInit
readonly response?: Response
readonly status?: number
readonly data?: ApiErrorData
}
API 错误的主要错误类.
属性 :
name: 'ApiError'- 错误名称code?: string- API错误代码request?: RequestInit- 原始请求response?: Response- HTTP 响应对象status?: number- HTTP状态代码data?: ApiErrorData- 输入错误数据
ApiErrorData 数据
type ApiErrorData =
| {
type: 'VALIDATION_ERROR'
value: ValidationErrorData
}
| {
type: 'UNKNOWN_ERROR'
value: unknown
}
对不同的错误类型进行区分的结合类型.
评估 错误 - 在API回复422状态时返回(验证失败)
value: ValidationErrorData- 含有违反审定规定的情况
未知错误 - 返回处理所有其他错误
value: unknown- 包含原始错误反应
校验 ErrorData
interface ValidationErrorData {
status: number
violations: Array<{
propertyPath: string
message: string
code: string | null
}>
detail: string
type: string
title: string
}
API结构化验证错误数据.
属性 :
status: number- HTTP状态代码(422)violations: Array- 违反审定清单propertyPath: string- 无法验证的外地路径(例如“电子邮件”、“地址.city”)message: string- 人类可读错误信息code: string | null- 验证错误代码
detail: string- 详细错误描述type: string- 错误类型标识符title: string- 错误标题
窗体状态
type FormState<T> =
| {
success: true
data: T
}
| {
success: false
error: ApiErrorData
}
处理表单提交状态的工具类型( 用于 React 服务器动作) .
窗体状态错误
class FormStateError extends Error {
constructor(public readonly error: ApiErrorData)
}
窗体状态错误的错误包装器 .
使用量
基本设置
import { createApiClient } from '@deliverart/sdk-js-core';
import { ErrorHandlerPlugin } from '@deliverart/sdk-js-error-handler';
const client = createApiClient({
baseUrl: 'https://api.deliverart.com'
})
.addPlugin(new ErrorHandlerPlugin());
处理校验错误
import { ApiError } from '@deliverart/sdk-js-error-handler';
import { CreateUser } from './requests';
try {
await client.call(new CreateUser({
email: 'invalid-email',
name: ''
}));
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
const violations = error.data.value.violations;
violations.forEach(violation => {
console.error(`${violation.propertyPath}: ${violation.message}`);
});
// Output:
// email: This value is not a valid email address.
// name: This value should not be blank.
}
}
在窗体中显示校验错误
反应示例
import { useState } from 'react';
import { ApiError } from '@deliverart/sdk-js-error-handler';
function CreateUserForm() {
const [errors, setErrors] = useState<Record<string, string>>({});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setErrors({});
try {
await client.call(new CreateUser(formData));
// Success!
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
const newErrors: Record<string, string> = {};
error.data.value.violations.forEach(violation => {
newErrors[violation.propertyPath] = violation.message;
});
setErrors(newErrors);
}
}
};
return (
<form onSubmit={handleSubmit}>
<input name="email" />
{errors.email && <span className="error">{errors.email}</span>}
<input name="name" />
{errors.name && <span className="error">{errors.name}</span>}
<button type="submit">Create User</button>
</form>
);
}
处理不同的 HTTP 状态代码
import { ApiError } from '@deliverart/sdk-js-error-handler';
try {
await client.call(new GetUser('123'));
} catch (error) {
if (error instanceof ApiError) {
switch (error.status) {
case 400:
console.error('Bad Request:', error.message);
break;
case 401:
console.error('Unauthorized - please login');
window.location.href = '/login';
break;
case 403:
console.error('Forbidden - insufficient permissions');
break;
case 404:
console.error('User not found');
break;
case 422:
// Validation error - handled above
break;
case 500:
console.error('Server error');
break;
default:
console.error('Unknown error:', error);
}
}
}
使用带有反应服务器动作的窗体状态
FormState对 Next.js 服务器动作特别有用:
// app/actions/create-user.ts
'use server';
import { throwableToFormState, FormState } from '@deliverart/sdk-js-error-handler';
import { sdk } from '@/lib/sdk';
import { CreateUser } from '@deliverart/sdk-js-user';
export async function createUserAction(
formData: FormData
): Promise<FormState<{ id: string }>> {
return throwableToFormState(async () => {
const result = await sdk.call(new CreateUser({
name: formData.get('name') as string,
email: formData.get('email') as string,
}));
return { id: result.id };
});
}
// app/components/CreateUserForm.tsx
'use client';
import { useFormState } from 'react-dom';
import { createUserAction } from '../actions/create-user';
export function CreateUserForm() {
const [state, formAction] = useFormState(createUserAction, null);
return (
<form action={formAction}>
<input name="name" required />
{state?.success === false &&
state.error.type === 'VALIDATION_ERROR' &&
state.error.value.violations
.filter(v => v.propertyPath === 'name')
.map(v => <span key={v.code} className="error">{v.message}</span>)
}
<input name="email" type="email" required />
{state?.success === false &&
state.error.type === 'VALIDATION_ERROR' &&
state.error.value.violations
.filter(v => v.propertyPath === 'email')
.map(v => <span key={v.code} className="error">{v.message}</span>)
}
<button type="submit">Create User</button>
{state?.success === true && (
<p className="success">User created with ID: {state.data.id}</p>
)}
</form>
);
}
全局错误处理器
为所有 API 错误设置全局错误处理器 :
// lib/error-handler.ts
import { ApiError } from '@deliverart/sdk-js-error-handler';
export function handleApiError(error: unknown) {
if (!(error instanceof ApiError)) {
console.error('Non-API error:', error);
return;
}
// Log to error tracking service (e.g., Sentry)
console.error('API Error:', {
status: error.status,
code: error.code,
message: error.message,
data: error.data,
});
// Show user-friendly message
if (error.status === 401) {
alert('Please login to continue');
window.location.href = '/login';
} else if (error.status === 403) {
alert('You do not have permission to perform this action');
} else if (error.status === 404) {
alert('The requested resource was not found');
} else if (error.data?.type === 'VALIDATION_ERROR') {
const firstViolation = error.data.value.violations[0];
alert(`Validation error: ${firstViolation.message}`);
} else {
alert('An unexpected error occurred. Please try again.');
}
}
// Usage in your app
try {
await client.call(request);
} catch (error) {
handleApiError(error);
}
类型警卫
创建清洁错误处理的类型保护 :
import { ApiError, ApiErrorData } from '@deliverart/sdk-js-error-handler';
function isValidationError(error: unknown): error is ApiError & {
data: { type: 'VALIDATION_ERROR', value: ValidationErrorData }
} {
return error instanceof ApiError &&
error.data?.type === 'VALIDATION_ERROR';
}
function isUnauthorizedError(error: unknown): error is ApiError {
return error instanceof ApiError && error.status === 401;
}
function isNotFoundError(error: unknown): error is ApiError {
return error instanceof ApiError && error.status === 404;
}
// Usage
try {
await client.call(request);
} catch (error) {
if (isValidationError(error)) {
// TypeScript knows error.data.value is ValidationErrorData
error.data.value.violations.forEach(v => {
console.log(v.propertyPath, v.message);
});
} else if (isUnauthorizedError(error)) {
redirectToLogin();
} else if (isNotFoundError(error)) {
show404Page();
}
}
将窗体状态错误转换为例外
import { formStateErrorToThrowable, FormState } from '@deliverart/sdk-js-error-handler';
const result: FormState<User> = await createUserAction(formData);
if (!result.success) {
// Convert FormState error back to throwable error
throw formStateErrorToThrowable(result.error);
}
// TypeScript knows result.data is User
console.log(result.data.id);
如何运作
插件增加了一个响应中间软件:
- 检查响应是否正确( E)
!response.ok(中文(简体) ) - 如果状态为 422, 尝试将响应解析为验证错误
- 如果解析成功,则投掷
ApiError与VALIDATION_ERROR类型 - 对于所有其他错误反应,丢弃
ApiError与UNKNOWN_ERROR类型 - 包括错误中的原始请求、响应和解析体
最佳做法
1. 集中处理出错
为整个应用程序的一致错误处理创建工具函数 :
export function getErrorMessage(error: unknown): string {
if (error instanceof ApiError) {
if (error.data?.type === 'VALIDATION_ERROR') {
const firstViolation = error.data.value.violations[0];
return firstViolation?.message ?? 'Validation failed';
}
return error.message;
}
if (error instanceof Error) {
return error.message;
}
return 'An unexpected error occurred';
}
2. 实地一级误差绘图
创建一个用于映射违法行为以形成字段的助手 :
import { ValidationErrorData } from '@deliverart/sdk-js-error-handler';
export function violationsToFieldErrors(
violations: ValidationErrorData['violations']
): Record<string, string> {
return violations.reduce((acc, violation) => {
acc[violation.propertyPath] = violation.message;
return acc;
}, {} as Record<string, string>);
}
// Usage
if (error.data?.type === 'VALIDATION_ERROR') {
const fieldErrors = violationsToFieldErrors(error.data.value.violations);
setErrors(fieldErrors);
}
3. 托拉斯通知
与土司通知库合并:
import { toast } from 'sonner';
import { ApiError } from '@deliverart/sdk-js-error-handler';
try {
await client.call(request);
toast.success('Operation completed successfully');
} catch (error) {
if (error instanceof ApiError && error.data?.type === 'VALIDATION_ERROR') {
error.data.value.violations.forEach(v => {
toast.error(v.message);
});
} else {
toast.error('An error occurred');
}
}
许可证
麻省理工学院
安全诊断
ApiError 保持原样 request, (中文(简体) ), response, (中文(简体) ), data, (中文(简体) ), code, (中文(简体) ), status 和可以读取给输入的消费者和窗体状态转换的信息。 运输物体、生体和状态/代码字段是不可假定的。 默认节点检查,字符串格式化和JSON序列化只暴露出一个允许的列表诊断:稳定的分类,HTTP状态/代码,方法和一个路径名,没有查询,碎片,来源或用户信息. 插件从实际请求上下文中抓取路径名 。 实地审定信息仍然有方案,没有列入这些诊断.
不记录原始 error.request, (中文(简体) ), error.response, (中文(简体) ), error.data,自定义调用器消息或任意堆栈/例外序列器。 此 SDK 行为不会将 Next.js 访问日志, OAuth 回调 URL 在别处登录, 或独立遥测请求上下文 .
重点建设-工艺测试进行无害环境管理和普通JS诊断、秘密遗漏、实际反应中间软件和形式状态相容性。 构建此软件包及其工作空间依赖, 然后运行 pnpm --filter @deliverart/sdk-js-error-handler test,不添加测试跑者依赖性:测试使用节点内置跑者.