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