Skip to main content

bgpkit_broker/
query.rs

1//! Query-related structs and implementation.
2use crate::BrokerItem;
3use serde::{Deserialize, Serialize};
4use std::fmt::{Display, Formatter};
5use std::net::IpAddr;
6
7/// QueryParams represents the query parameters to the backend API.
8///
9/// Example for constructing a QueryParams:
10/// ```
11/// use bgpkit_broker::QueryParams;
12/// let mut params = QueryParams::new();
13/// params = params.ts_start("1633046400");
14/// params = params.ts_end("1633132800");
15/// params = params.collector_id("rrc00");
16/// params = params.project("riperis");
17/// params = params.data_type("rib");
18/// params = params.page(2);
19/// params = params.page_size(20);
20/// ```
21/// The above example constructs a query that searches for BGP archive files that are:
22/// - after 2021-10-01T00:00:00 UTC
23/// - before 2021-10-02T00:00:00 UTC
24/// - from collector `rrc00`
25/// - from `riperis` collectors (already implied by collector=`rrc00` though)
26/// - rib table dump files
27/// - second page
28/// - each page contains 20 items
29#[derive(Debug, Serialize, Deserialize, Clone)]
30pub struct QueryParams {
31    /// start unix timestamp: files with time after or equals to `ts_start` will match
32    pub ts_start: Option<String>,
33    /// end unix timestamp: files with time before or equals to `ts_end` will match
34    pub ts_end: Option<String>,
35    /// collector identifier, e.g. `rrc00` or `route-views2`
36    pub collector_id: Option<String>,
37    /// archive project name: `riperis` or `routeviews`
38    pub project: Option<String>,
39    /// archive data type: `rib` or `updates`
40    pub data_type: Option<String>,
41    /// page number to seek to, starting from 1, default to 1
42    pub page: i64,
43    /// number of items each page contains, default to 10, max to 100000
44    pub page_size: i64,
45    /// collector peer IP address (for listing peers info)
46    pub peers_ip: Option<IpAddr>,
47    /// collector peer ASN (for listing peers info)
48    pub peers_asn: Option<u32>,
49    /// collector peer full feed status (for listing peers info)
50    pub peers_only_full_feed: bool,
51}
52
53/// Sorting order enum
54#[derive(Debug, Serialize, Deserialize, Clone)]
55pub enum SortOrder {
56    /// `ASC` -> sort by increasing on timestamp
57    ASC,
58    /// `DESC` -> sort by decreasing on timestamp
59    DESC,
60}
61
62/// Default [QueryParams] values
63impl Default for QueryParams {
64    fn default() -> Self {
65        QueryParams {
66            ts_start: None,
67            ts_end: None,
68            collector_id: None,
69            project: None,
70            data_type: None,
71            page: 1,
72            page_size: 100,
73            peers_ip: None,
74            peers_asn: None,
75            peers_only_full_feed: false,
76        }
77    }
78}
79
80impl Display for SortOrder {
81    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82        match self {
83            SortOrder::ASC => {
84                write!(f, "asc")
85            }
86            SortOrder::DESC => {
87                write!(f, "desc")
88            }
89        }
90    }
91}
92
93impl Display for QueryParams {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        let mut params_vec = vec![];
96        if let Some(v) = &self.ts_start {
97            params_vec.push(format!("ts_start={}", v));
98        }
99        if let Some(v) = &self.ts_end {
100            params_vec.push(format!("ts_end={}", v));
101        }
102        if let Some(v) = &self.collector_id {
103            params_vec.push(format!("collector_id={}", v));
104        }
105        if let Some(v) = &self.project {
106            params_vec.push(format!("project={}", v));
107        }
108        if let Some(v) = &self.data_type {
109            params_vec.push(format!("data_type={}", v));
110        }
111        params_vec.push(format!("page={}", self.page));
112        params_vec.push(format!("page_size={}", self.page_size));
113
114        if !params_vec.is_empty() {
115            write!(f, "?{}", params_vec.join("&"))
116        } else {
117            write!(f, "")
118        }
119    }
120}
121
122impl QueryParams {
123    pub fn new() -> QueryParams {
124        QueryParams {
125            ts_start: None,
126            ts_end: None,
127            collector_id: None,
128            project: None,
129            data_type: None,
130            page: 1,
131            page_size: 10,
132            ..Default::default()
133        }
134    }
135
136    /// set starting timestamp for the search and returns a new [QueryParams] object.
137    ///
138    /// ```
139    /// use bgpkit_broker::QueryParams;
140    /// let mut params = QueryParams::new();
141    /// params = params.ts_start("1633046400");
142    /// ```
143    pub fn ts_start(self, ts_start: &str) -> Self {
144        QueryParams {
145            ts_start: Some(ts_start.to_string()),
146            ..self
147        }
148    }
149
150    /// set ending timestamp for the search and returns a new [QueryParams] object.
151    ///
152    /// ```
153    /// use bgpkit_broker::QueryParams;
154    /// let mut params = QueryParams::new();
155    /// params = params.ts_end("1633046400");
156    /// ```
157    pub fn ts_end(self, ts_end: &str) -> Self {
158        QueryParams {
159            ts_end: Some(ts_end.to_string()),
160            ..self
161        }
162    }
163
164    /// set page number for the each for pagination. **the page number starts from 1**.
165    ///
166    /// ```
167    /// use bgpkit_broker::QueryParams;
168    /// let mut params = QueryParams::new();
169    /// params = params.page(3);
170    /// ```
171    pub fn page(self, page: i64) -> Self {
172        QueryParams { page, ..self }
173    }
174
175    /// set each page's size (number of items per page).
176    ///
177    /// ```
178    /// use bgpkit_broker::QueryParams;
179    /// let mut params = QueryParams::new();
180    /// params = params.page_size(20);
181    /// ```
182    pub fn page_size(self, page_size: i64) -> Self {
183        QueryParams { page_size, ..self }
184    }
185
186    /// set the type of data to search for:
187    /// - `rib`: table dump files
188    /// - `updates`: BGP updates files
189    ///
190    /// Without specifying a data type, it defaults to search for all types.
191    ///
192    /// ```
193    /// use bgpkit_broker::QueryParams;
194    /// let mut params = QueryParams::new();
195    /// params = params.data_type("rib");
196    /// ```
197    pub fn data_type(self, data_type: &str) -> Self {
198        QueryParams {
199            data_type: Some(data_type.to_string()),
200            ..self
201        }
202    }
203
204    /// set searching for only data from specific project:
205    /// - `routeviews`: RouteViews
206    /// - `riperis`: RIPE RIS
207    ///
208    /// ```
209    /// use bgpkit_broker::QueryParams;
210    /// let mut params = QueryParams::new();
211    /// params = params.project("routeviews");
212    /// ```
213    pub fn project(self, project: &str) -> Self {
214        QueryParams {
215            project: Some(project.to_string()),
216            ..self
217        }
218    }
219
220    /// set searching for only data from specific collector,
221    /// examples: `rrc00`, `route-views2`
222    ///
223    /// ```
224    /// use bgpkit_broker::QueryParams;
225    /// let mut params = QueryParams::new();
226    /// params = params.collector_id("rrc00");
227    /// ```
228    pub fn collector_id(self, collector_id: &str) -> Self {
229        QueryParams {
230            collector_id: Some(collector_id.to_string()),
231            ..self
232        }
233    }
234}
235
236#[allow(dead_code)]
237#[derive(Debug, Clone, Serialize, Deserialize)]
238#[cfg_attr(feature = "cli", derive(tabled::Tabled))]
239pub struct BrokerCollector {
240    pub id: i64,
241    pub name: String,
242    pub url: String,
243    pub project: String,
244    pub updates_interval: i64,
245}
246
247#[allow(dead_code)]
248#[derive(Debug, Clone, Serialize, Deserialize)]
249#[cfg_attr(feature = "cli", derive(tabled::Tabled))]
250pub struct BrokerItemType {
251    pub id: i64,
252    pub name: String,
253}
254
255#[derive(Debug, Serialize, Deserialize)]
256pub(crate) struct CollectorLatestResult {
257    /// total number of items
258    pub count: u32,
259
260    /// array of [BrokerItem]
261    pub data: Vec<BrokerItem>,
262}
263
264/// Query result struct that contains data or error message
265#[derive(Debug, Serialize, Deserialize)]
266pub(crate) struct BrokerQueryResult {
267    /// total number of items
268    pub total: Option<i64>,
269    /// number of items returned in **current** call
270    pub count: Option<i64>,
271    /// the page number of the current call
272    pub page: Option<i64>,
273    /// the number of items per page
274    pub page_size: Option<i64>,
275    /// Error message
276    pub error: Option<String>,
277    /// the returning data [Item]s
278    pub data: Vec<BrokerItem>,
279}
280
281#[allow(clippy::unwrap_used)]
282impl Display for BrokerQueryResult {
283    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
284        write!(f, "{}", serde_json::to_string(self).unwrap())
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291
292    #[test]
293    fn test_param_to_string() {
294        let param = QueryParams {
295            ts_start: Some("1".to_string()),
296            ts_end: Some("2".to_string()),
297            collector_id: None,
298            project: Some("test_project".to_string()),
299            data_type: None,
300            page: 1,
301            page_size: 20,
302            ..Default::default()
303        };
304
305        assert_eq!(
306            "?ts_start=1&ts_end=2&project=test_project&page=1&page_size=20".to_string(),
307            param.to_string()
308        );
309
310        let param = QueryParams {
311            ts_start: None,
312            ts_end: None,
313            collector_id: None,
314            project: None,
315            data_type: None,
316            page: 1,
317            page_size: 20,
318            ..Default::default()
319        };
320
321        assert_eq!("?page=1&page_size=20".to_string(), param.to_string());
322    }
323}