Skip to main content

earl_core/
schema.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
7#[serde(deny_unknown_fields)]
8pub struct AllowRule {
9    pub scheme: String,
10    pub host: String,
11    pub port: u16,
12    pub path_prefix: String,
13}
14
15#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq)]
16pub enum CommandMode {
17    #[serde(rename = "read")]
18    Read,
19    #[default]
20    #[serde(rename = "write")]
21    Write,
22}
23
24impl CommandMode {
25    pub fn as_str(&self) -> &'static str {
26        match self {
27            CommandMode::Read => "read",
28            CommandMode::Write => "write",
29        }
30    }
31}
32
33#[derive(Debug, Clone, Deserialize, Serialize)]
34#[serde(deny_unknown_fields)]
35pub struct ParamSpec {
36    pub name: String,
37    #[serde(rename = "type")]
38    pub r#type: ParamType,
39    #[serde(default)]
40    pub required: bool,
41    pub default: Option<Value>,
42    pub description: Option<String>,
43}
44
45#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
46#[serde(rename_all = "lowercase")]
47pub enum ParamType {
48    String,
49    Integer,
50    Number,
51    Boolean,
52    Null,
53    Array,
54    Object,
55}
56
57impl std::fmt::Display for ParamType {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        let label = match self {
60            ParamType::String => "string",
61            ParamType::Integer => "integer",
62            ParamType::Number => "number",
63            ParamType::Boolean => "boolean",
64            ParamType::Null => "null",
65            ParamType::Array => "array",
66            ParamType::Object => "object",
67        };
68        write!(f, "{label}")
69    }
70}
71
72#[derive(Debug, Clone, Deserialize, Serialize)]
73#[serde(tag = "kind", rename_all = "snake_case")]
74pub enum AuthTemplate {
75    None,
76    ApiKey {
77        location: ApiKeyLocation,
78        name: String,
79        secret: String,
80    },
81    Bearer {
82        secret: String,
83    },
84    Basic {
85        username: String,
86        password_secret: String,
87    },
88    OAuth2Profile {
89        profile: String,
90    },
91}
92
93#[derive(Debug, Clone, Deserialize, Serialize)]
94#[serde(rename_all = "snake_case")]
95pub enum ApiKeyLocation {
96    Header,
97    Query,
98    Cookie,
99}
100
101#[derive(Debug, Clone, Deserialize, Serialize)]
102#[serde(tag = "kind", rename_all = "snake_case")]
103pub enum BodyTemplate {
104    None,
105    Json {
106        value: Value,
107    },
108    FormUrlencoded {
109        fields: BTreeMap<String, Value>,
110    },
111    Multipart {
112        parts: Vec<MultipartPartTemplate>,
113    },
114    RawText {
115        value: String,
116        content_type: Option<String>,
117    },
118    RawBytesBase64 {
119        value: String,
120        content_type: Option<String>,
121    },
122    FileStream {
123        path: String,
124        content_type: Option<String>,
125    },
126}
127
128#[derive(Debug, Clone, Deserialize, Serialize)]
129#[serde(deny_unknown_fields)]
130pub struct MultipartPartTemplate {
131    pub name: String,
132    pub value: Option<String>,
133    pub bytes_base64: Option<String>,
134    pub file_path: Option<String>,
135    pub content_type: Option<String>,
136    pub filename: Option<String>,
137}
138
139#[derive(Debug, Clone, Deserialize, Serialize)]
140#[serde(deny_unknown_fields)]
141pub struct TransportTemplate {
142    pub timeout_ms: Option<u64>,
143    pub max_response_bytes: Option<u64>,
144    pub redirects: Option<RedirectTemplate>,
145    pub retry: Option<RetryTemplate>,
146    pub compression: Option<bool>,
147    pub tls: Option<TlsTemplate>,
148    pub proxy_profile: Option<String>,
149}
150
151#[derive(Debug, Clone, Deserialize, Serialize)]
152#[serde(deny_unknown_fields)]
153pub struct RedirectTemplate {
154    #[serde(default = "default_follow_redirects")]
155    pub follow: bool,
156    #[serde(default = "default_redirect_hops")]
157    pub max_hops: usize,
158}
159
160#[derive(Debug, Clone, Deserialize, Serialize)]
161#[serde(deny_unknown_fields)]
162pub struct RetryTemplate {
163    #[serde(default)]
164    pub max_attempts: usize,
165    #[serde(default = "default_backoff_ms")]
166    pub backoff_ms: u64,
167    #[serde(default)]
168    pub retry_on_status: Vec<u16>,
169}
170
171#[derive(Debug, Clone, Deserialize, Serialize)]
172#[serde(deny_unknown_fields)]
173pub struct TlsTemplate {
174    pub min_version: Option<String>,
175}
176
177#[derive(Debug, Clone, Deserialize, Serialize)]
178#[serde(deny_unknown_fields)]
179pub struct ResultTemplate {
180    #[serde(default)]
181    pub decode: ResultDecode,
182    pub extract: Option<ResultExtract>,
183    #[serde(default = "default_result_output")]
184    pub output: String,
185    pub result_alias: Option<String>,
186}
187
188impl Default for ResultTemplate {
189    fn default() -> Self {
190        Self {
191            decode: ResultDecode::default(),
192            extract: None,
193            output: default_result_output(),
194            result_alias: None,
195        }
196    }
197}
198
199fn default_result_output() -> String {
200    "{{ result }}".to_string()
201}
202
203#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)]
204#[serde(rename_all = "snake_case")]
205pub enum ResultDecode {
206    #[default]
207    Auto,
208    Json,
209    Text,
210    Html,
211    Xml,
212    Binary,
213}
214
215#[derive(Debug, Clone, Deserialize, Serialize)]
216#[serde(untagged)]
217pub enum ResultExtract {
218    JsonPointer { json_pointer: String },
219    Regex { regex: String },
220    XPath { xpath: String },
221    CssSelector { css_selector: String },
222}
223
224pub fn default_follow_redirects() -> bool {
225    true
226}
227
228pub fn default_redirect_hops() -> usize {
229    5
230}
231
232pub fn default_backoff_ms() -> u64 {
233    250
234}