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 #[serde(default)]
270 pub sni: Option<String>,
271 #[serde(default)]
274 pub alpn: Vec<String>,
275}
276
277impl From<TcpFrontend> for RequestTcpFrontend {
278 fn from(val: TcpFrontend) -> Self {
279 let source_address = val.address;
280 let source_cluster_id = val.cluster_id.clone();
281 let source_sni = val.sni.clone();
282 let source_alpn = val.alpn.clone();
283 let request_frontend = RequestTcpFrontend {
284 cluster_id: val.cluster_id,
285 address: val.address.into(),
286 tags: val.tags,
287 sni: val.sni,
288 alpn: val.alpn,
289 };
290
291 debug_assert_eq!(
294 request_frontend.cluster_id, source_cluster_id,
295 "TCP frontend cluster_id must survive the proto conversion"
296 );
297 debug_assert_eq!(
298 SocketAddr::from(request_frontend.address),
299 source_address,
300 "TCP frontend address must round-trip through the proto encoding"
301 );
302 debug_assert_eq!(
306 request_frontend.sni, source_sni,
307 "TCP frontend sni must survive the proto conversion"
308 );
309 debug_assert_eq!(
310 request_frontend.alpn, source_alpn,
311 "TCP frontend alpn must survive the proto conversion"
312 );
313 request_frontend
314 }
315}
316
317#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
319pub struct UdpFrontend {
320 pub cluster_id: String,
321 pub address: SocketAddr,
322 pub tags: BTreeMap<String, String>,
324}
325
326impl From<UdpFrontend> for RequestUdpFrontend {
327 fn from(val: UdpFrontend) -> Self {
328 RequestUdpFrontend {
329 cluster_id: val.cluster_id,
330 address: val.address.into(),
331 tags: val.tags,
332 }
333 }
334}
335
336#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
338pub struct Backend {
339 pub cluster_id: String,
340 pub backend_id: String,
341 pub address: SocketAddr,
342 #[serde(default)]
343 #[serde(skip_serializing_if = "Option::is_none")]
344 pub sticky_id: Option<String>,
345 #[serde(default)]
346 #[serde(skip_serializing_if = "Option::is_none")]
347 pub load_balancing_parameters: Option<LoadBalancingParams>,
348 #[serde(default)]
349 #[serde(skip_serializing_if = "Option::is_none")]
350 pub backup: Option<bool>,
351}
352
353impl Ord for Backend {
354 fn cmp(&self, o: &Backend) -> Ordering {
355 let fields_all_equal = self.cluster_id == o.cluster_id
361 && self.backend_id == o.backend_id
362 && self.sticky_id == o.sticky_id
363 && self.load_balancing_parameters == o.load_balancing_parameters
364 && self.backup == o.backup
365 && self.address == o.address;
366
367 let ordering = self
368 .cluster_id
369 .cmp(&o.cluster_id)
370 .then(self.backend_id.cmp(&o.backend_id))
371 .then(self.sticky_id.cmp(&o.sticky_id))
372 .then(
373 self.load_balancing_parameters
374 .cmp(&o.load_balancing_parameters),
375 )
376 .then(self.backup.cmp(&o.backup))
377 .then(socketaddr_cmp(&self.address, &o.address));
378
379 debug_assert_eq!(
380 ordering == Ordering::Equal,
381 fields_all_equal,
382 "Backend::cmp returns Equal iff every keyed field is equal"
383 );
384 ordering
385 }
386}
387
388impl PartialOrd for Backend {
389 fn partial_cmp(&self, other: &Backend) -> Option<Ordering> {
390 Some(self.cmp(other))
391 }
392}
393
394impl Backend {
395 pub fn to_add_backend(self) -> AddBackend {
396 let source_address = self.address;
397 let source_backend_id = self.backend_id.clone();
398 let add_backend = AddBackend {
399 cluster_id: self.cluster_id,
400 address: self.address.into(),
401 sticky_id: self.sticky_id,
402 backend_id: self.backend_id,
403 load_balancing_parameters: self.load_balancing_parameters,
404 backup: self.backup,
405 };
406
407 debug_assert_eq!(
410 add_backend.backend_id, source_backend_id,
411 "backend_id must survive to_add_backend"
412 );
413 debug_assert_eq!(
414 SocketAddr::from(add_backend.address),
415 source_address,
416 "backend address must round-trip through to_add_backend"
417 );
418 add_backend
419 }
420}
421
422impl fmt::Display for RunState {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 write!(f, "{self:?}")
425 }
426}
427
428pub type MessageId = String;
429
430impl WorkerResponse {
431 pub fn ok<T>(id: T) -> Self
432 where
433 T: ToString,
434 {
435 Self {
436 id: id.to_string(),
437 message: String::new(),
438 status: ResponseStatus::Ok.into(),
439 content: None,
440 }
441 }
442
443 pub fn ok_with_content<T>(id: T, content: ResponseContent) -> Self
444 where
445 T: ToString,
446 {
447 Self {
448 id: id.to_string(),
449 status: ResponseStatus::Ok.into(),
450 message: String::new(),
451 content: Some(content),
452 }
453 }
454
455 pub fn error<T, U>(id: T, error: U) -> Self
456 where
457 T: ToString,
458 U: ToString,
459 {
460 Self {
461 id: id.to_string(),
462 message: error.to_string(),
463 status: ResponseStatus::Failure.into(),
464 content: None,
465 }
466 }
467
468 pub fn processing<T>(id: T) -> Self
469 where
470 T: ToString,
471 {
472 Self {
473 id: id.to_string(),
474 message: String::new(),
475 status: ResponseStatus::Processing.into(),
476 content: None,
477 }
478 }
479
480 pub fn with_status<T>(id: T, status: ResponseStatus) -> Self
481 where
482 T: ToString,
483 {
484 Self {
485 id: id.to_string(),
486 message: String::new(),
487 status: status.into(),
488 content: None,
489 }
490 }
491
492 pub fn is_failure(&self) -> bool {
493 self.status == ResponseStatus::Failure as i32
494 }
495}
496
497impl fmt::Display for WorkerResponse {
498 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
499 write!(f, "{}-{:?}", self.id, self.status)
500 }
501}
502
503impl fmt::Display for FilteredTimeSerie {
504 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
505 write!(
506 f,
507 "FilteredTimeSerie {{\nlast_second: {},\nlast_minute:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\nlast_hour:\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n{:?}\n}}",
508 self.last_second,
509 &self.last_minute[0..10],
510 &self.last_minute[10..20],
511 &self.last_minute[20..30],
512 &self.last_minute[30..40],
513 &self.last_minute[40..50],
514 &self.last_minute[50..60],
515 &self.last_hour[0..10],
516 &self.last_hour[10..20],
517 &self.last_hour[20..30],
518 &self.last_hour[30..40],
519 &self.last_hour[40..50],
520 &self.last_hour[50..60]
521 )
522 }
523}
524
525fn socketaddr_cmp(a: &SocketAddr, b: &SocketAddr) -> Ordering {
526 let ordering = a.ip().cmp(&b.ip()).then(a.port().cmp(&b.port()));
527 debug_assert_eq!(
531 ordering == Ordering::Equal,
532 a.ip() == b.ip() && a.port() == b.port(),
533 "socketaddr_cmp is Equal iff ip and port both match"
534 );
535 ordering
536}