sozu_command_lib/
response.rs

1use std::{cmp::Ordering, collections::BTreeMap, fmt, net::SocketAddr};
2
3use crate::{
4    proto::command::{
5        AddBackend, FilteredTimeSerie, LoadBalancingParams, PathRule, PathRuleKind,
6        RequestHttpFrontend, RequestTcpFrontend, Response, ResponseContent, ResponseStatus,
7        RulePosition, RunState, WorkerResponse,
8    },
9    state::ClusterId,
10};
11
12impl Response {
13    pub fn new(
14        status: ResponseStatus,
15        message: String,
16        content: Option<ResponseContent>,
17    ) -> Response {
18        Response {
19            status: status as i32,
20            message,
21            content,
22        }
23    }
24}
25
26/// An HTTP or HTTPS frontend, as used *within* Sōzu
27#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub struct HttpFrontend {
29    /// Send a 401, DENY, if cluster_id is None
30    pub cluster_id: Option<ClusterId>,
31    pub address: SocketAddr,
32    pub hostname: String,
33    #[serde(default)]
34    #[serde(skip_serializing_if = "is_default_path_rule")]
35    pub path: PathRule,
36    #[serde(default)]
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub method: Option<String>,
39    #[serde(default)]
40    pub position: RulePosition,
41    pub tags: Option<BTreeMap<String, String>>,
42}
43
44impl From<HttpFrontend> for RequestHttpFrontend {
45    fn from(val: HttpFrontend) -> Self {
46        RequestHttpFrontend {
47            cluster_id: val.cluster_id,
48            address: val.address.into(),
49            hostname: val.hostname,
50            path: val.path,
51            method: val.method,
52            position: val.position.into(),
53            tags: val.tags.unwrap_or_default(),
54        }
55    }
56}
57
58impl From<Backend> for AddBackend {
59    fn from(val: Backend) -> Self {
60        AddBackend {
61            cluster_id: val.cluster_id,
62            backend_id: val.backend_id,
63            address: val.address.into(),
64            sticky_id: val.sticky_id,
65            load_balancing_parameters: val.load_balancing_parameters,
66            backup: val.backup,
67        }
68    }
69}
70
71impl PathRule {
72    pub fn prefix<S>(value: S) -> Self
73    where
74        S: ToString,
75    {
76        Self {
77            kind: PathRuleKind::Prefix.into(),
78            value: value.to_string(),
79        }
80    }
81
82    pub fn regex<S>(value: S) -> Self
83    where
84        S: ToString,
85    {
86        Self {
87            kind: PathRuleKind::Regex.into(),
88            value: value.to_string(),
89        }
90    }
91
92    pub fn equals<S>(value: S) -> Self
93    where
94        S: ToString,
95    {
96        Self {
97            kind: PathRuleKind::Equals.into(),
98            value: value.to_string(),
99        }
100    }
101
102    pub fn from_cli_options(
103        path_prefix: Option<String>,
104        path_regex: Option<String>,
105        path_equals: Option<String>,
106    ) -> Self {
107        match (path_prefix, path_regex, path_equals) {
108            (Some(prefix), _, _) => PathRule {
109                kind: PathRuleKind::Prefix as i32,
110                value: prefix,
111            },
112            (None, Some(regex), _) => PathRule {
113                kind: PathRuleKind::Regex as i32,
114                value: regex,
115            },
116            (None, None, Some(equals)) => PathRule {
117                kind: PathRuleKind::Equals as i32,
118                value: equals,
119            },
120            _ => PathRule::default(),
121        }
122    }
123}
124
125pub fn is_default_path_rule(p: &PathRule) -> bool {
126    PathRuleKind::try_from(p.kind) == Ok(PathRuleKind::Prefix) && p.value.is_empty()
127}
128
129impl fmt::Display for PathRule {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        match PathRuleKind::try_from(self.kind) {
132            Ok(PathRuleKind::Prefix) => write!(f, "prefix '{}'", self.value),
133            Ok(PathRuleKind::Regex) => write!(f, "regexp '{}'", self.value),
134            Ok(PathRuleKind::Equals) => write!(f, "equals '{}'", self.value),
135            Err(_) => write!(f, ""),
136        }
137    }
138}
139
140/// A TCP frontend, as used *within* Sōzu
141#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub struct TcpFrontend {
143    pub cluster_id: String,
144    pub address: SocketAddr,
145    /// custom tags to identify the frontend in the access logs
146    pub tags: BTreeMap<String, String>,
147}
148
149impl From<TcpFrontend> for RequestTcpFrontend {
150    fn from(val: TcpFrontend) -> Self {
151        RequestTcpFrontend {
152            cluster_id: val.cluster_id,
153            address: val.address.into(),
154            tags: val.tags,
155        }
156    }
157}
158
159/// A backend, as used *within* Sōzu
160#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
161pub struct Backend {
162    pub cluster_id: String,
163    pub backend_id: String,
164    pub address: SocketAddr,
165    #[serde(default)]
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub sticky_id: Option<String>,
168    #[serde(default)]
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub load_balancing_parameters: Option<LoadBalancingParams>,
171    #[serde(default)]
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub backup: Option<bool>,
174}
175
176impl Ord for Backend {
177    fn cmp(&self, o: &Backend) -> Ordering {
178        self.cluster_id
179            .cmp(&o.cluster_id)
180            .then(self.backend_id.cmp(&o.backend_id))
181            .then(self.sticky_id.cmp(&o.sticky_id))
182            .then(
183                self.load_balancing_parameters
184                    .cmp(&o.load_balancing_parameters),
185            )
186            .then(self.backup.cmp(&o.backup))
187            .then(socketaddr_cmp(&self.address, &o.address))
188    }
189}
190
191impl PartialOrd for Backend {
192    fn partial_cmp(&self, other: &Backend) -> Option<Ordering> {
193        Some(self.cmp(other))
194    }
195}
196
197impl Backend {
198    pub fn to_add_backend(self) -> AddBackend {
199        AddBackend {
200            cluster_id: self.cluster_id,
201            address: self.address.into(),
202            sticky_id: self.sticky_id,
203            backend_id: self.backend_id,
204            load_balancing_parameters: self.load_balancing_parameters,
205            backup: self.backup,
206        }
207    }
208}
209
210impl fmt::Display for RunState {
211    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212        write!(f, "{self:?}")
213    }
214}
215
216pub type MessageId = String;
217
218impl WorkerResponse {
219    pub fn ok<T>(id: T) -> Self
220    where
221        T: ToString,
222    {
223        Self {
224            id: id.to_string(),
225            message: String::new(),
226            status: ResponseStatus::Ok.into(),
227            content: None,
228        }
229    }
230
231    pub fn ok_with_content<T>(id: T, content: ResponseContent) -> Self
232    where
233        T: ToString,
234    {
235        Self {
236            id: id.to_string(),
237            status: ResponseStatus::Ok.into(),
238            message: String::new(),
239            content: Some(content),
240        }
241    }
242
243    pub fn error<T, U>(id: T, error: U) -> Self
244    where
245        T: ToString,
246        U: ToString,
247    {
248        Self {
249            id: id.to_string(),
250            message: error.to_string(),
251            status: ResponseStatus::Failure.into(),
252            content: None,
253        }
254    }
255
256    pub fn processing<T>(id: T) -> Self
257    where
258        T: ToString,
259    {
260        Self {
261            id: id.to_string(),
262            message: String::new(),
263            status: ResponseStatus::Processing.into(),
264            content: None,
265        }
266    }
267
268    pub fn with_status<T>(id: T, status: ResponseStatus) -> Self
269    where
270        T: ToString,
271    {
272        Self {
273            id: id.to_string(),
274            message: String::new(),
275            status: status.into(),
276            content: None,
277        }
278    }
279
280    pub fn is_failure(&self) -> bool {
281        self.status == ResponseStatus::Failure as i32
282    }
283}
284
285impl fmt::Display for WorkerResponse {
286    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
287        write!(f, "{}-{:?}", self.id, self.status)
288    }
289}
290
291impl fmt::Display for FilteredTimeSerie {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        write!(
294            f,
295            "FilteredTimeSerie {{\nlast_second: {},\nlast_minute:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\nlast_hour:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n}}",
296            self.last_second,
297            &self.last_minute[0..10], &self.last_minute[10..20], &self.last_minute[20..30], &self.last_minute[30..40], &self.last_minute[40..50], &self.last_minute[50..60],
298            &self.last_hour[0..10], &self.last_hour[10..20], &self.last_hour[20..30], &self.last_hour[30..40], &self.last_hour[40..50], &self.last_hour[50..60])
299    }
300}
301
302fn socketaddr_cmp(a: &SocketAddr, b: &SocketAddr) -> Ordering {
303    a.ip().cmp(&b.ip()).then(a.port().cmp(&b.port()))
304}