1use 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 pub span: Span,
27}
28
29#[derive(Debug, Clone)]
30pub enum AuthSpec {
31 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 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 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#[derive(Debug, Clone)]
179pub enum ExecStage {
180 Source { reference: ValueRef, span: Span },
182 Command { tokens: Vec<ExecToken>, span: Span },
183}
184
185#[derive(Debug, Clone)]
186pub enum ExecToken {
187 Text { parts: Vec<TextPart>, span: Span },
189 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 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
227pub type FieldMap = BTreeMap<String, NamedField>;