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 response_stream: bool,
84 pub query_params: Vec<NamedField>,
85 pub headers: Vec<NamedField>,
86 pub vars: Vec<VarDef>,
87 pub body: Option<BodySpec>,
88 pub exec: ExecSpec,
89 pub span: Span,
90}
91
92#[derive(Debug, Clone)]
93pub enum PathSegment {
94 Literal(String),
95 Param {
96 name: String,
97 ty: TypeExpr,
98 span: Span,
99 },
100}
101
102#[derive(Debug, Clone)]
103pub struct NamedField {
104 pub name: String,
105 pub optional: bool,
106 pub ty: TypeExpr,
107 pub span: Span,
108}
109
110#[derive(Debug, Clone)]
111pub struct VarDef {
112 pub name: String,
113 pub source: ValueSource,
114 pub span: Span,
115}
116
117#[derive(Debug, Clone)]
118pub enum BodySpec {
119 Json {
121 schema: Option<JsonSchema>,
122 span: Span,
123 },
124 Form {
125 fields: Vec<NamedField>,
126 span: Span,
127 },
128 String {
129 span: Span,
130 },
131 Binary {
132 span: Span,
133 },
134}
135
136#[derive(Debug, Clone)]
137pub struct JsonSchema {
138 pub fields: Vec<JsonField>,
139}
140
141#[derive(Debug, Clone)]
142pub struct JsonField {
143 pub name: String,
144 pub optional: bool,
145 pub ty: JsonFieldType,
146 pub span: Span,
147}
148
149#[derive(Debug, Clone)]
150pub enum JsonFieldType {
151 Scalar(TypeExpr),
152 Array(TypeExpr),
153}
154
155#[derive(Debug, Clone)]
156pub enum TypeExpr {
157 Int,
158 Float,
159 Boolean,
160 Uuid,
161 String,
162 Json,
163 Binary,
164 IntRange { min: i64, max: i64, span: Span },
165 FloatRange { min: f64, max: f64, span: Span },
166 Union { variants: Vec<String>, span: Span },
167 Regex { pattern: String, span: Span },
168}
169
170impl TypeExpr {
171 pub fn name(&self) -> &'static str {
172 match self {
173 TypeExpr::Int => "int",
174 TypeExpr::Float => "float",
175 TypeExpr::Boolean => "boolean",
176 TypeExpr::Uuid => "uuid",
177 TypeExpr::String => "string",
178 TypeExpr::Json => "json",
179 TypeExpr::Binary => "binary",
180 TypeExpr::IntRange { .. } => "int(range)",
181 TypeExpr::FloatRange { .. } => "float(range)",
182 TypeExpr::Union { .. } => "union",
183 TypeExpr::Regex { .. } => "regex",
184 }
185 }
186}
187
188#[derive(Debug, Clone)]
189pub struct ExecSpec {
190 pub raw: String,
191 pub span: Span,
192 pub statements: Vec<Vec<ExecStage>>,
197}
198
199impl ExecSpec {
200 pub fn all_stages(&self) -> impl Iterator<Item = &ExecStage> {
201 self.statements.iter().flatten()
202 }
203}
204
205#[derive(Debug, Clone)]
207pub enum ExecStage {
208 Source {
210 reference: ValueRef,
211 span: Span,
212 },
213 Command {
214 tokens: Vec<ExecToken>,
215 span: Span,
216 },
217}
218
219#[derive(Debug, Clone)]
220pub enum ExecToken {
221 Text {
223 parts: Vec<TextPart>,
224 force_quote: bool,
225 span: Span,
226 },
227 Group { pieces: Vec<GroupPiece>, span: Span },
229}
230
231#[derive(Debug, Clone)]
232pub enum TextPart {
233 Literal(String),
234 Interp(ValueRef),
235}
236
237#[derive(Debug, Clone)]
238pub struct GroupPiece {
239 pub parts: Vec<TextPart>,
240 pub force_quote: bool,
241}
242
243#[derive(Debug, Clone)]
244pub enum ValueRef {
245 Query(String),
246 Path(String),
247 Header(String),
248 Var(String),
249 Body {
251 path: Vec<String>,
252 },
253}
254
255impl ValueRef {
256 pub fn describe(&self) -> String {
257 match self {
258 ValueRef::Query(n) => format!("query param `{}`", n),
259 ValueRef::Path(n) => format!("path param `{}`", n),
260 ValueRef::Header(n) => format!("header `{}`", n),
261 ValueRef::Var(n) => format!("var `{}`", n),
262 ValueRef::Body { path } if path.is_empty() => "body".to_string(),
263 ValueRef::Body { path } => format!("body field `{}`", path.join(".")),
264 }
265 }
266}
267
268pub type FieldMap = BTreeMap<String, NamedField>;