Openstatus www.openstatus.dev
at main 175 lines 4.4 kB view raw
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}