Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
10 kB ยท 412 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413import { JSONPath } from "jsonpath-plus";import { z } from "zod";
import { isDnsAssertionRequest, isHttpAssertionRequest } from "./type-guards";import type { Assertion, AssertionRequest, AssertionResult } from "./types";
export const stringCompare = z.enum([ "contains", "not_contains", "eq", "not_eq", "empty", "not_empty", "gt", "gte", "lt", "lte",]);export const numberCompare = z.enum(["eq", "not_eq", "gt", "gte", "lt", "lte"]);
export const recordCompare = z.enum([ "contains", "not_contains", "eq", "not_eq",]);
function evaluateNumber( value: number, compare: z.infer<typeof numberCompare>, target: number,): AssertionResult { switch (compare) { case "eq": if (value !== target) { return { success: false, message: `Expected ${value} to be equal to ${target}`, }; } break; case "not_eq": if (value === target) { return { success: false, message: `Expected ${value} to not be equal to ${target}`, }; } break; case "gt": if (value <= target) { return { success: false, message: `Expected ${value} to be greater than ${target}`, }; } break; case "gte": if (value < target) { return { success: false, message: `Expected ${value} to be greater than or equal to ${target}`, }; } break; case "lt": if (value >= target) { return { success: false, message: `Expected ${value} to be less than ${target}`, }; } break; case "lte": if (value > target) { return { success: false, message: `Expected ${value} to be less than or equal to ${target}`, }; } break; } return { success: true };}
function evaluateString( value: string, compare: z.infer<typeof stringCompare>, target: string,): AssertionResult { switch (compare) { case "contains": if (!value.includes(target)) { return { success: false, message: `Expected ${value} to contain ${target}`, }; } break; case "not_contains": if (value.includes(target)) { return { success: false, message: `Expected ${value} to not contain ${target}`, }; } break; case "empty": if (value !== "") { return { success: false, message: `Expected ${value} to be empty` }; } break; case "not_empty": if (value === "") { return { success: false, message: `Expected ${value} to not be empty` }; } break; case "eq": if (value !== target) { return { success: false, message: `Expected ${value} to be equal to ${target}`, }; } break; case "not_eq": if (value === target) { return { success: false, message: `Expected ${value} to not be equal to ${target}`, }; } break; case "gt": if (value <= target) { return { success: false, message: `Expected ${value} to be greater than ${target}`, }; } break; case "gte": if (value < target) { return { success: false, message: `Expected ${value} to be greater than or equal to ${target}`, }; } break; case "lt": if (value >= target) { return { success: false, message: `Expected ${value} to be less than ${target}`, }; } break; case "lte": if (value > target) { return { success: false, message: `Expected ${value} to be less than or equal to ${target}`, }; } break; } return { success: true };}
function evaluateRecord( values: string[], compare: z.infer<typeof recordCompare>, target: string,): AssertionResult { const valuesString = values.join(", ");
switch (compare) { case "contains": if (!values.some((v) => v.includes(target))) { return { success: false, message: `Expected DNS records [${valuesString}] to contain ${target}`, }; } break; case "not_contains": if (values.some((v) => v.includes(target))) { return { success: false, message: `Expected DNS records [${valuesString}] to not contain ${target}`, }; } break; case "eq": if (!values.includes(target)) { return { success: false, message: `Expected DNS records [${valuesString}] to equal ${target}`, }; } break; case "not_eq": if (values.includes(target)) { return { success: false, message: `Expected DNS records [${valuesString}] to not equal ${target}`, }; } break; } return { success: true };}
export const base = z.looseObject({ version: z.enum(["v1"]).prefault("v1"), type: z.string(),});export const statusAssertion = base.extend( z.object({ type: z.literal("status"), compare: numberCompare, target: z.int().positive(), }).shape,);
export const headerAssertion = base.extend( z.object({ type: z.literal("header"), compare: stringCompare, key: z.string(), target: z.string(), }).shape,);
export const textBodyAssertion = base.extend( z.object({ type: z.literal("textBody"), compare: stringCompare, target: z.string(), }).shape,);
export const jsonBodyAssertion = base.extend( z.object({ type: z.literal("jsonBody"), path: z.string(), // https://www.npmjs.com/package/jsonpath-plus compare: stringCompare, target: z.string(), }).shape,);
export const dnsRecords = ["A", "AAAA", "CNAME", "MX", "TXT", "NS"] as const;
export const recordAssertion = base.extend( z.object({ type: z.literal("dnsRecord"), key: z.enum(dnsRecords), compare: recordCompare, target: z.string(), }).shape,);
export const assertion = z.discriminatedUnion("type", [ statusAssertion, headerAssertion, textBodyAssertion, jsonBodyAssertion, recordAssertion,]);
export class StatusAssertion implements Assertion { readonly schema: z.infer<typeof statusAssertion>;
constructor(schema: z.infer<typeof statusAssertion>) { this.schema = schema; }
public assert(req: AssertionRequest): AssertionResult { if (!isHttpAssertionRequest(req)) { return { success: false, message: "Invalid request type for status assertion", }; } const { success, message } = evaluateNumber( req.status, this.schema.compare, this.schema.target, ); if (success) { return { success }; } return { success, message: `Status: ${message}` }; }}
export class HeaderAssertion implements Assertion { readonly schema: z.infer<typeof headerAssertion>;
constructor(schema: z.infer<typeof headerAssertion>) { this.schema = schema; }
public assert(req: AssertionRequest): AssertionResult { if (!isHttpAssertionRequest(req)) { return { success: false, message: "Invalid request type for header assertion", }; }
const key = this.schema.key.toLowerCase(); const value = Object.entries(req.header).find( ([name]) => name.toLowerCase() === key, )?.[1];
const { success, message } = evaluateString( value ?? "", this.schema.compare, this.schema.target, ); if (success) { return { success }; } return { success, message: `Header ${this.schema.key}: ${message}` }; }}
export class TextBodyAssertion implements Assertion { readonly schema: z.infer<typeof textBodyAssertion>;
constructor(schema: z.infer<typeof textBodyAssertion>) { this.schema = schema; }
public assert(req: AssertionRequest): AssertionResult { if (!isHttpAssertionRequest(req)) { return { success: false, message: "Invalid request type for text body assertion", }; } const { success, message } = evaluateString( req.body, this.schema.compare, this.schema.target, ); if (success) { return { success }; } return { success, message: `Body: ${message}` }; }}export class JsonBodyAssertion implements Assertion { readonly schema: z.infer<typeof jsonBodyAssertion>;
constructor(schema: z.infer<typeof jsonBodyAssertion>) { this.schema = schema; }
public assert(req: AssertionRequest): AssertionResult { if (!isHttpAssertionRequest(req)) { return { success: false, message: "Invalid request type for JSON body assertion", }; } try { const json = JSON.parse(req.body); const value = JSONPath({ path: this.schema.path, json }); const { success, message } = evaluateString( value, this.schema.compare, this.schema.target, ); if (success) { return { success }; } return { success, message: `Body: ${message}` }; } catch (_e) { console.error("Unable to parse json"); return { success: false, message: "Unable to parse json" }; } }}
export class DnsRecordAssertion implements Assertion { readonly schema: z.infer<typeof recordAssertion>;
constructor(schema: z.infer<typeof recordAssertion>) { this.schema = schema; }
public assert(req: AssertionRequest): AssertionResult { if (!isDnsAssertionRequest(req)) { return { success: false, message: "Invalid request type for DNS record assertion", }; } const records = req.records[this.schema.key] || []; const { success, message } = evaluateRecord( records, this.schema.compare, this.schema.target, ); if (success) { return { success }; } return { success, message: `DNS Record ${this.schema.key}: ${message}` }; }}