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 {
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 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#[derive(Debug, Clone)]
193pub enum ExecStage {
194 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 Text {
209 parts: Vec<TextPart>,
210 force_quote: bool,
211 span: Span,
212 },
213 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 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
254pub type FieldMap = BTreeMap<String, NamedField>;