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#[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}
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#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
142pub struct TcpFrontend {
143 pub cluster_id: String,
144 pub address: SocketAddr,
145 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#[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}