Skip to main content

earl_core/
schema.rs

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