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 {
92        name: String,
93        ty: TypeExpr,
94        span: Span,
95    },
96}
97
98#[derive(Debug, Clone)]
99pub struct NamedField {
100    pub name: String,
101    pub optional: bool,
102    pub ty: TypeExpr,
103    pub span: Span,
104}
105
106#[derive(Debug, Clone)]
107pub struct VarDef {
108    pub name: String,
109    pub source: ValueSource,
110    pub span: Span,
111}
112
113#[derive(Debug, Clone)]
114pub enum BodySpec {
115    /// Raw textual body, no schema (`BODY json` unschematized, `BODY string`).
116    Json {
117        schema: Option<JsonSchema>,
118        span: Span,
119    },
120    Form {
121        fields: Vec<NamedField>,
122        span: Span,
123    },
124    String {
125        span: Span,
126    },
127    Binary {
128        span: Span,
129    },
130}
131
132#[derive(Debug, Clone)]
133pub struct JsonSchema {
134    pub fields: Vec<JsonField>,
135}
136
137#[derive(Debug, Clone)]
138pub struct JsonField {
139    pub name: String,
140    pub optional: bool,
141    pub ty: JsonFieldType,
142    pub span: Span,
143}
144
145#[derive(Debug, Clone)]
146pub enum JsonFieldType {
147    Scalar(TypeExpr),
148    Array(TypeExpr),
149}
150
151#[derive(Debug, Clone)]
152pub enum TypeExpr {
153    Int,
154    Float,
155    Boolean,
156    Uuid,
157    String,
158    Json,
159    Binary,
160    IntRange { min: i64, max: i64, span: Span },
161    FloatRange { min: f64, max: f64, span: Span },
162    Union { variants: Vec<String>, span: Span },
163    Regex { pattern: String, span: Span },
164}
165
166impl TypeExpr {
167    pub fn name(&self) -> &'static str {
168        match self {
169            TypeExpr::Int => "int",
170            TypeExpr::Float => "float",
171            TypeExpr::Boolean => "boolean",
172            TypeExpr::Uuid => "uuid",
173            TypeExpr::String => "string",
174            TypeExpr::Json => "json",
175            TypeExpr::Binary => "binary",
176            TypeExpr::IntRange { .. } => "int(range)",
177            TypeExpr::FloatRange { .. } => "float(range)",
178            TypeExpr::Union { .. } => "union",
179            TypeExpr::Regex { .. } => "regex",
180        }
181    }
182}
183
184#[derive(Debug, Clone)]
185pub struct ExecSpec {
186    pub raw: String,
187    pub span: Span,
188    pub pipeline: Vec<ExecStage>,
189}
190
191/// A pipeline stage: either a value source piped to next, or a command.
192#[derive(Debug, Clone)]
193pub enum ExecStage {
194    /// A bare value reference (e.g. `$`, `$.path`, `%name`) used as stdin into next stage.
195    Source {
196        reference: ValueRef,
197        span: Span,
198    },
199    Command {
200        tokens: Vec<ExecToken>,
201        span: Span,
202    },
203}
204
205#[derive(Debug, Clone)]
206pub enum ExecToken {
207    /// A token built from text + quoted-string `{...}` interpolations. Always emitted.
208    Text {
209        parts: Vec<TextPart>,
210        force_quote: bool,
211        span: Span,
212    },
213    /// A `[...]` shell-piece group; if any interpolation is missing, omit the whole group.
214    Group { pieces: Vec<GroupPiece>, span: Span },
215}
216
217#[derive(Debug, Clone)]
218pub enum TextPart {
219    Literal(String),
220    Interp(ValueRef),
221}
222
223#[derive(Debug, Clone)]
224pub struct GroupPiece {
225    pub parts: Vec<TextPart>,
226    pub force_quote: bool,
227}
228
229#[derive(Debug, Clone)]
230pub enum ValueRef {
231    Query(String),
232    Path(String),
233    Header(String),
234    Var(String),
235    /// Whole body or a JSON path into the body. Empty path = whole body.
236    Body {
237        path: Vec<String>,
238    },
239}
240
241impl ValueRef {
242    pub fn describe(&self) -> String {
243        match self {
244            ValueRef::Query(n) => format!("query param `{}`", n),
245            ValueRef::Path(n) => format!("path param `{}`", n),
246            ValueRef::Header(n) => format!("header `{}`", n),
247            ValueRef::Var(n) => format!("var `{}`", n),
248            ValueRef::Body { path } if path.is_empty() => "body".to_string(),
249            ValueRef::Body { path } => format!("body field `{}`", path.join(".")),
250        }
251    }
252}
253
254/// Helper map type used by validators.
255pub type FieldMap = BTreeMap<String, NamedField>;