//! Route binding: which `(host, method, path)` tuples invoke a given //! script. The storage shape (this file) is intentionally flat — the //! orchestrator parses these into typed `HostPattern` / `PathPattern` //! values for matching, and reconstructs strings for display. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::ScriptId; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum HostKind { /// Matches any host header. Any, /// Exact hostname match: `sub.example.com`. Strict, /// Wildcard suffix match: `*.example.com` matches any subdomain of /// `example.com`. Capture name is reserved for the future /// `{name}.example.com` syntax (currently always None on writes). Wildcard, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum PathKind { /// Literal string equality: `/greet` matches only `/greet`. Exact, /// Strict-subtree match. Stored as the prefix including the trailing /// slash; `/greet/*` is stored as path "/greet/" and matches /// `/greet/x` but not `/greet` itself (which would need its own /// exact route). Prefix, /// Named-parameter pattern: `/greet/:name` where each `:foo` consumes /// exactly one segment and captures it into `ctx.request.params.foo`. Param, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Route { pub id: Uuid, pub script_id: ScriptId, pub host_kind: HostKind, /// For `Any`: empty string. For `Strict`: full hostname. For /// `Wildcard`: the suffix after the leading `*.` (e.g. `example.com`). pub host: String, pub host_param_name: Option, pub path_kind: PathKind, /// Raw path as the user typed it. For `Prefix`, normalized to end /// with `/` (the trailing `*` is dropped on write). pub path: String, /// `None` = any method. pub method: Option, pub created_at: DateTime, }