Skip to content

Commit

Permalink
feat: expose zod error (#1474)
Browse files Browse the repository at this point in the history
  • Loading branch information
gao-sun authored Jul 8, 2022
1 parent bb790ce commit 81b63f0
Show file tree
Hide file tree
Showing 10 changed files with 56 additions and 22 deletions.
2 changes: 1 addition & 1 deletion packages/connector-apple/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};

Expand Down
5 changes: 4 additions & 1 deletion packages/connector-types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/console/src/components/Toast/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ div.toast {
&.error {
border: 1px solid var(--color-error);
background-color: var(--color-danger-toast-background);
white-space: pre-line;
}
}
4 changes: 3 additions & 1 deletion packages/console/src/hooks/use-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
}
Expand Down
20 changes: 19 additions & 1 deletion packages/core/src/errors/RequestError/index.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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'));
}
}
5 changes: 1 addition & 4 deletions packages/core/src/middleware/koa-connector-error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
30 changes: 19 additions & 11 deletions packages/core/src/middleware/koa-guard.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -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();
};
Expand Down
2 changes: 1 addition & 1 deletion packages/phrases/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion packages/phrases/src/locales/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ const errors = {
jwt_sub_missing: 'JWT 缺失 `sub`',
},
guard: {
invalid_input: '请求输入无效',
invalid_input: '请求中 {{type}} 无效',
invalid_pagination: '分页参数无效',
},
oidc: {
Expand Down
7 changes: 6 additions & 1 deletion packages/schemas/src/api/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

0 comments on commit 81b63f0

Please sign in to comment.