Openstatus
www.openstatus.dev
1import { Code, ConnectError } from "@connectrpc/connect";
2import { monitorPeriodicity } from "@openstatus/db/src/schema/constants";
3import { monitorMethods } from "@openstatus/db/src/schema/monitors/constants";
4import type { Periodicity, Region } from "@openstatus/proto/monitor/v1";
5
6import {
7 MONITOR_DEFAULTS,
8 openTelemetryToDb,
9 periodicityToString,
10 regionsToDbString,
11 regionsToStrings,
12 validateRegions,
13} from "./converters";
14
15type MonitorPeriodicity = (typeof monitorPeriodicity)[number];
16type MonitorMethod = (typeof monitorMethods)[number];
17
18/**
19 * Validate and convert periodicity string to enum type.
20 */
21export function toValidPeriodicity(
22 value: string | undefined,
23): MonitorPeriodicity {
24 const valid = monitorPeriodicity as readonly string[];
25 if (value && valid.includes(value)) {
26 return value as MonitorPeriodicity;
27 }
28 return "1m";
29}
30
31/**
32 * Validate and convert method string to enum type.
33 */
34export function toValidMethod(value: string | undefined): MonitorMethod {
35 const upper = value?.toUpperCase();
36 const valid = monitorMethods as readonly string[];
37 if (upper && valid.includes(upper)) {
38 return upper as MonitorMethod;
39 }
40 return "GET";
41}
42
43/**
44 * Validate required monitor fields common to all monitor types.
45 * Note: name, url/uri, and periodicity are validated by protovalidate interceptor.
46 * Throws ConnectError if validation fails.
47 */
48export function validateCommonMonitorFields(mon: {
49 regions?: Region[];
50}): void {
51 if (mon.regions && mon.regions.length > 0) {
52 const regionStrings = regionsToStrings(mon.regions);
53 const invalidRegions = validateRegions(regionStrings);
54 if (invalidRegions.length > 0) {
55 throw new ConnectError(
56 `Invalid regions: ${invalidRegions.join(", ")}`,
57 Code.InvalidArgument,
58 );
59 }
60 }
61}
62
63/**
64 * Extract common database values for all monitor types.
65 */
66export function getCommonDbValues(mon: {
67 name: string;
68 periodicity?: Periodicity;
69 timeout?: bigint;
70 degradedAt?: bigint;
71 active?: boolean;
72 description?: string;
73 public?: boolean;
74 regions?: Region[];
75 retry?: bigint;
76 openTelemetry?: Parameters<typeof openTelemetryToDb>[0];
77}) {
78 const otelConfig = openTelemetryToDb(mon.openTelemetry);
79
80 const periodicityStr = mon.periodicity
81 ? periodicityToString(mon.periodicity)
82 : undefined;
83 const regionStrings = mon.regions ? regionsToStrings(mon.regions) : [];
84
85 return {
86 name: mon.name,
87 periodicity: toValidPeriodicity(periodicityStr),
88 timeout: mon.timeout ? Number(mon.timeout) : MONITOR_DEFAULTS.timeout,
89 degradedAfter: mon.degradedAt ? Number(mon.degradedAt) : undefined,
90 active: mon.active ?? MONITOR_DEFAULTS.active,
91 description: mon.description || MONITOR_DEFAULTS.description,
92 public: mon.public ?? MONITOR_DEFAULTS.public,
93 regions: regionsToDbString(regionStrings),
94 retry: mon.retry ? Number(mon.retry) : MONITOR_DEFAULTS.retry,
95 otelEndpoint: otelConfig.otelEndpoint,
96 otelHeaders: otelConfig.otelHeaders,
97 };
98}
99
100/**
101 * Extract common database values for update operations.
102 * Only includes fields that are explicitly provided (not undefined).
103 * This enables partial updates where only specified fields are changed.
104 */
105export function getCommonDbValuesForUpdate(mon: {
106 name?: string;
107 periodicity?: Periodicity;
108 timeout?: bigint;
109 degradedAt?: bigint;
110 active?: boolean;
111 description?: string;
112 public?: boolean;
113 regions?: Region[];
114 retry?: bigint;
115 openTelemetry?: Parameters<typeof openTelemetryToDb>[0];
116}) {
117 const result: Record<string, unknown> = {};
118
119 if (mon.name !== undefined && mon.name !== "") {
120 result.name = mon.name;
121 }
122
123 if (mon.periodicity !== undefined && mon.periodicity !== 0) {
124 const periodicityStr = periodicityToString(mon.periodicity);
125 result.periodicity = toValidPeriodicity(periodicityStr);
126 }
127
128 if (mon.timeout !== undefined && mon.timeout !== BigInt(0)) {
129 result.timeout = Number(mon.timeout);
130 }
131
132 if (mon.degradedAt !== undefined) {
133 result.degradedAfter = Number(mon.degradedAt);
134 }
135
136 if (mon.active !== undefined) {
137 result.active = mon.active;
138 }
139
140 if (mon.description !== undefined) {
141 result.description = mon.description;
142 }
143
144 if (mon.public !== undefined) {
145 result.public = mon.public;
146 }
147
148 if (mon.regions !== undefined && mon.regions.length > 0) {
149 const regionStrings = regionsToStrings(mon.regions);
150 result.regions = regionsToDbString(regionStrings);
151 }
152
153 if (mon.retry !== undefined && mon.retry !== BigInt(0)) {
154 result.retry = Number(mon.retry);
155 }
156
157 if (mon.openTelemetry !== undefined) {
158 const otelConfig = openTelemetryToDb(mon.openTelemetry);
159 result.otelEndpoint = otelConfig.otelEndpoint;
160 result.otelHeaders = otelConfig.otelHeaders;
161 }
162
163 return result;
164}