Import source: backend/src/lib/envelope
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,105 @@
|
|||||||
|
import type { FastifyReply, FastifyRequest } from "fastify";
|
||||||
|
|
||||||
|
export type ApiErrorCode =
|
||||||
|
| "VALIDATION_ERROR"
|
||||||
|
| "PERMISSION_DENIED"
|
||||||
|
| "NETWORK_ERROR"
|
||||||
|
| "MAINTENANCE"
|
||||||
|
| "NOT_FOUND"
|
||||||
|
| "PARTIAL_FAILURE"
|
||||||
|
| "RATE_LIMIT_ERROR"
|
||||||
|
| "METHOD_NOT_ALLOWED";
|
||||||
|
|
||||||
|
export type ApiError = {
|
||||||
|
code: ApiErrorCode;
|
||||||
|
message: string;
|
||||||
|
fields?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiMeta = {
|
||||||
|
requestId: string;
|
||||||
|
latencyMs?: number;
|
||||||
|
pagination?: {
|
||||||
|
page: number;
|
||||||
|
pageSize: number;
|
||||||
|
total: number;
|
||||||
|
pageCount: number;
|
||||||
|
};
|
||||||
|
rateLimit?: {
|
||||||
|
limit: number;
|
||||||
|
remaining: number;
|
||||||
|
reset: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function requestId(request: FastifyRequest) {
|
||||||
|
return (request.headers["x-request-id"] as string | undefined) || request.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ok<T>(request: FastifyRequest, data: T, meta: Partial<ApiMeta> = {}) {
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
meta: {
|
||||||
|
requestId: requestId(request),
|
||||||
|
...meta,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fail(
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
statusCode: number,
|
||||||
|
error: ApiError,
|
||||||
|
meta: Partial<ApiMeta> = {},
|
||||||
|
) {
|
||||||
|
return reply.status(statusCode).send({
|
||||||
|
data: null,
|
||||||
|
meta: {
|
||||||
|
requestId: requestId(request),
|
||||||
|
...meta,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiException extends Error {
|
||||||
|
statusCode: number;
|
||||||
|
code: ApiErrorCode;
|
||||||
|
fields?: Record<string, string>;
|
||||||
|
meta?: Partial<ApiMeta>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
statusCode: number,
|
||||||
|
code: ApiErrorCode,
|
||||||
|
message: string,
|
||||||
|
options: { fields?: Record<string, string>; meta?: Partial<ApiMeta> } = {},
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
this.code = code;
|
||||||
|
this.fields = options.fields;
|
||||||
|
this.meta = options.meta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validationError(message: string, fields: Record<string, string>) {
|
||||||
|
return new ApiException(400, "VALIDATION_ERROR", message, { fields });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function permissionDenied(message = "Authentication required to access this resource.") {
|
||||||
|
return new ApiException(401, "PERMISSION_DENIED", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forbidden(message = "This account is not authorized for this operation.") {
|
||||||
|
return new ApiException(403, "PERMISSION_DENIED", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function notFound(message: string) {
|
||||||
|
return new ApiException(404, "NOT_FOUND", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function conflict(message: string, fields: Record<string, string>) {
|
||||||
|
return new ApiException(409, "VALIDATION_ERROR", message, { fields });
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user