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}