Skip to main content

apimock_routing/
view.rs

1//! Read-only views on routing state for GUI tooling.
2//!
3//! # Stage-1 scope (5.0.0)
4//!
5//! Per the 5.0.0 brief, this module defines the *shape* of the read-only
6//! API that a future GUI will depend on. The types are declared with
7//! their fields and rustdoc-annotated responsibilities; populating them
8//! from a live `RuleSet` is stage-2 work. We deliberately ship the
9//! signatures first so that:
10//!
11//! 1. the GUI work can start against a frozen type surface,
12//! 2. any downstream code (docs site, dashboards) can begin modelling
13//!    against stable identifiers, and
14//! 3. field additions in later stages are additive rather than
15//!    reshaping.
16//!
17//! Every type here is `#[non_exhaustive]` so adding fields later is not
18//! a breaking change.
19//!
20//! # What these types deliberately hide
21//!
22//! A `RouteCatalogSnapshot` does NOT include execution state (compiled
23//! Rhai AST, open file handles, etc.). It is a photograph of the
24//! declarative routing configuration at one moment — the kind of
25//! information a GUI shows in a "routes" panel, not the live runtime.
26
27use serde::Serialize;
28
29/// A complete snapshot of the server's routing configuration at one moment.
30///
31/// # Why a snapshot rather than a live reference
32///
33/// GUIs navigate, filter, and diff. A borrowed live reference would
34/// require the GUI to hold a lock on the running server's state — not
35/// feasible across an async boundary or an IPC channel. Snapshots are
36/// cheap to clone, cheap to send, and never block the server.
37#[derive(Clone, Debug, Serialize)]
38#[non_exhaustive]
39pub struct RouteCatalogSnapshot {
40    /// Rule sets in the same order as they would be evaluated at request time.
41    pub rule_sets: Vec<RuleSetView>,
42    /// Fallback respond dir (file-based zero-config responder).
43    pub fallback_respond_dir: Option<String>,
44}
45
46impl RouteCatalogSnapshot {
47    /// Return a snapshot with no content. Used as the stage-1 placeholder
48    /// and as the "no rule sets configured" shape.
49    pub fn empty() -> Self {
50        Self {
51            rule_sets: Vec::new(),
52            fallback_respond_dir: None,
53        }
54    }
55}
56
57/// GUI-facing view of one rule set.
58#[derive(Clone, Debug, Serialize)]
59#[non_exhaustive]
60pub struct RuleSetView {
61    /// Index within the parent [`RouteCatalogSnapshot::rule_sets`] list.
62    /// Identifies the rule set across edit commands.
63    pub index: usize,
64    /// Source file this rule set was loaded from (relative to the project).
65    pub source_path: String,
66    /// Optional URL path prefix shared across every rule in this set.
67    pub url_path_prefix: Option<String>,
68    /// Optional respond-dir prefix shared across every rule in this set.
69    pub respond_dir_prefix: Option<String>,
70    /// The rules, in evaluation order.
71    pub rules: Vec<RuleView>,
72}
73
74/// GUI-facing view of one rule.
75#[derive(Clone, Debug, Serialize)]
76#[non_exhaustive]
77pub struct RuleView {
78    /// Zero-based index within the parent rule set.
79    pub index: usize,
80    /// Human-readable summary of the match conditions (e.g.
81    /// `"GET /api/v1/users starts_with"`). GUI uses this for list rows.
82    pub when_summary: String,
83    /// The declarative response shape.
84    pub respond: RespondView,
85}
86
87/// GUI-facing view of one response shape.
88#[derive(Clone, Debug, Serialize)]
89#[non_exhaustive]
90pub enum RespondView {
91    /// Serve a file. The path is resolved against the rule set's
92    /// `respond_dir_prefix` at request time.
93    File { path: String, csv_records_key: Option<String> },
94    /// Return a literal text body. `status` is the response code to use
95    /// (defaults to 200 when absent).
96    Text { text: String, status: Option<u16> },
97    /// Return an empty body with just this status code.
98    Status { code: u16 },
99}
100
101/// Shown to the user when they ask "what rule would match *this* request?".
102///
103/// # Why we carry both the match and the non-matches
104///
105/// A GUI debugger doesn't just answer "which rule matched" — it answers
106/// "why didn't the rule I expected match?". Surfacing the mismatches
107/// lets the UI highlight the first failing predicate on each rule.
108#[derive(Clone, Debug, Serialize)]
109#[non_exhaustive]
110pub struct RouteMatchView {
111    /// Matching rule, if any. `None` means the request would fall through
112    /// to the dynamic-route fallback.
113    pub matched: Option<MatchedRule>,
114    /// Every rule the matcher considered before deciding, with the
115    /// reason it was skipped. Order matches evaluation order.
116    pub considered: Vec<MatchConsidered>,
117}
118
119#[derive(Clone, Debug, Serialize)]
120#[non_exhaustive]
121pub struct MatchedRule {
122    pub rule_set_index: usize,
123    pub rule_index: usize,
124}
125
126#[derive(Clone, Debug, Serialize)]
127#[non_exhaustive]
128pub struct MatchConsidered {
129    pub rule_set_index: usize,
130    pub rule_index: usize,
131    /// Free-form text describing why this rule was skipped
132    /// (e.g. `"url_path mismatch"`, `"header 'authorization' missing"`).
133    pub reason: String,
134}
135
136/// Summary of every validation issue found in a [`RouteCatalogSnapshot`].
137///
138/// `ok` iff `issues` is empty; a GUI can render `ok = true` as a green
139/// banner and iterate `issues` otherwise.
140#[derive(Clone, Debug, Serialize)]
141#[non_exhaustive]
142pub struct RouteValidation {
143    pub ok: bool,
144    pub issues: Vec<RouteValidationIssue>,
145}
146
147impl RouteValidation {
148    pub fn ok() -> Self {
149        Self {
150            ok: true,
151            issues: Vec::new(),
152        }
153    }
154}
155
156#[derive(Clone, Debug, Serialize)]
157#[non_exhaustive]
158pub struct RouteValidationIssue {
159    /// Which rule-set this issue came from.
160    pub rule_set_index: usize,
161    /// Which rule within the set, if the issue is rule-scoped.
162    pub rule_index: Option<usize>,
163    /// Severity as the GUI should render it.
164    pub severity: ValidationSeverity,
165    /// Human-readable description.
166    pub message: String,
167}
168
169#[derive(Clone, Copy, Debug, Serialize)]
170pub enum ValidationSeverity {
171    Error,
172    Warning,
173}