Openstatus
www.openstatus.dev
1import { Code, ConnectError } from "@connectrpc/connect";
2
3/**
4 * Error reasons for structured error handling.
5 */
6export const ErrorReason = {
7 MONITOR_NOT_FOUND: "MONITOR_NOT_FOUND",
8 MONITOR_REQUIRED: "MONITOR_REQUIRED",
9 MONITOR_ID_REQUIRED: "MONITOR_ID_REQUIRED",
10 MONITOR_CREATE_FAILED: "MONITOR_CREATE_FAILED",
11 MONITOR_UPDATE_FAILED: "MONITOR_UPDATE_FAILED",
12 MONITOR_PARSE_FAILED: "MONITOR_PARSE_FAILED",
13 MONITOR_RUN_CREATE_FAILED: "MONITOR_RUN_CREATE_FAILED",
14 MONITOR_INVALID_DATA: "MONITOR_INVALID_DATA",
15 MONITOR_TYPE_MISMATCH: "MONITOR_TYPE_MISMATCH",
16 RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
17} as const;
18
19export type ErrorReason = (typeof ErrorReason)[keyof typeof ErrorReason];
20
21const DOMAIN = "openstatus.dev";
22
23/**
24 * Creates a ConnectError with structured metadata.
25 *
26 * This provides machine-parseable error information via metadata headers
27 * while maintaining human-readable error messages.
28 */
29function createError(
30 message: string,
31 code: Code,
32 reason: ErrorReason,
33 metadata?: Record<string, string>,
34): ConnectError {
35 const headers = new Headers({
36 "error-domain": DOMAIN,
37 "error-reason": reason,
38 });
39
40 if (metadata) {
41 for (const [key, value] of Object.entries(metadata)) {
42 headers.set(`error-${key}`, value);
43 }
44 }
45
46 return new ConnectError(message, code, headers);
47}
48
49/**
50 * Creates a "monitor not found" error with the monitor ID in metadata.
51 */
52export function monitorNotFoundError(monitorId: string): ConnectError {
53 return createError(
54 "Monitor not found",
55 Code.NotFound,
56 ErrorReason.MONITOR_NOT_FOUND,
57 { "monitor-id": monitorId },
58 );
59}
60
61/**
62 * Creates a "monitor required" error.
63 */
64export function monitorRequiredError(): ConnectError {
65 return createError(
66 "Monitor is required",
67 Code.InvalidArgument,
68 ErrorReason.MONITOR_REQUIRED,
69 );
70}
71
72/**
73 * Creates a "monitor ID required" error.
74 */
75export function monitorIdRequiredError(): ConnectError {
76 return createError(
77 "Monitor ID is required",
78 Code.InvalidArgument,
79 ErrorReason.MONITOR_ID_REQUIRED,
80 );
81}
82
83/**
84 * Creates a "failed to create monitor" error.
85 */
86export function monitorCreateFailedError(): ConnectError {
87 return createError(
88 "Failed to create monitor",
89 Code.Internal,
90 ErrorReason.MONITOR_CREATE_FAILED,
91 );
92}
93
94/**
95 * Creates a "failed to update monitor" error.
96 */
97export function monitorUpdateFailedError(monitorId: string): ConnectError {
98 return createError(
99 "Failed to update monitor",
100 Code.Internal,
101 ErrorReason.MONITOR_UPDATE_FAILED,
102 { "monitor-id": monitorId },
103 );
104}
105
106/**
107 * Creates a "monitor type mismatch" error when trying to update with wrong type.
108 */
109export function monitorTypeMismatchError(
110 monitorId: string,
111 expectedType: string,
112 actualType: string,
113): ConnectError {
114 return createError(
115 `Monitor type mismatch: expected ${expectedType}, got ${actualType}`,
116 Code.InvalidArgument,
117 ErrorReason.MONITOR_TYPE_MISMATCH,
118 {
119 "monitor-id": monitorId,
120 "expected-type": expectedType,
121 "actual-type": actualType,
122 },
123 );
124}
125
126/**
127 * Creates a "failed to parse monitor data" error.
128 */
129export function monitorParseFailedError(monitorId?: string): ConnectError {
130 return createError(
131 "Failed to parse monitor data",
132 Code.Internal,
133 ErrorReason.MONITOR_PARSE_FAILED,
134 monitorId ? { "monitor-id": monitorId } : undefined,
135 );
136}
137
138/**
139 * Creates a "failed to create monitor run" error.
140 */
141export function monitorRunCreateFailedError(monitorId: string): ConnectError {
142 return createError(
143 "Failed to create monitor run",
144 Code.Internal,
145 ErrorReason.MONITOR_RUN_CREATE_FAILED,
146 { "monitor-id": monitorId },
147 );
148}
149
150/**
151 * Creates an "invalid monitor data" error for corrupted data.
152 */
153export function monitorInvalidDataError(monitorId: string): ConnectError {
154 return createError(
155 "Invalid monitor data, please contact support",
156 Code.Internal,
157 ErrorReason.MONITOR_INVALID_DATA,
158 { "monitor-id": monitorId },
159 );
160}
161
162/**
163 * Creates a rate limit exceeded error.
164 */
165export function rateLimitExceededError(
166 limit: number,
167 current: number,
168): ConnectError {
169 return createError(
170 "Upgrade for more checks",
171 Code.ResourceExhausted,
172 ErrorReason.RATE_LIMIT_EXCEEDED,
173 { limit: String(limit), current: String(current) },
174 );
175}