1use std::{cmp::Ordering, collections::BTreeMap, fmt, net::SocketAddr};
2
3use crate::{
4 proto::command::{
5 AddBackend, FilteredTimeSerie, Header, HstsConfig, LoadBalancingParams, PathRule,
6 PathRuleKind, RequestHttpFrontend, RequestTcpFrontend, Response, ResponseContent,
7 ResponseStatus, 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#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub struct HttpFrontend {
29 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 #[serde(default)]
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub redirect: Option<i32>,
50 #[serde(default)]
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub redirect_scheme: Option<i32>,
53 #[serde(default)]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub redirect_template: Option<String>,
56 #[serde(default)]
57 #[serde(skip_serializing_if = "Option::is_none")]
58 pub rewrite_host: Option<String>,
59 #[serde(default)]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub rewrite_path: Option<String>,
62 #[serde(default)]
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub rewrite_port: Option<u32>,
65 #[serde(default)]
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub required_auth: Option<bool>,
68 #[serde(default)]
69 #[serde(skip_serializing_if = "Vec::is_empty")]
70 pub headers: Vec<Header>,
71 #[serde(default)]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub hsts: Option<HstsConfig>,
76}
77
78impl From<HttpFrontend> for RequestHttpFrontend {
79 fn from(val: HttpFrontend) -> Self {
80 RequestHttpFrontend {
81 cluster_id: val.cluster_id,
82 address: val.address.into(),
83 hostname: val.hostname,
84 path: val.path,
85 method: val.method,
86 position: val.position.into(),
87 tags: val.tags.unwrap_or_default(),
88 redirect: val.redirect,
89 redirect_scheme: val.redirect_scheme,
90 redirect_template: val.redirect_template,
91 rewrite_host: val.rewrite_host,
92 rewrite_path: val.rewrite_path,
93 rewrite_port: val.rewrite_port,
94 required_auth: val.required_auth,
95 headers: val.headers,
96 hsts: val.hsts,
97 }
98 }
99}
100
101impl From<Backend> for AddBackend {
102 fn from(val: Backend) -> Self {
103 AddBackend {
104 cluster_id: val.cluster_id,
105 backend_id: val.backend_id,
106 address: val.address.into(),
107 sticky_id: val.sticky_id,
108 load_balancing_parameters: val.load_balancing_parameters,
109 backup: val.backup,
110 }
111 }
112}
113
114impl PathRule {
115 pub fn prefix<S>(value: S) -> Self
116 where
117 S: ToString,
118 {
119 Self {
120 kind: PathRuleKind::Prefix.into(),
121 value: value.to_string(),
122 }
123 }
124
125 pub fn regex<S>(value: S) -> Self
126 where
127 S: ToString,
128 {
129 Self {
130 kind: PathRuleKind::Regex.into(),
131 value: value.to_string(),
132 }
133 }
134
135 pub fn equals<S>(value: S) -> Self
136 where
137 S: ToString,
138 {
139 Self {
140 kind: PathRuleKind::Equals.into(),
141 value: value.to_string(),
142 }
143 }
144
145 pub fn from_cli_options(
146 path_prefix: Option<String>,
147 path_regex: Option<String>,
148 path_equals: Option<String>,
149 ) -> Self {
150 match (path_prefix, path_regex, path_equals) {
151 (Some(prefix), _, _) => PathRule {
152 kind: PathRuleKind::Prefix as i32,
153 value: prefix,
154 },
155 (None, Some(regex), _) => PathRule {
156 kind: PathRuleKind::Regex as i32,
157 value: regex,
158 },
159 (None, None, Some(equals)) => PathRule {
160 kind: PathRuleKind::Equals as i32,
161 value: equals,
162 },
163 _ => PathRule::default(),
164 }
165 }
166}
167
168pub fn is_default_path_rule(p: &PathRule) -> bool {
169 PathRuleKind::try_from(p.kind) == Ok(PathRuleKind::Prefix) && p.value.is_empty()
170}
171
172impl fmt::Display for PathRule {
173 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
174 match PathRuleKind::try_from(self.kind) {
175 Ok(PathRuleKind::Prefix) => write!(f, "prefix '{}'", self.value),
176 Ok(PathRuleKind::Regex) => write!(f, "regexp '{}'", self.value),
177 Ok(PathRuleKind::Equals) => write!(f, "equals '{}'", self.value),
178 Err(_) => write!(f, ""),
179 }
180 }
181}
182
183#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
185pub struct TcpFrontend {
186 pub cluster_id: String,
187 pub address: SocketAddr,
188 pub tags: BTreeMap<String, String>,
190}
191
192impl From<TcpFrontend> for RequestTcpFrontend {
193 fn from(val: TcpFrontend) -> Self {
194 RequestTcpFrontend {
195 cluster_id: val.cluster_id,
196 address: val.address.into(),
197 tags: val.tags,
198 }
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
204pub struct Backend {
205 pub cluster_id: String,
206 pub backend_id: String,
207 pub address: SocketAddr,
208 #[serde(default)]
209 #[serde(skip_serializing_if = "Option::is_none")]
210 pub sticky_id: Option<String>,
211 #[serde(default)]
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub load_balancing_parameters: Option<LoadBalancingParams>,
214 #[serde(default)]
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub backup: Option<bool>,
217}
218
219impl Ord for Backend {
220 fn cmp(&self, o: &Backend) -> Ordering {
221 self.cluster_id
222 .cmp(&o.cluster_id)
223 .then(self.backend_id.cmp(&o.backend_id))
224 .then(self.sticky_id.cmp(&o.sticky_id))
225 .then(
226 self.load_balancing_parameters
227 .cmp(&o.load_balancing_parameters),
228 )
229 .then(self.backup.cmp(&o.backup))
230 .then(socketaddr_cmp(&self.address, &o.address))
231 }
232}
233
234impl PartialOrd for Backend {
235 fn partial_cmp(&self, other: &Backend) -> Option<Ordering> {
236 Some(self.cmp(other))
237 }
238}
239
240impl Backend {
241 pub fn to_add_backend(self) -> AddBackend {
242 AddBackend {
243 cluster_id: self.cluster_id,
244 address: self.address.into(),
245 sticky_id: self.sticky_id,
246 backend_id: self.backend_id,
247 load_balancing_parameters: self.load_balancing_parameters,
248 backup: self.backup,
249 }
250 }
251}
252
253impl fmt::Display for RunState {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 write!(f, "{self:?}")
256 }
257}
258
259pub type MessageId = String;
260
261impl WorkerResponse {
262 pub fn ok<T>(id: T) -> Self
263 where
264 T: ToString,
265 {
266 Self {
267 id: id.to_string(),
268 message: String::new(),
269 status: ResponseStatus::Ok.into(),
270 content: None,
271 }
272 }
273
274 pub fn ok_with_content<T>(id: T, content: ResponseContent) -> Self
275 where
276 T: ToString,
277 {
278 Self {
279 id: id.to_string(),
280 status: ResponseStatus::Ok.into(),
281 message: String::new(),
282 content: Some(content),
283 }
284 }
285
286 pub fn error<T, U>(id: T, error: U) -> Self
287 where
288 T: ToString,
289 U: ToString,
290 {
291 Self {
292 id: id.to_string(),
293 message: error.to_string(),
294 status: ResponseStatus::Failure.into(),
295 content: None,
296 }
297 }
298
299 pub fn processing<T>(id: T) -> Self
300 where
301 T: ToString,
302 {
303 Self {
304 id: id.to_string(),
305 message: String::new(),
306 status: ResponseStatus::Processing.into(),
307 content: None,
308 }
309 }
310
311 pub fn with_status<T>(id: T, status: ResponseStatus) -> Self
312 where
313 T: ToString,
314 {
315 Self {
316 id: id.to_string(),
317 message: String::new(),
318 status: status.into(),
319 content: None,
320 }
321 }
322
323 pub fn is_failure(&self) -> bool {
324 self.status == ResponseStatus::Failure as i32
325 }
326}
327
328impl fmt::Display for WorkerResponse {
329 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
330 write!(f, "{}-{:?}", self.id, self.status)
331 }
332}
333
334impl fmt::Display for FilteredTimeSerie {
335 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
336 write!(
337 f,
338 "FilteredTimeSerie {{\nlast_second: {},\nlast_minute:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\nlast_hour:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n}}",
339 self.last_second,
340 &self.last_minute[0..10],
341 &self.last_minute[10..20],
342 &self.last_minute[20..30],
343 &self.last_minute[30..40],
344 &self.last_minute[40..50],
345 &self.last_minute[50..60],
346 &self.last_hour[0..10],
347 &self.last_hour[10..20],
348 &self.last_hour[20..30],
349 &self.last_hour[30..40],
350 &self.last_hour[40..50],
351 &self.last_hour[50..60]
352 )
353 }
354}
355
356fn socketaddr_cmp(a: &SocketAddr, b: &SocketAddr) -> Ordering {
357 a.ip().cmp(&b.ip()).then(a.port().cmp(&b.port()))
358}