transdirect/
client.rs

1use num_traits::{Float,Unsigned};
2use serde::de::DeserializeOwned;
3use serde::Serialize;
4use restson::{RestClient, blocking::RestClient as BRestClient};
5
6use crate::Error;
7use crate::account::{Account,AuthenticateWith,Member};
8use crate::booking::{BookingRequest,BookingResponse};
9
10static API_ENDPOINT: &str = if cfg!(test) { 
11    "https://private-anon-a28d0f1a72-transdirectapiv4.apiary-mock.com/api/" }
12    else {
13    "https://www.transdirect.com.au/api/"
14};
15
16/// Client object for interacting with the API
17/// 
18/// You are forced to use this as a proxy for authentication: it is essentially
19/// a factory, but named for convenience (I think). A `Client` object can be
20/// constructed the constructors [`new`], [`from_auth`], [`from_basic_auth`],
21/// or [`from_apikey`].
22/// 
23/// Creates a synchronous (currently) client. Optimistically, we will implement
24/// an async version through tokio, but I have absolutely no idea what that
25/// entails.
26/// 
27/// # Examples
28/// This example details the basic task of retrieving a quote from the
29/// Transdirect API.
30/// 
31/// #[ignore]
32/// ```
33/// use transdirect::{TransdirectClient, BookingRequest};
34/// ```
35pub struct Client<'a> {
36    authenticated: bool,
37    restclient: BRestClient, // restson seems to have no advantages over reqwest
38    pub sender: Option<&'a Account>, // Should eventually be default
39}
40
41impl<'a> Client<'a> {
42    pub fn new() -> Self {
43        Self {
44            authenticated: false,
45            restclient: RestClient::new_blocking(API_ENDPOINT)
46                .expect("Should be a valid URL or connected to the internet"),
47            sender: None
48        }
49    }
50    
51    pub fn from_auth(auth: AuthenticateWith) -> Result<Self, Error> {
52        let mut newclient = Self::new();
53        
54        Self::auth(&mut newclient, auth)?;
55
56        Ok(newclient)
57    }
58    
59    pub fn from_basic(user: &str, password: &str) -> Result<Self, Error> {
60        Self::from_auth(AuthenticateWith::Basic(user, password))
61    }
62    
63    pub fn from_api_key(apikey: &str) -> Result<Self, Error> {
64        Self::from_auth(AuthenticateWith::APIKey(apikey))
65    }
66    
67    pub fn auth(&mut self, auth: AuthenticateWith) -> Result<(), Error> {
68        use AuthenticateWith::*;
69
70        match auth {
71            Basic(user, pass) => self.restclient.set_auth(user, pass),
72            APIKey(key) => self.restclient.set_header("Api-key", key).expect("Should be able to set Api-key header"),
73        }
74        
75        match self.restclient.get::<_, Member>(()) {
76            Ok(_) => {
77                self.authenticated = true;
78                Ok(())
79            },
80            Err(err) => Err(Error::HTTPError(err.to_string())),
81        }
82    }
83    
84    pub fn quotes<'b, T, U>(&self, request: &'b BookingRequest<T, U>) -> Result<BookingResponse<T, U>, Error>
85    where T: Unsigned + DeserializeOwned + Serialize, U: Float + DeserializeOwned + Serialize {
86        self
87            .restclient
88            .post_capture::<_, _, BookingResponse<T, U>>((), request)
89            .map(|s| s.into_inner())
90            .map_err(|e| Error::HTTPError(e.to_string())) // Eventually remove entirely
91    }
92    
93    /// Gets a copy of a booking from its id; note that this is
94    /// different from its connote (consignment note or tracking number).
95    /// 
96    /// # Examples
97    /// 
98    /// ```no_run
99    /// use transdirect::BookingResponse;
100    /// use transdirect::TransdirectClient as Client;
101    /// let c = Client::new();
102    /// //...
103    /// let oldbooking: BookingResponse = c.booking(623630).expect("Valid booking");
104    /// // Do something interesting
105    /// # // oldbooking.update()
106    pub fn booking<T, U>(&self, booking_id: u32) -> Result<BookingResponse<T, U>, Error>
107    where T: Unsigned + DeserializeOwned, U: Float + DeserializeOwned {
108        self
109            .restclient
110            .get::<u32, BookingResponse<T, U>>(booking_id)
111            .map(|s| s.into_inner())
112            .map_err(|e| Error::HTTPError(e.to_string()))
113    }
114}
115
116
117#[cfg(test)]
118mod tests {
119    use crate::*;
120    use crate::TransdirectClient as Client;
121    
122    fn src_dest() -> (Account, Account){
123        (Account { 
124            address: "130 Royal St".to_string(),
125            name: "John Smith".to_string(),
126            email: "jsmith@google.com".to_string(),
127            postcode: "6008".to_string(),
128            state: "WA".to_string(),
129            suburb: "East Perth".to_string(),
130            kind: "business".to_string(),
131            country: "AU".to_string(),
132            company_name: "Royal Australian Mint".to_string()
133        },
134        Account {
135            address: "1 Pearl Bay Ave".to_string(),
136            name: "Jane Doe".to_string(),
137            email: "jdoe@google.com".to_string(),
138            postcode: "2008".to_string(),
139            state: "NSW".to_string(),
140            suburb: "Mosman".to_string(),
141            kind: "residential".to_string(),
142            country: "AU".to_string(),
143            company_name: "Sydney Harbour Operations Ltd.".to_string()
144        }
145        )
146    }
147    
148    #[test]
149    fn should_get_response() {
150        let c = Client::new();
151        let items = vec![Product { weight: 2.0, quantity: 1, dimensions: Dimensions { length: 5.0f64, width: 5.0f64, height: 5.0f64 }, ..Product::new() }];
152        let (sender, receiver) = src_dest();
153        let b = BookingRequest {
154            declared_value: 53.3,
155            items,
156            sender: Some(&sender),
157            receiver: Some(&receiver),
158            ..BookingRequest::default()
159        };
160
161        let m = c.quotes(&b);
162        
163        assert!(m.is_ok());
164    }
165    
166    #[test]
167    fn should_get_booking() {
168        let c = Client::new();
169        let booking = c.booking::<u32, f64>(623630);
170
171        assert!(booking.is_ok());
172    }
173}