1#![allow(dead_code)]
104extern crate failure;
105extern crate reqwest;
106extern crate serde;
107extern crate serde_json;
108
109#[cfg(test)]
110extern crate mockito;
111
112use serde::{Serialize, Deserialize};
113use failure::Error;
114use reqwest::header;
115use reqwest::Url;
116use std::marker::PhantomData;
117
118const URL: &str = "https://api.airtable.com/v0";
119
120#[derive(Debug)]
121pub struct Base<T: Record> {
122 http_client: reqwest::Client,
123
124 table: String,
125 api_key: String,
126 app_key: String,
127
128 phantom: PhantomData<T>,
129}
130
131pub fn new<T>(api_key: &str, app_key: &str, table: &str) -> Base<T>
132where
133 T: Record,
134{
135 let mut headers = header::HeaderMap::new();
136 headers.insert(
137 header::AUTHORIZATION,
138 header::HeaderValue::from_str(&format!("Bearer {}", &api_key)).expect("invalid api key"),
139 );
140
141 headers.insert(
142 reqwest::header::CONTENT_TYPE,
143 header::HeaderValue::from_str("application/json").expect("invalid content type"),
144 );
145
146 let http_client = reqwest::Client::builder()
147 .default_headers(headers)
148 .build()
149 .expect("unable to create client");
150
151 Base {
152 http_client,
153 api_key: api_key.to_owned(),
154 app_key: app_key.to_owned(),
155 table: table.to_owned(),
156 phantom: PhantomData,
157 }
158}
159
160#[derive(Serialize, Deserialize, Debug)]
161struct SRecord<T> {
162 #[serde(default, skip_serializing)]
163 id: String,
164 fields: T,
165}
166
167#[derive(Deserialize, Debug)]
168struct RecordPage<T> {
169 records: Vec<SRecord<T>>,
170
171 #[serde(default)]
172 offset: String,
173}
174
175pub struct Paginator<'base, T: Record> {
176 base: &'base Base<T>,
177 offset: Option<String>,
179 iterator: std::vec::IntoIter<T>,
180 query_builder: QueryBuilder<'base, T>,
181}
182
183impl<'base, T> Iterator for Paginator<'base, T>
184where
185 for<'de> T: Deserialize<'de>,
186 T: Record,
187{
188 type Item = T;
189 fn next(&mut self) -> Option<Self::Item> {
191 let next = self.iterator.next();
192 if next.is_some() {
193 return next;
194 }
195
196 if self.offset.is_none() {
197 return None;
198 }
199
200 let mut url = Url::parse(&format!(
201 "{}/{}/{}",
202 URL, self.base.app_key, self.base.table
203 ))
204 .unwrap();
205 url.query_pairs_mut()
206 .append_pair("offset", self.offset.as_ref().unwrap());
207
208 if self.query_builder.view.is_some() {
209 url.query_pairs_mut()
210 .append_pair("view", self.query_builder.view.as_ref().unwrap());
211 }
212
213 if self.query_builder.formula.is_some() {
214 url.query_pairs_mut().append_pair(
215 "filterByFormula",
216 self.query_builder.formula.as_ref().unwrap(),
217 );
218 }
219
220 if self.query_builder.sort.is_some() {
221 for (i, ref sort) in self.query_builder.sort.as_ref().unwrap().iter().enumerate() {
222 url.query_pairs_mut()
223 .append_pair(&format!("sort[{}][field]", i), &sort.0);
224 url.query_pairs_mut()
225 .append_pair(&format!("sort[{}][direction]", i), &sort.1.to_string());
226 }
227 }
228
229 let mut response = self
232 .base
233 .http_client
234 .get(url.as_str())
235 .send()
236 .ok()?;
237
238 let results: RecordPage<T> = response.json().ok()?;
239
240 if results.offset.is_empty() {
241 self.offset = None;
242 } else {
243 self.offset = Some(results.offset);
244 }
245
246 let window: Vec<T> = results
247 .records
248 .into_iter()
249 .map(|record| {
250 let mut record_t: T = record.fields;
251 record_t.set_id(record.id);
252 record_t
253 })
254 .collect();
255
256 self.iterator = window.into_iter();
257 self.iterator.next()
258 }
259}
260
261pub trait Record {
262 fn set_id(&mut self, String);
263 fn id(&self) -> &str;
264}
265
266pub enum SortDirection {
267 Descending,
268 Ascending,
269}
270
271impl ToString for SortDirection {
272 fn to_string(&self) -> String {
273 match self {
274 SortDirection::Descending => String::from("desc"),
275 SortDirection::Ascending => String::from("asc"),
276 }
277 }
278}
279
280pub struct QueryBuilder<'base, T: Record> {
281 base: &'base Base<T>,
282
283 fields: Option<Vec<String>>,
284 view: Option<String>,
285 formula: Option<String>,
286
287 sort: Option<Vec<(String, SortDirection)>>,
289}
290
291impl<'base, T> QueryBuilder<'base, T>
292where
293 for<'de> T: Deserialize<'de>,
294 T: Record,
295{
296 pub fn view(mut self, view: &str) -> Self {
297 self.view = Some(view.to_owned());
298 self
299 }
300
301 pub fn formula(mut self, formula: &str) -> Self {
302 self.formula = Some(formula.to_owned());
303 self
304 }
305
306 pub fn sort(mut self, field: &str, direction: SortDirection) -> Self {
307 match self.sort {
308 None => {
309 self.sort = Some(vec![(field.to_owned(), direction)]);
310 }
311 Some(ref mut sort) => {
312 let tuple = (field.to_owned(), direction);
313 sort.push(tuple);
314 }
315 };
316 self
317 }
318}
319
320impl<'base, T> IntoIterator for QueryBuilder<'base, T>
321where
322 for<'de> T: Deserialize<'de>,
323 T: Record,
324{
325 type Item = T;
326 type IntoIter = Paginator<'base, T>;
327
328 fn into_iter(self) -> Self::IntoIter {
329 Paginator {
330 base: &self.base,
331 offset: Some("".to_owned()),
332 iterator: vec![].into_iter(),
333 query_builder: self,
334 }
335 }
336}
337
338impl<T> Base<T>
339where
340 for<'de> T: Deserialize<'de>,
341 T: Record,
342{
343 pub fn query(&self) -> QueryBuilder<T> {
344 QueryBuilder {
345 base: self,
346 fields: None,
347 view: None,
348 formula: None,
349 sort: None,
350 }
351 }
352
353 pub fn create(&self, record: &T) -> Result<(), Error>
354 where
355 T: serde::Serialize,
356 {
357 let url = format!("{}/{}/{}", URL, self.app_key, self.table);
358
359 let serializing_record = SRecord {
360 id: String::new(),
361 fields: record,
362 };
363
364 let json = serde_json::to_string(&serializing_record)?;
365
366 self.http_client
367 .post(&url)
368 .body(json)
369 .send()?
370 .error_for_status()?;
371
372 Ok(())
373 }
374
375 pub fn update(&self, record: &T) -> Result<(), Error>
380 where
381 T: serde::Serialize,
382 {
383 let url = format!("{}/{}/{}/{}", URL, self.app_key, self.table, record.id());
384
385 let serializing_record = SRecord {
386 id: record.id().to_owned(),
387 fields: record,
388 };
389
390 let json = serde_json::to_string(&serializing_record)?;
391
392 self.http_client
393 .patch(&url)
394 .body(json)
395 .send()?
396 .error_for_status()?;
397
398 Ok(())
399 }
400}