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, RequestUdpFrontend, Response,
7 ResponseContent, 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 let source_address = val.address;
81 let source_hostname = val.hostname.clone();
82 let request_frontend = RequestHttpFrontend {
83 cluster_id: val.cluster_id,
84 address: val.address.into(),
85 hostname: val.hostname,
86 path: val.path,
87 method: val.method,
88 position: val.position.into(),
89 tags: val.tags.unwrap_or_default(),
90 redirect: val.redirect,
91 redirect_scheme: val.redirect_scheme,
92 redirect_template: val.redirect_template,
93 rewrite_host: val.rewrite_host,
94 rewrite_path: val.rewrite_path,
95 rewrite_port: val.rewrite_port,
96 required_auth: val.required_auth,
97 headers: val.headers,
98 hsts: val.hsts,
99 };
100
101 debug_assert_eq!(
106 SocketAddr::from(request_frontend.address),
107 source_address,
108 "frontend address must round-trip through the proto encoding"
109 );
110 debug_assert_eq!(
111 request_frontend.hostname, source_hostname,
112 "frontend hostname must survive the proto conversion"
113 );
114 request_frontend
115 }
116}
117
118impl From<Backend> for AddBackend {
119 fn from(val: Backend) -> Self {
120 let source_address = val.address;
121 let source_cluster_id = val.cluster_id.clone();
122 let source_backend_id = val.backend_id.clone();
123 let add_backend = AddBackend {
124 cluster_id: val.cluster_id,
125 backend_id: val.backend_id,
126 address: val.address.into(),
127 sticky_id: val.sticky_id,
128 load_balancing_parameters: val.load_balancing_parameters,
129 backup: val.backup,
130 };
131
132 debug_assert_eq!(
137 add_backend.cluster_id, source_cluster_id,
138 "backend cluster_id must survive the proto conversion"
139 );
140 debug_assert_eq!(
141 add_backend.backend_id, source_backend_id,
142 "backend_id must survive the proto conversion"
143 );
144 debug_assert_eq!(
145 SocketAddr::from(add_backend.address),
146 source_address,
147 "backend address must round-trip through the proto encoding"
148 );
149 add_backend
150 }
151}
152
153impl PathRule {
154 pub fn prefix<S>(value: S) -> Self
155 where
156 S: ToString,
157 {
158 let rule = Self {
159 kind: PathRuleKind::Prefix.into(),
160 value: value.to_string(),
161 };
162 debug_assert_eq!(
165 PathRuleKind::try_from(rule.kind),
166 Ok(PathRuleKind::Prefix),
167 "prefix() must encode a Prefix-kind rule"
168 );
169 rule
170 }
171
172 pub fn regex<S>(value: S) -> Self
173 where
174 S: ToString,
175 {
176 let rule = Self {
177 kind: PathRuleKind::Regex.into(),
178 value: value.to_string(),
179 };
180 debug_assert_eq!(
181 PathRuleKind::try_from(rule.kind),
182 Ok(PathRuleKind::Regex),
183 "regex() must encode a Regex-kind rule"
184 );
185 rule
186 }
187
188 pub fn equals<S>(value: S) -> Self
189 where
190 S: ToString,
191 {
192 let rule = Self {
193 kind: PathRuleKind::Equals.into(),
194 value: value.to_string(),
195 };
196 debug_assert_eq!(
197 PathRuleKind::try_from(rule.kind),
198 Ok(PathRuleKind::Equals),
199 "equals() must encode an Equals-kind rule"
200 );
201 rule
202 }
203
204 pub fn from_cli_options(
205 path_prefix: Option<String>,
206 path_regex: Option<String>,
207 path_equals: Option<String>,
208 ) -> Self {
209 let had_prefix = path_prefix.is_some();
213 let had_regex = path_regex.is_some();
214 let rule = match (path_prefix, path_regex, path_equals) {
215 (Some(prefix), _, _) => PathRule {
216 kind: PathRuleKind::Prefix as i32,
217 value: prefix,
218 },
219 (None, Some(regex), _) => PathRule {
220 kind: PathRuleKind::Regex as i32,
221 value: regex,
222 },
223 (None, None, Some(equals)) => PathRule {
224 kind: PathRuleKind::Equals as i32,
225 value: equals,
226 },
227 _ => PathRule::default(),
228 };
229
230 debug_assert!(
234 !had_prefix || rule.kind == PathRuleKind::Prefix as i32,
235 "a path prefix must produce a Prefix rule regardless of other flags"
236 );
237 debug_assert!(
238 had_prefix || !had_regex || rule.kind == PathRuleKind::Regex as i32,
239 "absent a prefix, a regex must produce a Regex rule"
240 );
241 rule
242 }
243}
244
245pub fn is_default_path_rule(p: &PathRule) -> bool {
246 PathRuleKind::try_from(p.kind) == Ok(PathRuleKind::Prefix) && p.value.is_empty()
247}
248
249impl fmt::Display for PathRule {
250 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
251 match PathRuleKind::try_from(self.kind) {
252 Ok(PathRuleKind::Prefix) => write!(f, "prefix '{}'", self.value),
253 Ok(PathRuleKind::Regex) => write!(f, "regexp '{}'", self.value),
254 Ok(PathRuleKind::Equals) => write!(f, "equals '{}'", self.value),
255 Err(_) => write!(f, ""),
256 }
257 }
258}
259
260#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
262pub struct TcpFrontend {
263 pub cluster_id: String,
264 pub address: SocketAddr,
265 pub tags: BTreeMap<String, String>,
267}
268
269impl From<TcpFrontend> for RequestTcpFrontend {
270 fn from(val: TcpFrontend) -> Self {
271 let source_address = val.address;
272 let source_cluster_id = val.cluster_id.clone();
273 let request_frontend = RequestTcpFrontend {
274 cluster_id: val.cluster_id,
275 address: val.address.into(),
276 tags: val.tags,
277 };
278
279 debug_assert_eq!(
282 request_frontend.cluster_id, source_cluster_id,
283 "TCP frontend cluster_id must survive the proto conversion"
284 );
285 debug_assert_eq!(
286 SocketAddr::from(request_frontend.address),
287 source_address,
288 "TCP frontend address must round-trip through the proto encoding"
289 );
290 request_frontend
291 }
292}
293
294#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
296pub struct UdpFrontend {
297 pub cluster_id: String,
298 pub address: SocketAddr,
299 pub tags: BTreeMap<String, String>,
301}
302
303impl From<UdpFrontend> for RequestUdpFrontend {
304 fn from(val: UdpFrontend) -> Self {
305 RequestUdpFrontend {
306 cluster_id: val.cluster_id,
307 address: val.address.into(),
308 tags: val.tags,
309 }
310 }
311}
312
313#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
315pub struct Backend {
316 pub cluster_id: String,
317 pub backend_id: String,
318 pub address: SocketAddr,
319 #[serde(default)]
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub sticky_id: Option<String>,
322 #[serde(default)]
323 #[serde(skip_serializing_if = "Option::is_none")]
324 pub load_balancing_parameters: Option<LoadBalancingParams>,
325 #[serde(default)]
326 #[serde(skip_serializing_if = "Option::is_none")]
327 pub backup: Option<bool>,
328}
329
330impl Ord for Backend {
331 fn cmp(&self, o: &Backend) -> Ordering {
332 let fields_all_equal = self.cluster_id == o.cluster_id
338 && self.backend_id == o.backend_id
339 && self.sticky_id == o.sticky_id
340 && self.load_balancing_parameters == o.load_balancing_parameters
341 && self.backup == o.backup
342 && self.address == o.address;
343
344 let ordering = self
345 .cluster_id
346 .cmp(&o.cluster_id)
347 .then(self.backend_id.cmp(&o.backend_id))
348 .then(self.sticky_id.cmp(&o.sticky_id))
349 .then(
350 self.load_balancing_parameters
351 .cmp(&o.load_balancing_parameters),
352 )
353 .then(self.backup.cmp(&o.backup))
354 .then(socketaddr_cmp(&self.address, &o.address));
355
356 debug_assert_eq!(
357 ordering == Ordering::Equal,
358 fields_all_equal,
359 "Backend::cmp returns Equal iff every keyed field is equal"
360 );
361 ordering
362 }
363}
364
365impl PartialOrd for Backend {
366 fn partial_cmp(&self, other: &Backend) -> Option<Ordering> {
367 Some(self.cmp(other))
368 }
369}
370
371impl Backend {
372 pub fn to_add_backend(self) -> AddBackend {
373 let source_address = self.address;
374 let source_backend_id = self.backend_id.clone();
375 let add_backend = AddBackend {
376 cluster_id: self.cluster_id,
377 address: self.address.into(),
378 sticky_id: self.sticky_id,
379 backend_id: self.backend_id,
380 load_balancing_parameters: self.load_balancing_parameters,
381 backup: self.backup,
382 };
383
384 debug_assert_eq!(
387 add_backend.backend_id, source_backend_id,
388 "backend_id must survive to_add_backend"
389 );
390 debug_assert_eq!(
391 SocketAddr::from(add_backend.address),
392 source_address,
393 "backend address must round-trip through to_add_backend"
394 );
395 add_backend
396 }
397}
398
399impl fmt::Display for RunState {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 write!(f, "{self:?}")
402 }
403}
404
405pub type MessageId = String;
406
407impl WorkerResponse {
408 pub fn ok<T>(id: T) -> Self
409 where
410 T: ToString,
411 {
412 Self {
413 id: id.to_string(),
414 message: String::new(),
415 status: ResponseStatus::Ok.into(),
416 content: None,
417 }
418 }
419
420 pub fn ok_with_content<T>(id: T, content: ResponseContent) -> Self
421 where
422 T: ToString,
423 {
424 Self {
425 id: id.to_string(),
426 status: ResponseStatus::Ok.into(),
427 message: String::new(),
428 content: Some(content),
429 }
430 }
431
432 pub fn error<T, U>(id: T, error: U) -> Self
433 where
434 T: ToString,
435 U: ToString,
436 {
437 Self {
438 id: id.to_string(),
439 message: error.to_string(),
440 status: ResponseStatus::Failure.into(),
441 content: None,
442 }
443 }
444
445 pub fn processing<T>(id: T) -> Self
446 where
447 T: ToString,
448 {
449 Self {
450 id: id.to_string(),
451 message: String::new(),
452 status: ResponseStatus::Processing.into(),
453 content: None,
454 }
455 }
456
457 pub fn with_status<T>(id: T, status: ResponseStatus) -> Self
458 where
459 T: ToString,
460 {
461 Self {
462 id: id.to_string(),
463 message: String::new(),
464 status: status.into(),
465 content: None,
466 }
467 }
468
469 pub fn is_failure(&self) -> bool {
470 self.status == ResponseStatus::Failure as i32
471 }
472}
473
474impl fmt::Display for WorkerResponse {
475 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
476 write!(f, "{}-{:?}", self.id, self.status)
477 }
478}
479
480impl fmt::Display for FilteredTimeSerie {
481 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
482 write!(
483 f,
484 "FilteredTimeSerie {{\nlast_second: {},\nlast_minute:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\nlast_hour:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n}}",
485 self.last_second,
486 &self.last_minute[0..10],
487 &self.last_minute[10..20],
488 &self.last_minute[20..30],
489 &self.last_minute[30..40],
490 &self.last_minute[40..50],
491 &self.last_minute[50..60],
492 &self.last_hour[0..10],
493 &self.last_hour[10..20],
494 &self.last_hour[20..30],
495 &self.last_hour[30..40],
496 &self.last_hour[40..50],
497 &self.last_hour[50..60]
498 )
499 }
500}
501
502fn socketaddr_cmp(a: &SocketAddr, b: &SocketAddr) -> Ordering {
503 let ordering = a.ip().cmp(&b.ip()).then(a.port().cmp(&b.port()));
504 debug_assert_eq!(
508 ordering == Ordering::Equal,
509 a.ip() == b.ip() && a.port() == b.port(),
510 "socketaddr_cmp is Equal iff ip and port both match"
511 );
512 ordering
513}