square_ox/api/
locations.rs

1/*!
2Customers functionality of the [Square API](https://developer.squareup.com).
3 */
4
5use crate::client::SquareClient;
6use crate::api::{Verb, SquareAPI};
7use crate::errors::{SquareError, LocationBuildError, ValidationError};
8use crate::response::SquareResponse;
9use crate::objects::{
10    Address, BusinessHours, BusinessHoursPeriod, Coordinates, Location, TaxIds,
11    enums::{
12        Currency, LocationStatus, LocationType
13    }
14};
15
16use serde::{Deserialize, Serialize};
17use square_ox_derive::Builder;
18use crate::builder::{Builder, ParentBuilder, Validate, Buildable};
19
20impl SquareClient {
21    pub fn locations(&self) -> Locations {
22        Locations {
23            client: &self,
24        }
25    }
26}
27
28pub struct Locations<'a> {
29    client: &'a SquareClient,
30}
31
32impl<'a> Locations<'a> {
33    /// See which [Location](Location)s are available by requesting the information from the
34    /// [Square API](https://developer.squareup.com) and subsequently receiving them formatted as a
35    /// list of [Location](Location)s.
36    /// # Example
37    /// ```rust
38    ///use square_ox::{
39    ///    response::{SquareResponse, ResponseError},
40    ///    client::SquareClient
41    ///    };
42    ///
43    /// async {
44    ///     let locations = SquareClient::new("some_token")
45    ///         .locations()
46    ///         .list()
47    ///         .await;
48    /// };
49    /// ```
50    pub async fn list(self) -> Result<SquareResponse, SquareError> {
51        self.client.request(
52            Verb::GET,
53            SquareAPI::Locations("".to_string()),
54            None::<&Location>,
55            None,
56        ).await
57    }
58
59    /// Create a new [Location](Location) at the [Square API](https://developer.squareup.com).
60    /// # Arguments
61    /// * `new_location` - A [LocationCreationWrapper](LocationCreationWrapper).
62    /// # Example
63    /// ```rust
64    ///use square_ox::{
65    ///         response::{SquareResponse, ResponseError},
66    ///         client::SquareClient,
67    ///         builder::Builder,
68    ///         api::locations::LocationCreationWrapper
69    ///     };
70    /// use square_ox::builder::Buildable;
71    ///
72    ///  async {
73    ///     let location = Builder::from(LocationCreationWrapper::default())
74    ///         .name("The Foo Bar".to_string())
75    ///         .build()
76    ///         .await
77    ///         .unwrap();
78    ///     let res = SquareClient::new("some_token")
79    ///         .locations()
80    ///         .create(location)
81    ///         .await;
82    /// };
83    /// ```
84    pub async fn create(self, new_location: LocationCreationWrapper)
85                                 -> Result<SquareResponse, SquareError> {
86        self.client.request(
87            Verb::POST,
88            SquareAPI::Locations("".to_string()),
89            Some(&new_location),
90            None,
91        ).await
92    }
93
94    /// Update an existing [Location](Location) at the [Square API](https://developer.squareup.com).
95    /// # Arguments
96    /// * `updated_location` - A [LocationCreationWrapper](LocationCreationWrapper).
97    /// * `location_id` - The id of the location that is to be updated.
98    /// # Example
99    /// ```rust
100    ///  use square_ox::{
101    ///         response::{SquareResponse, ResponseError},
102    ///         client::SquareClient,
103    ///         builder::Builder,
104    ///         api::locations::LocationCreationWrapper
105    ///     };
106    ///
107    ///  async {
108    ///     let location = Builder::from(LocationCreationWrapper::default())
109    ///         .name("The New Foo Bar".to_string())
110    ///         .build()
111    ///         .await
112    ///         .unwrap();
113    ///     let res = SquareClient::new("some_token")
114    ///         .locations()
115    ///         .update(location, "foo_bar_id".to_string())
116    ///         .await;
117    /// };
118    /// ```
119    pub async fn update(self, updated_location: LocationCreationWrapper, location_id: String)
120                                 -> Result<SquareResponse, SquareError> {
121        self.client.request(
122            Verb::PUT,
123            SquareAPI::Locations(format!("/{}", location_id)),
124            Some(&updated_location),
125            None,
126        ).await
127    }
128
129    /// Retrieve a [Location](Location) from [Square API](https://developer.squareup.com) by the
130    /// location id.
131    /// # Arguments
132    /// * `location_id` - The id of the location that is to be retrieved.
133    /// # Example
134    /// ```rust
135    /// use square_ox::{
136    ///    response::{SquareResponse, ResponseError},
137    ///    client::SquareClient
138    ///    };
139    ///
140    ///  async {
141    ///     let res = SquareClient::new("some_token")
142    ///         .locations()
143    ///         .retrieve("foo_bar_id".to_string())
144    ///         .await;
145    /// };
146    /// ```
147    pub async fn retrieve(self, location_id: String)
148                                   -> Result<SquareResponse, SquareError> {
149        self.client.request(
150            Verb::GET,
151            SquareAPI::Locations(format!("/{}", location_id)),
152            None::<&LocationCreationWrapper>,
153            None,
154        ).await
155    }
156}
157
158// -------------------------------------------------------------------------------------------------
159// LocationCreationWrapper builder implementation
160// -------------------------------------------------------------------------------------------------
161/// Build a  wrapping a [Location](Location)
162///
163/// When passing a [Location](Location) to one of the request methods, they almost always must
164/// be wrapped within a [LocationCreationWrapper](LocationCreationWrapper) to adhere to the
165/// [Square API](https://developer.squareup.com) contract.
166///
167/// A [Location](Location) must have a name upon creation, otherwise it is not seen as a valid
168/// new [Location](Location).
169/// * `.name()`
170///
171/// # Example: Build a [LocationCreationWrapper](LocationCreationWrapper)
172/// ```
173/// use square_ox::{
174///     builder::Builder,
175///     api::locations::LocationCreationWrapper,
176/// };
177///
178/// async {
179///     let builder = Builder::from(LocationCreationWrapper::default())
180///     .name("The Foo Bar".to_string())
181///     .build()
182///     .await;
183/// };
184/// ```
185#[derive(Clone, Debug, Serialize, Deserialize, Default)]
186pub struct LocationCreationWrapper {
187    location: Location
188}
189
190impl Validate for LocationCreationWrapper {
191    fn validate(self) -> Result<Self, ValidationError> where Self: Sized {
192        if self.location.name.is_some() {
193            Ok(self)
194        } else {
195            Err(ValidationError)
196        }
197
198    }
199}
200
201impl<T: ParentBuilder> Builder<LocationCreationWrapper, T> {
202    pub fn name(mut self, name: String) -> Self {
203        self.body.location.name = Some(name);
204
205        self
206    }
207
208    pub fn address(mut self, address: Address) -> Self {
209        self.body.location.address = Some(address);
210
211        self
212    }
213
214    pub fn business_email(mut self, business_email: String) -> Self {
215        self.body.location.business_email = Some(business_email);
216
217        self
218    }
219
220    /// Add individual [BusinessHoursPeriod](BusinessHoursPeriod)'s by the use of this method.
221    pub fn add_business_hours_period(mut self, business_hours_period: BusinessHoursPeriod) -> Self {
222        match self.body.location.business_hours.take() {
223            Some(mut business_hours) => {
224                business_hours.periods.push(business_hours_period);
225                self.body.location.business_hours = Some(business_hours);
226            }
227            None => self.body.location.business_hours = Some(BusinessHours {
228                periods: vec![business_hours_period]
229            })
230        }
231
232        self
233    }
234
235    /// Add a complete [BusinessHours](BusinessHours) object by using this method.
236    pub fn business_hours(mut self, business_hours: BusinessHours) -> Self {
237        self.body.location.business_hours = Some(business_hours);
238
239        self
240    }
241
242    pub fn business_name(mut self, business_name: String) -> Self {
243        self.body.location.business_name = Some(business_name);
244
245        self
246    }
247
248    /// Add an individual *capability* by the use of this method.
249    pub fn add_capability(mut self, capability: String) -> Self {
250        match self.body.location.capabilities.take() {
251            Some(mut capabilities) => {
252                capabilities.push(capability);
253                self.body.location.capabilities = Some(capabilities)
254            }
255            None => self.body.location.capabilities = Some(vec![capability]),
256        }
257
258        self
259    }
260
261    /// Add multiple *capabilities* at once through this method. This method will overwrite all
262    /// other *capabilities* that are already held by the [Location](Location) object.
263    pub fn capabilities(mut self, capabilities: Vec<String>) -> Self {
264        self.body.location.capabilities = Some(capabilities);
265
266        self
267    }
268
269    pub fn coordinates(mut self, coordinates: Coordinates) -> Self {
270        self.body.location.coordinates = Some(coordinates);
271
272        self
273    }
274
275    pub fn country(mut self, country: String) -> Self {
276        self.body.location.country = Some(country);
277
278        self
279    }
280
281    pub fn currency(mut self, currency: Currency) -> Self {
282        self.body.location.currency = Some(currency);
283
284        self
285    }
286
287    pub fn description(mut self, description: String) -> Self {
288        self.body.location.description = Some(description);
289
290        self
291    }
292
293    pub fn facebook_url(mut self, facebook_url: String) -> Self {
294        self.body.location.facebook_url = Some(facebook_url);
295
296        self
297    }
298
299    pub fn full_format_logo_url(mut self, full_format_logo_url: String) -> Self {
300        self.body.location.full_format_logo_url = Some(full_format_logo_url);
301
302        self
303    }
304
305    pub fn instagram_username(mut self, instagram_username: String) -> Self {
306        self.body.location.instagram_username = Some(instagram_username);
307
308        self
309    }
310
311    pub fn language_code(mut self, language_code: String) -> Self {
312        self.body.location.language_code = Some(language_code);
313
314        self
315    }
316
317    pub fn logo_url(mut self, logo_url: String) -> Self {
318        self.body.location.logo_url = Some(logo_url);
319
320        self
321    }
322
323    pub fn mcc(mut self, mcc: String) -> Self {
324        self.body.location.mcc = Some(mcc);
325
326        self
327    }
328
329    pub fn merchant_id(mut self, merchant_id: String) -> Self {
330        self.body.location.merchant_id = Some(merchant_id);
331
332        self
333    }
334
335    pub fn phone_number(mut self, phone_number: String) -> Self {
336        self.body.location.phone_number = Some(phone_number);
337
338        self
339    }
340
341    pub fn pos_background_url(mut self, pos_background_url: String) -> Self {
342        self.body.location.pos_background_url = Some(pos_background_url);
343
344        self
345    }
346
347    pub fn status(mut self, status: LocationStatus) -> Self {
348        self.body.location.status = Some(status);
349
350        self
351    }
352
353    pub fn tax_ids(mut self, tax_ids: TaxIds) -> Self {
354        self.body.location.tax_ids = Some(tax_ids);
355
356        self
357    }
358
359    pub fn timezone(mut self, timezone: String) -> Self {
360        self.body.location.timezone = Some(timezone);
361
362        self
363    }
364
365    pub fn twitter_username(mut self, twitter_username: String) -> Self {
366        self.body.location.twitter_username = Some(twitter_username);
367
368        self
369    }
370
371    pub fn location_type(mut self, location_type: LocationType) -> Self {
372        self.body.location.type_name = Some(location_type);
373
374        self
375    }
376
377    pub fn website_url(mut self, website_url: String) -> Self {
378        self.body.location.website_url = Some(website_url);
379
380        self
381    }
382}
383
384#[cfg(test)]
385mod test_locations {
386    use super::*;
387
388    #[tokio::test]
389    async fn test_list_locations() {
390        use dotenv::dotenv;
391        use std::env;
392
393        dotenv().ok();
394        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
395        let sut = SquareClient::new(&access_token);
396
397        let result = sut.locations()
398            .list()
399            .await;
400        assert!(result.is_ok())
401    }
402
403    #[tokio::test]
404    async fn test_location_builder() {
405        let expected = Location {
406            id: None,
407            name: Some("New Test Location".to_string()),
408            business_email: None,
409            address: None,
410            timezone: None,
411            capabilities: None,
412            status: None,
413            created_id: None,
414            coordinates: None,
415            country: None,
416            created_at: None,
417            currency: None,
418            description: None,
419            facebook_url: Some("some_url".to_string()),
420            full_format_logo_url: None,
421            logo_url: None,
422            instagram_username: None,
423            language_code: None,
424            mcc: None,
425            merchant_id: None,
426            phone_number: None,
427            pos_background_url: None,
428            tax_ids: None,
429            twitter_username: None,
430            type_name: Some(LocationType::Physical),
431            business_hours: None,
432            business_name: None,
433            website_url: None
434        };
435        let actual = Builder::from(LocationCreationWrapper::default())
436            .name("New Test Location".to_string())
437            .facebook_url("some_url".to_string())
438            .location_type(LocationType::Physical)
439            .build();
440
441        assert!(actual.is_ok());
442
443        assert_eq!(format!("{:?}", expected), format!("{:?}", actual.unwrap().location))
444    }
445
446    #[tokio::test]
447    async fn test_location_builder_fail() {
448        let res = Builder::from(LocationCreationWrapper::default())
449            .facebook_url("some_url".to_string())
450            .location_type(LocationType::Physical)
451            .build();
452
453        assert!(res.is_err());
454    }
455
456    // #[tokio::test]
457    async fn test_create_location() {
458        use dotenv::dotenv;
459        use std::env;
460
461        dotenv().ok();
462        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
463        let sut = SquareClient::new(&access_token);
464
465        let input = LocationCreationWrapper {
466            location: Location {
467                id: None,
468                name: Some("New Test Location".to_string()),
469                business_name: None,
470                address: None,
471                timezone: None,
472                capabilities: None,
473                status: None,
474                created_id: None,
475                coordinates: None,
476                country: None,
477                created_at: None,
478                currency: None,
479                description: None,
480                facebook_url: None,
481                full_format_logo_url: None,
482                logo_url: None,
483                instagram_username: None,
484                language_code: None,
485                mcc: None,
486                merchant_id: None,
487                phone_number: None,
488                pos_background_url: None,
489                tax_ids: None,
490                twitter_username: None,
491                type_name: Some(LocationType::Physical),
492                business_hours: None,
493                website_url: None,
494                business_email: None
495            }
496        };
497
498        let res = sut.locations()
499            .create(input)
500            .await;
501
502        assert!(res.is_ok())
503    }
504
505    #[tokio::test]
506    async fn test_update_location() {
507        use dotenv::dotenv;
508        use std::env;
509
510        dotenv().ok();
511        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
512        let sut = SquareClient::new(&access_token);
513
514        let input = LocationCreationWrapper {
515            location: Location {
516                id: None,
517                name: Some("Updated Test Location".to_string()),
518                business_email: None,
519                address: None,
520                timezone: None,
521                capabilities: None,
522                status: None,
523                created_id: None,
524                coordinates: None,
525                country: None,
526                created_at: None,
527                currency: None,
528                description: None,
529                facebook_url: None,
530                full_format_logo_url: None,
531                logo_url: None,
532                instagram_username: None,
533                language_code: None,
534                mcc: None,
535                merchant_id: None,
536                phone_number: None,
537                pos_background_url: None,
538                tax_ids: None,
539                twitter_username: None,
540                type_name: Some(LocationType::Physical),
541                business_hours: None,
542                business_name: None,
543                website_url: Some("example-website.com".to_string())
544            }
545        };
546
547        let res = sut.locations()
548            .update(input,"LBQ9DAD5WCHB0".to_string())
549            .await;
550
551        assert!(res.is_ok())
552    }
553
554    #[tokio::test]
555    async fn test_retrieve_location() {
556        use dotenv::dotenv;
557        use std::env;
558
559        dotenv().ok();
560        let access_token = env::var("ACCESS_TOKEN").expect("ACCESS_TOKEN to be set");
561        let sut = SquareClient::new(&access_token);
562
563        let res = sut.locations()
564            .retrieve("LBQ9DAD5WCHB0".to_string())
565            .await;
566
567        assert!(res.is_ok())
568    }
569}