1use crate::error::{Error, ParseUrlError};
2use mime::Mime;
3use reqwest::header::HeaderValue;
4use serde::Deserialize;
5use std::fmt;
6use url::Url;
7
8#[derive(Debug)]
10pub enum TargetState {
11 Active,
12 Dropped,
13 Any,
14}
15
16impl fmt::Display for TargetState {
17 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18 match self {
19 Self::Active => f.write_str("active"),
20 Self::Dropped => f.write_str("dropped"),
21 Self::Any => f.write_str("any"),
22 }
23 }
24}
25
26#[derive(Debug, Copy, Clone, Deserialize, Eq, PartialEq)]
28pub enum TargetHealth {
29 #[serde(alias = "up")]
30 Up,
31 #[serde(alias = "down")]
32 Down,
33 #[serde(alias = "unknown")]
34 Unknown,
35}
36
37impl TargetHealth {
38 pub fn is_up(&self) -> bool {
39 *self == Self::Up
40 }
41
42 pub fn is_down(&self) -> bool {
43 *self == Self::Down
44 }
45
46 pub fn is_unknown(&self) -> bool {
47 *self == Self::Unknown
48 }
49}
50
51impl fmt::Display for TargetHealth {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match self {
54 Self::Up => f.write_str("up"),
55 Self::Down => f.write_str("down"),
56 Self::Unknown => f.write_str("unknown"),
57 }
58 }
59}
60
61#[derive(Debug, Copy, Clone, Deserialize, Eq, PartialEq)]
63pub enum RuleHealth {
64 #[serde(alias = "ok")]
65 Good,
66 #[serde(alias = "err")]
67 Bad,
68 #[serde(alias = "unknown")]
69 Unknown,
70}
71
72impl RuleHealth {
73 pub fn is_good(&self) -> bool {
74 *self == Self::Good
75 }
76
77 pub fn is_bad(&self) -> bool {
78 *self == Self::Bad
79 }
80
81 pub fn is_unknown(&self) -> bool {
82 *self == Self::Unknown
83 }
84}
85
86impl fmt::Display for RuleHealth {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 Self::Good => f.write_str("ok"),
90 Self::Bad => f.write_str("err"),
91 Self::Unknown => f.write_str("unknown"),
92 }
93 }
94}
95
96#[derive(Debug, Copy, Clone, Deserialize, Eq, PartialEq)]
98pub enum AlertState {
99 #[serde(alias = "inactive")]
100 Inactive,
101 #[serde(alias = "pending")]
102 Pending,
103 #[serde(alias = "firing")]
104 Firing,
105}
106
107impl AlertState {
108 pub fn is_inactive(&self) -> bool {
109 *self == Self::Inactive
110 }
111
112 pub fn is_pending(&self) -> bool {
113 *self == Self::Pending
114 }
115
116 pub fn is_firing(&self) -> bool {
117 *self == Self::Firing
118 }
119}
120
121impl fmt::Display for AlertState {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 match self {
124 Self::Inactive => f.write_str("inactive"),
125 Self::Pending => f.write_str("pending"),
126 Self::Firing => f.write_str("firing"),
127 }
128 }
129}
130
131#[derive(Copy, Clone, Debug)]
133pub enum RuleKind {
134 Alerting,
135 Recording,
136}
137
138impl RuleKind {
139 pub(crate) fn to_query_param(&self) -> String {
140 match self {
141 Self::Alerting => String::from("alert"),
142 Self::Recording => String::from("record"),
143 }
144 }
145}
146
147impl fmt::Display for RuleKind {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 Self::Alerting => f.write_str("alerting"),
151 Self::Recording => f.write_str("recording"),
152 }
153 }
154}
155
156#[allow(clippy::enum_variant_names)]
157#[derive(Debug, Clone, PartialEq)]
158pub(crate) enum Label<'a> {
159 Equal((&'a str, &'a str)),
160 NotEqual((&'a str, &'a str)),
161 RegexEqual((&'a str, &'a str)),
162 RegexNotEqual((&'a str, &'a str)),
163}
164
165impl<'a> fmt::Display for Label<'a> {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
167 match self {
168 Self::Equal((k, v)) => write!(f, "{}=\"{}\"", k, v),
169 Self::NotEqual((k, v)) => write!(f, "{}!=\"{}\"", k, v),
170 Self::RegexEqual((k, v)) => write!(f, "{}=~\"{}\"", k, v),
171 Self::RegexNotEqual((k, v)) => write!(f, "{}!~\"{}\"", k, v),
172 }
173 }
174}
175
176pub(crate) trait ToBaseUrl {
181 fn to_base_url(self) -> Result<Url, Error>;
182}
183
184impl ToBaseUrl for &str {
185 fn to_base_url(self) -> Result<Url, Error> {
186 Url::parse(self).map_err(|source| {
187 Error::ParseUrl(ParseUrlError {
188 message: "failed to build Prometheus server base URL",
189 source,
190 })
191 })
192 }
193}
194
195impl ToBaseUrl for String {
196 fn to_base_url(self) -> Result<Url, Error> {
197 Url::parse(&self).map_err(|source| {
198 Error::ParseUrl(ParseUrlError {
199 message: "failed to build Prometheus server base URL",
200 source,
201 })
202 })
203 }
204}
205
206pub(crate) fn build_final_url(mut url: Url, path: &str) -> Url {
207 let base_path = url.path();
208 match base_path {
209 "/" => return url.join(path).unwrap(),
210 _ => {
211 let p = format!("{}/{}", base_path, path);
212 url.set_path(&p);
213 }
214 }
215 url
216}
217
218pub(crate) fn is_json(v: Option<&HeaderValue>) -> bool {
219 match v
220 .and_then(|h| h.to_str().ok())
221 .and_then(|h| h.parse::<Mime>().ok())
222 {
223 Some(mime) => match (mime.type_(), mime.subtype()) {
224 (mime::APPLICATION, mime::JSON) => true,
225 _ => false,
226 },
227 None => false,
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::{build_final_url, is_json, ToBaseUrl};
234
235 #[test]
236 fn test_simple_str_to_url() {
237 let s = "http://127.0.0.1:9090";
238 let url = s.to_base_url().unwrap();
239 assert_eq!("http://127.0.0.1:9090/", url.as_str());
240 }
241
242 #[test]
243 fn test_proxied_str_to_url() {
244 let s = "http://proxy.example.com/prometheus";
245 let url = s.to_base_url().unwrap();
246 assert_eq!("http://proxy.example.com/prometheus", url.as_str());
247 }
248
249 #[test]
250 fn test_simple_string_to_url() {
251 let s = String::from("http://127.0.0.1:9090");
252 let url = s.to_base_url().unwrap();
253 assert_eq!("http://127.0.0.1:9090/", url.as_str());
254 }
255
256 #[test]
257 fn test_proxied_string_to_url() {
258 let s = String::from("http://proxy.example.com/prometheus");
259 let url = s.to_base_url().unwrap();
260 assert_eq!("http://proxy.example.com/prometheus", url.as_str());
261 }
262
263 #[test]
264 fn test_simple_url_finalization() {
265 let s = String::from("http://127.0.0.1:9090");
266 let url = s.to_base_url().unwrap();
267 let final_url = build_final_url(url, "api/v1/targets");
268 assert_eq!("http://127.0.0.1:9090/api/v1/targets", final_url.as_str());
269 }
270
271 #[test]
272 fn test_proxied_url_finalization() {
273 let s = String::from("http://proxy.example.com/prometheus");
274 let url = s.to_base_url().unwrap();
275 let final_url = build_final_url(url, "api/v1/targets");
276 assert_eq!(
277 "http://proxy.example.com/prometheus/api/v1/targets",
278 final_url.as_str()
279 );
280 }
281
282 #[test]
283 fn test_is_json() {
284 let header = reqwest::header::HeaderValue::from_static("application/json");
285 assert!(is_json(Some(&header)));
286 }
287
288 #[test]
289 fn test_is_json_with_charset() {
290 let header = reqwest::header::HeaderValue::from_static("application/json; charset=utf-8");
291 assert!(is_json(Some(&header)));
292 }
293}