Skip to main content

auraxis/api/request/
mod.rs

1mod builder;
2
3pub use builder::CensusRequestBuilder;
4
5use std::future::{Future, IntoFuture};
6
7use reqwest::Client;
8
9use crate::AuraxisError;
10
11use super::response::CensusResponse;
12
13#[derive(Debug, Clone)]
14pub struct CensusRequest {
15    client: Client,
16    collection: String,
17    url: String,
18    query_params: Vec<(String, String)>,
19}
20
21impl IntoFuture for CensusRequest {
22    type Output = Result<CensusResponse, AuraxisError>;
23    type IntoFuture = impl Future<Output = Self::Output>;
24
25    fn into_future(self) -> Self::IntoFuture {
26        async move {
27            let mut request = self.client.get(self.url);
28            if !self.query_params.is_empty() {
29                request = request.query(&self.query_params);
30            }
31
32            let response = request.send().await?;
33
34            CensusResponse::from_response(response).await
35        }
36    }
37}
38
39#[derive(Debug, Clone)]
40pub enum SortDirection {
41    Ascending,
42    Descending,
43}
44
45impl From<SortDirection> for &'static str {
46    fn from(direction: SortDirection) -> Self {
47        match direction {
48            SortDirection::Ascending => "1",
49            SortDirection::Descending => "-1",
50        }
51    }
52}
53
54#[derive(Debug, Clone)]
55pub struct Sort {
56    field: String,
57    direction: SortDirection,
58}
59
60#[derive(Debug, Default, Clone)]
61pub enum JoinType {
62    Inner = 0,
63    #[default]
64    Outer = 1,
65}
66
67#[derive(Debug, Clone)]
68pub struct Join {
69    r#type: String,
70    on: String,
71    to: String,
72    list: Option<bool>,
73    show: Option<Vec<String>>,
74    hide: Option<Vec<String>>,
75    inject_at: String,
76    terms: Option<Vec<Filter>>,
77    join_type: Option<JoinType>,
78}
79
80impl Join {
81    pub fn new(
82        r#type: impl Into<String>,
83        on: impl Into<String>,
84        to: impl Into<String>,
85        inject_at: impl Into<String>,
86    ) -> Self {
87        Self {
88            r#type: r#type.into(),
89            on: on.into(),
90            to: to.into(),
91            list: None,
92            show: None,
93            hide: None,
94            inject_at: inject_at.into(),
95            terms: None,
96            join_type: None,
97        }
98    }
99
100    pub fn list(mut self, list: bool) -> Self {
101        self.list = Some(list);
102        self
103    }
104
105    pub fn show(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
106        self.show = Some(fields.into_iter().map(Into::into).collect());
107        self
108    }
109
110    pub fn hide(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
111        self.hide = Some(fields.into_iter().map(Into::into).collect());
112        self
113    }
114
115    pub fn terms(mut self, terms: impl IntoIterator<Item = Filter>) -> Self {
116        self.terms = Some(terms.into_iter().collect());
117        self
118    }
119
120    pub fn join_type(mut self, join_type: JoinType) -> Self {
121        self.join_type = Some(join_type);
122        self
123    }
124}
125
126impl From<Join> for String {
127    fn from(join: Join) -> Self {
128        format_join(&join)
129    }
130}
131
132impl From<&Join> for String {
133    fn from(join: &Join) -> Self {
134        format_join(join)
135    }
136}
137
138#[derive(Debug, Clone)]
139pub struct Tree {
140    field: String,
141    list: Option<bool>,
142    prefix: Option<String>,
143    start: Option<String>,
144}
145
146#[derive(Copy, Clone, Debug)]
147pub enum FilterType {
148    LessThan,
149    LessThanEqualOrEqualTo,
150    GreaterThan,
151    GreaterThanOrEqualTo,
152    StartsWith,
153    Contains,
154    EqualTo,
155    NotEqualTo,
156}
157
158impl From<FilterType> for &'static str {
159    fn from(filter_type: FilterType) -> Self {
160        match filter_type {
161            FilterType::LessThan => "<",
162            FilterType::LessThanEqualOrEqualTo => "[",
163            FilterType::GreaterThan => ">",
164            FilterType::GreaterThanOrEqualTo => "]",
165            FilterType::StartsWith => "^",
166            FilterType::Contains => "*",
167            FilterType::EqualTo => "",
168            FilterType::NotEqualTo => "!",
169        }
170    }
171}
172
173#[derive(Debug, Clone)]
174pub struct Filter {
175    field: String,
176    filter: FilterType,
177    value: String,
178}
179
180impl From<Filter> for String {
181    fn from(filter: Filter) -> Self {
182        format!(
183            "{}={}{}",
184            filter.field,
185            <FilterType as Into<&'static str>>::into(filter.filter),
186            filter.value
187        )
188    }
189}
190
191impl From<&Filter> for String {
192    fn from(filter: &Filter) -> Self {
193        format!(
194            "{}={}{}",
195            filter.field,
196            <FilterType as Into<&'static str>>::into(filter.filter),
197            filter.value
198        )
199    }
200}
201
202impl Filter {
203    pub fn into_pair(self) -> (String, String) {
204        (
205            self.field,
206            format!(
207                "{}{}",
208                <FilterType as Into<&'static str>>::into(self.filter),
209                self.value
210            ),
211        )
212    }
213}
214
215fn format_join(join: &Join) -> String {
216    let show = join.show.as_ref().map(|show_fields| show_fields.join("'"));
217
218    let hide = join.hide.as_ref().map(|hide_fields| hide_fields.join("'"));
219
220    let terms = join.terms.as_ref().map(|terms| {
221        terms
222            .iter()
223            .map(String::from)
224            .collect::<Vec<String>>()
225            .join("'")
226    });
227
228    let mut join_formatted = format!(
229        "type:{}^on:{}^to:{}^inject_at:{}",
230        join.r#type, join.on, join.to, join.inject_at
231    );
232
233    if join.list == Some(true) {
234        join_formatted += "^list:1";
235    }
236
237    if let Some(show) = show {
238        join_formatted += format!("^show:{}", show).as_str();
239    }
240
241    if let Some(hide) = hide {
242        join_formatted += format!("^hide:{}", hide).as_str();
243    }
244
245    if let Some(terms) = terms {
246        join_formatted += format!("^terms:{}", terms).as_str();
247    }
248
249    if let Some(join_type) = &join.join_type {
250        match join_type {
251            JoinType::Inner => join_formatted += "^outer:0",
252            JoinType::Outer => join_formatted += "^outer:1",
253        }
254    }
255
256    join_formatted
257}
258
259#[cfg(test)]
260mod tests {
261    use super::{Filter, FilterType, Join, JoinType};
262
263    #[test]
264    fn join_serialization_matches_for_owned_and_borrowed() {
265        let join = Join::new("character", "character_id", "character_id", "character")
266            .list(true)
267            .show(["name.first", "faction_id"])
268            .terms([Filter {
269                field: "name.first_lower".to_string(),
270                filter: FilterType::StartsWith,
271                value: "te".to_string(),
272            }])
273            .join_type(JoinType::Inner);
274
275        let owned = String::from(join.clone());
276        let borrowed = String::from(&join);
277
278        assert_eq!(owned, borrowed);
279        assert!(owned.contains("^list:1"));
280        assert!(owned.contains("^outer:0"));
281        assert!(owned.contains("^terms:name.first_lower=^te"));
282    }
283
284    #[test]
285    fn join_builder_supports_show_only_serialization() {
286        let join = Join::new(
287            "characters_world",
288            "character_id",
289            "character_id",
290            "characters_world",
291        )
292        .show(["world_id"]);
293
294        let serialized = String::from(join);
295
296        assert_eq!(
297            serialized,
298            "type:characters_world^on:character_id^to:character_id^inject_at:characters_world^show:world_id"
299        );
300    }
301
302    #[test]
303    fn filter_into_pair_preserves_operator_in_value() {
304        let filter = Filter {
305            field: "name.first_lower".to_string(),
306            filter: FilterType::StartsWith,
307            value: "te st".to_string(),
308        };
309
310        assert_eq!(
311            filter.into_pair(),
312            ("name.first_lower".to_string(), "^te st".to_string())
313        );
314    }
315}