mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
feat: expose zod error (#1474)
This commit is contained in:
parent
bb790ce4d1
commit
81b63f07bb
10 changed files with 56 additions and 22 deletions
|
@ -23,7 +23,7 @@ export default class AppleConnector implements SocialConnector {
|
|||
const result = appleConfigGuard.safeParse(config);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error.message);
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfig, result.error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -41,10 +41,13 @@ export enum ConnectorErrorCodes {
|
|||
|
||||
export class ConnectorError extends Error {
|
||||
public code: ConnectorErrorCodes;
|
||||
public data: unknown;
|
||||
|
||||
constructor(code: ConnectorErrorCodes, message?: string) {
|
||||
constructor(code: ConnectorErrorCodes, data?: unknown) {
|
||||
const message = typeof data === 'string' ? data : 'Connector error occurred.';
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.data = typeof data === 'string' ? { message: data } : data;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,5 +31,6 @@ div.toast {
|
|||
&.error {
|
||||
border: 1px solid var(--color-error);
|
||||
background-color: var(--color-danger-toast-background);
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,9 @@ export class RequestError extends Error {
|
|||
const toastError = async (response: Response) => {
|
||||
try {
|
||||
const data = await response.json<RequestErrorBody>();
|
||||
toast.error(data.message || t('admin_console.errors.unknown_server_error'));
|
||||
toast.error(
|
||||
[data.message, data.details].join('\n') || t('admin_console.errors.unknown_server_error')
|
||||
);
|
||||
} catch {
|
||||
toast.error(t('admin_console.errors.unknown_server_error'));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
import { LogtoErrorCode, LogtoErrorI18nKey } from '@logto/phrases';
|
||||
import { RequestErrorBody, RequestErrorMetadata } from '@logto/schemas';
|
||||
import { conditional, Optional } from '@silverhand/essentials';
|
||||
import i18next from 'i18next';
|
||||
import pick from 'lodash.pick';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
const formatZodError = ({ issues }: ZodError): string[] =>
|
||||
issues.map((issue) => {
|
||||
const base = `Error in key path "${issue.path.map((node) => String(node)).join('.')}": (${
|
||||
issue.code
|
||||
}) `;
|
||||
|
||||
if (issue.code === 'invalid_type') {
|
||||
return base + `Expected ${issue.expected} but received ${issue.received}.`;
|
||||
}
|
||||
|
||||
return base + issue.message;
|
||||
});
|
||||
export default class RequestError extends Error {
|
||||
code: LogtoErrorCode;
|
||||
status: number;
|
||||
|
@ -27,6 +41,10 @@ export default class RequestError extends Error {
|
|||
}
|
||||
|
||||
get body(): RequestErrorBody {
|
||||
return pick(this, 'message', 'code', 'data');
|
||||
return pick(this, 'message', 'code', 'data', 'details');
|
||||
}
|
||||
|
||||
get details(): Optional<string> {
|
||||
return conditional(this.data instanceof ZodError && formatZodError(this.data).join('\n'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,7 @@ export default function koaConnectorErrorHandler<StateT, ContextT>(): Middleware
|
|||
throw error;
|
||||
}
|
||||
|
||||
const { code, message } = error;
|
||||
|
||||
// Original OIDCProvider Error description and details are provided in the data field
|
||||
const data = { message };
|
||||
const { code, data } = error;
|
||||
|
||||
switch (code) {
|
||||
case ConnectorErrorCodes.InsufficientRequestParameters:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { has } from '@silverhand/essentials';
|
||||
import { has, Optional } from '@silverhand/essentials';
|
||||
import { MiddlewareType } from 'koa';
|
||||
import koaBody from 'koa-body';
|
||||
import { IMiddleware, IRouterParamContext } from 'koa-router';
|
||||
|
@ -41,6 +41,18 @@ export const isGuardMiddleware = <Type extends IMiddleware>(
|
|||
): function_ is WithGuardConfig<Type> =>
|
||||
function_.name === 'guardMiddleware' && has(function_, 'config');
|
||||
|
||||
const tryParse = <Output, Definition, Input>(
|
||||
type: 'query' | 'body' | 'params',
|
||||
guard: Optional<ZodType<Output, Definition, Input>>,
|
||||
data: unknown
|
||||
) => {
|
||||
try {
|
||||
return guard?.parse(data);
|
||||
} catch (error: unknown) {
|
||||
throw new RequestError({ code: 'guard.invalid_input', type }, error);
|
||||
}
|
||||
};
|
||||
|
||||
export default function koaGuard<
|
||||
StateT,
|
||||
ContextT extends IRouterParamContext,
|
||||
|
@ -62,16 +74,12 @@ export default function koaGuard<
|
|||
WithGuardedContext<ContextT, GuardQueryT, GuardBodyT, GuardParametersT>,
|
||||
ResponseBodyT
|
||||
> = async (ctx, next) => {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
ctx.guard = {
|
||||
query: query?.parse(ctx.request.query),
|
||||
body: body?.parse(ctx.request.body),
|
||||
params: params?.parse(ctx.params),
|
||||
} as Guarded<GuardQueryT, GuardBodyT, GuardParametersT>; // Have to do this since it's too complicated for TS
|
||||
} catch (error: unknown) {
|
||||
throw new RequestError('guard.invalid_input', error);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
ctx.guard = {
|
||||
query: tryParse('query', query, ctx.request.query),
|
||||
body: tryParse('body', body, ctx.request.body),
|
||||
params: tryParse('params', params, ctx.params),
|
||||
} as Guarded<GuardQueryT, GuardBodyT, GuardParametersT>; // Have to do this since it's too complicated for TS
|
||||
|
||||
return next();
|
||||
};
|
||||
|
|
|
@ -546,7 +546,7 @@ const errors = {
|
|||
jwt_sub_missing: 'Missing `sub` in JWT.',
|
||||
},
|
||||
guard: {
|
||||
invalid_input: 'The request input is invalid.',
|
||||
invalid_input: 'The request {{type}} is invalid.',
|
||||
invalid_pagination: 'The request pagination value is invalid.',
|
||||
},
|
||||
oidc: {
|
||||
|
|
|
@ -524,7 +524,7 @@ const errors = {
|
|||
jwt_sub_missing: 'JWT 缺失 `sub`',
|
||||
},
|
||||
guard: {
|
||||
invalid_input: '请求输入无效',
|
||||
invalid_input: '请求中 {{type}} 无效',
|
||||
invalid_pagination: '分页参数无效',
|
||||
},
|
||||
oidc: {
|
||||
|
|
|
@ -6,4 +6,9 @@ export type RequestErrorMetadata = Record<string, unknown> & {
|
|||
expose?: boolean;
|
||||
};
|
||||
|
||||
export type RequestErrorBody = { message: string; data: unknown; code: LogtoErrorCode };
|
||||
export type RequestErrorBody = {
|
||||
message: string;
|
||||
data: unknown;
|
||||
code: LogtoErrorCode;
|
||||
details?: string;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue