igdb_rs/
request_builder.rs

1use crate::request_filters::Filter;
2use std::str::FromStr;
3use std::string::ToString;
4use surf::middleware::HttpClient;
5use url::Url;
6
7const HEADER_KEY_NAME: &str = "user-key";
8
9#[derive(Clone)]
10/// Request Builder struct
11pub struct RequestBuilder {
12    pub(crate) fields: Vec<String>,
13    pub(crate) filters: Vec<Filter>,
14    pub(crate) sort: (String, String),
15    pub(crate) limit: usize,
16    pub(crate) search: String,
17}
18
19impl Default for RequestBuilder {
20    fn default() -> Self {
21        RequestBuilder {
22            fields: Vec::new(),
23            filters: vec![],
24            sort: (String::new(), String::new()),
25            limit: 10,
26            search: String::new(),
27        }
28    }
29}
30
31pub enum OrderBy {
32    Descending,
33    Ascending,
34}
35
36impl ToString for OrderBy {
37    fn to_string(&self) -> String {
38        match self {
39            OrderBy::Ascending => "asc",
40            OrderBy::Descending => "desc",
41        }
42        .into()
43    }
44}
45
46pub enum Equality {
47    Less,
48    LessOrEqual,
49    Greater,
50    GreaterOrEqual,
51    Equal,
52    NotEqual,
53}
54
55impl ToString for Equality {
56    fn to_string(&self) -> String {
57        match self {
58            Equality::Equal => "=",
59            Equality::NotEqual => "!=",
60            Equality::Greater => ">",
61            Equality::Less => "<",
62            Equality::GreaterOrEqual => ">=",
63            Equality::LessOrEqual => "<=",
64        }
65        .into()
66    }
67}
68
69impl RequestBuilder {
70    /// Creates a new Request Builder
71    ///It can also be created using IGDBClient::create_request();
72    /// # Examples
73    /// ```
74    ///use igdb_rs::request_builder::RequestBuilder;
75    ///
76    /// let mut request = RequestBuilder::new();
77    /// ```
78    /// ```
79    ///use igdb_rs::client::IGDBClient;
80    ///
81    ///let mut request = IGDBClient::create_request();
82    /// ```
83    pub fn new() -> RequestBuilder {
84        RequestBuilder::default()
85    }
86
87    pub(crate) fn build(&self, api_key: &str, url: &str) -> surf::Request<impl HttpClient> {
88        let body = &self.build_body();
89
90        log::debug!("url: {}, body: {}", url, String::from_utf8_lossy(body));
91
92        let mut req =
93            surf::Request::new(http::Method::GET, Url::from_str(url).unwrap()).body_bytes(body);
94
95        req.headers().insert(HEADER_KEY_NAME, api_key);
96        req.headers().insert("content-type", "application/text");
97
98        req
99    }
100
101    pub(crate) fn build_body(&self) -> Vec<u8> {
102        let fields = self
103            .fields
104            .iter()
105            .enumerate()
106            .fold(String::new(), |mut acc, (i, v)| {
107                acc.push_str(v);
108                let separator = match i {
109                    _ if i == (self.fields.len() - 1) => ";",
110                    _ => ",",
111                };
112                acc.push_str(separator);
113                acc
114            });
115
116        let filter_clause =
117            self.filters
118                .iter()
119                .enumerate()
120                .fold(String::new(), |mut acc, (i, filter)| {
121                    if i == 0 {
122                        acc.push_str("where ")
123                    }
124                    if i != 0 {
125                        acc.push_str(" & ")
126                    };
127
128                    acc.push_str(&format!(
129                        "{} {} {}",
130                        filter.key, filter.symbol, filter.value
131                    ));
132
133                    if i == (self.filters.len() - 1) {
134                        acc.push_str(";");
135                    }
136                    acc
137                });
138
139        self.format_body_parts(fields, filter_clause)
140            .as_bytes()
141            .to_vec()
142    }
143
144    fn format_body_parts(&self, fields: String, filters: String) -> String {
145        let mut order = String::new();
146
147        let mut body = format!("fields {}", fields);
148
149        if !str::is_empty(&self.search) {
150            body = format!("{} search \"{}\";", body, self.search);
151        }
152
153        if !self.filters.is_empty() {
154            body = format!("{} {}", body, filters);
155        }
156
157        if !str::is_empty(&self.sort.0) {
158            order.push_str(&format!("sort {} {}", self.sort.0, self.sort.1));
159            body = format!("{} {};", body, order);
160        }
161
162        body = format!("{} limit {};", body, self.limit);
163        body
164    }
165}
166
167#[test]
168fn request_builder_with_all_fields() {
169    let mut builder = RequestBuilder::new();
170
171    builder.all_fields();
172
173    let body = builder.build_body();
174
175    assert_eq!(
176        "fields *; limit 10;",
177        String::from_utf8_lossy(&body).to_owned()
178    );
179}
180
181#[test]
182fn request_builder_with_fields_and_where_clause_body_build() {
183    let mut builder = RequestBuilder::new();
184
185    builder
186        .add_field("name")
187        .add_field("involved_companies")
188        .add_where("name", Equality::Equal, "Conan")
189        .add_where("id", Equality::Less, 39047.to_string());
190
191    let body = builder.build_body();
192
193    assert_eq!(
194        "fields name,involved_companies; where name = Conan & id < 39047; limit 10;",
195        String::from_utf8_lossy(&body).to_owned()
196    );
197}
198
199#[test]
200fn request_builder_with_fields_where_clause_and_sort_asc_body_build() {
201    let mut builder = RequestBuilder::new();
202
203    builder
204        .add_field("name")
205        .add_field("involved_companies")
206        .add_where("id", Equality::Equal, 39047.to_string())
207        .add_where("name", Equality::Equal, "Conan")
208        .sort_by("name", OrderBy::Ascending)
209        .limit(2);
210
211    let body = builder.build_body();
212
213    assert_eq!(
214        "fields name,involved_companies; where id = 39047 & name = Conan; sort name asc; limit 2;",
215        String::from_utf8_lossy(&body).to_owned()
216    );
217}