Skip to main content

mii_http/
spec.rs

1//! AST for the .http specs file.
2
3use std::collections::BTreeMap;
4use std::ops::Range;
5
6pub type Span = Range<usize>;
7
8#[derive(Debug, Clone)]
9pub struct Spec {
10    pub setup: Setup,
11    pub endpoints: Vec<Endpoint>,
12}
13
14#[derive(Debug, Clone, Default)]
15pub struct Setup {
16    pub version: Option<u32>,
17    pub base: Option<String>,
18    pub auth: Option<AuthSpec>,
19    pub jwt_verifier: Option<ValueSource>,
20    pub token_secret: Option<ValueSource>,
21    pub max_body_size: Option<u64>,
22    pub max_query_param_size: Option<u64>,
23    pub max_header_size: Option<u64>,
24    pub timeout_ms: Option<u64>,
25    /// span of the setup region (for diagnostics)
26    pub span: Span,
27}
28
29#[derive(Debug, Clone)]
30pub enum AuthSpec {
31    /// Bearer token expected in given header name.
32    BearerHeader { header: String, span: Span },
33}
34
35#[derive(Debug, Clone)]
36pub enum ValueSource {
37    Env { name: String, span: Span },
38    Header { name: String, span: Span },
39    Literal { value: String, span: Span },
40}
41
42impl ValueSource {
43    pub fn span(&self) -> Span {
44        match self {
45            ValueSource::Env { span, .. }
46            | ValueSource::Header { span, .. }
47            | ValueSource::Literal { span, .. } => span.clone(),
48        }
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
53pub enum Method {
54    Get,
55    Post,
56    Put,
57    Delete,
58    Patch,
59}
60
61impl Method {
62    pub fn as_str(self) -> &'static str {
63        match self {
64            Method::Get => "GET",
65            Method::Post => "POST",
66            Method::Put => "PUT",
67            Method::Delete => "DELETE",
68            Method::Patch => "PATCH",
69        }
70    }
71}
72
73#[derive(Debug, Clone)]
74pub struct Endpoint {
75    pub method: Method,
76    pub path: String,
77    /// Parsed segments of the path: literal or `:name` typed param.
78    pub path_segments: Vec<PathSegment>,
79    pub response_type: Option<String>,
80    pub query_params: Vec<NamedField>,
81    pub headers: Vec<NamedField>,
82    pub vars: Vec<VarDef>,
83    pub body: Option<BodySpec>,
84    pub exec: ExecSpec,
85    pub span: Span,
86}
87
88#[derive(Debug, Clone)]
89pub enum PathSegment {
90    Literal(String),
91    Param { name: String, ty: TypeExpr, span: Span },
92}
93
94#[derive(Debug, Clone)]
95pub struct NamedField {
96    pub name: String,
97    pub optional: bool,
98    pub ty: TypeExpr,
99    pub span: Span,
100}
101
102#[derive(Debug, Clone)]
103pub struct VarDef {
104    pub name: String,
105    pub source: ValueSource,
106    pub span: Span,
107}
108
109#[derive(Debug, Clone)]
110pub enum BodySpec {
111    /// Raw textual body, no schema (`BODY json` unschematized, `BODY string`).
112    Json { schema: Option<JsonSchema>, span: Span },
113    Form { fields: Vec<NamedField>, span: Span },
114    String { span: Span },
115    Binary { span: Span },
116}
117
118#[derive(Debug, Clone)]
119pub struct JsonSchema {
120    pub fields: Vec<JsonField>,
121}
122
123#[derive(Debug, Clone)]
124pub struct JsonField {
125    pub name: String,
126    pub optional: bool,
127    pub ty: JsonFieldType,
128    pub span: Span,
129}
130
131#[derive(Debug, Clone)]
132pub enum JsonFieldType {
133    Scalar(TypeExpr),
134    Array(TypeExpr),
135}
136
137#[derive(Debug, Clone)]
138pub enum TypeExpr {
139    Int,
140    Float,
141    Boolean,
142    Uuid,
143    String,
144    Json,
145    Binary,
146    IntRange { min: i64, max: i64, span: Span },
147    FloatRange { min: f64, max: f64, span: Span },
148    Union { variants: Vec<String>, span: Span },
149    Regex { pattern: String, span: Span },
150}
151
152impl TypeExpr {
153    pub fn name(&self) -> &'static str {
154        match self {
155            TypeExpr::Int => "int",
156            TypeExpr::Float => "float",
157            TypeExpr::Boolean => "boolean",
158            TypeExpr::Uuid => "uuid",
159            TypeExpr::String => "string",
160            TypeExpr::Json => "json",
161            TypeExpr::Binary => "binary",
162            TypeExpr::IntRange { .. } => "int(range)",
163            TypeExpr::FloatRange { .. } => "float(range)",
164            TypeExpr::Union { .. } => "union",
165            TypeExpr::Regex { .. } => "regex",
166        }
167    }
168}
169
170#[derive(Debug, Clone)]
171pub struct ExecSpec {
172    pub raw: String,
173    pub span: Span,
174    pub pipeline: Vec<ExecStage>,
175}
176
177/// A pipeline stage: either a value source piped to next, or a command.
178#[derive(Debug, Clone)]
179pub enum ExecStage {
180    /// A bare value reference (e.g. `$`, `$.path`, `%name`) used as stdin into next stage.
181    Source { reference: ValueRef, span: Span },
182    Command { tokens: Vec<ExecToken>, span: Span },
183}
184
185#[derive(Debug, Clone)]
186pub enum ExecToken {
187    /// A token built from text + `{...}` interpolations. Always emitted.
188    Text { parts: Vec<TextPart>, span: Span },
189    /// A `[...]` group: zero or more pieces; if any required interp is missing, omit whole group.
190    Group { pieces: Vec<GroupPiece>, span: Span },
191}
192
193#[derive(Debug, Clone)]
194pub enum TextPart {
195    Literal(String),
196    Interp(ValueRef),
197}
198
199#[derive(Debug, Clone)]
200pub struct GroupPiece {
201    pub parts: Vec<TextPart>,
202}
203
204#[derive(Debug, Clone)]
205pub enum ValueRef {
206    Query(String),
207    Path(String),
208    Header(String),
209    Var(String),
210    /// Whole body or a JSON path into the body. Empty path = whole body.
211    Body { path: Vec<String> },
212}
213
214impl ValueRef {
215    pub fn describe(&self) -> String {
216        match self {
217            ValueRef::Query(n) => format!("query param `{}`", n),
218            ValueRef::Path(n) => format!("path param `{}`", n),
219            ValueRef::Header(n) => format!("header `{}`", n),
220            ValueRef::Var(n) => format!("var `{}`", n),
221            ValueRef::Body { path } if path.is_empty() => "body".to_string(),
222            ValueRef::Body { path } => format!("body field `{}`", path.join(".")),
223        }
224    }
225}
226
227/// Helper map type used by validators.
228pub type FieldMap = BTreeMap<String, NamedField>;