transdirect/
booking.rs

1use std::collections::HashMap;
2
3use restson::{RestPath, Error as RestsonError};
4use num_traits::{Float,Unsigned};
5use serde_derive::{Serialize, Deserialize};
6use serde::{de, ser};
7
8use crate::product::{Product,Service};
9use crate::account::Account;
10
11/// Enum describing the status of a booking
12/// 
13/// As defined by the [specification](https://transdirectapiv4.docs.apiary.io/reference/bookings-/-simple-quotes/single-booking)
14#[non_exhaustive]
15#[derive(Debug, Eq, PartialEq, Default)]
16pub enum BookingStatus {
17    #[default]
18    New,
19    PendingPayment,
20    Paid,
21    RequestSent,
22    Reviewed,
23    Confirmed,
24    Cancelled,
25    PendingReview,
26    RequestFailed,
27    BookedManually,
28}
29
30impl<'de> de::Deserialize<'de> for BookingStatus {
31    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
32    where D: de::Deserializer<'de>
33    {
34        let variant = String::deserialize(deserializer)?;
35        match variant.as_str() {
36            "new"             => Ok(Self::New),
37            "pending_payment" => Ok(Self::PendingPayment),
38            "paid"            => Ok(Self::Paid),
39            "request_sent"    => Ok(Self::RequestSent),
40            "reviewed"        => Ok(Self::Reviewed),
41            "confirmed"       => Ok(Self::Confirmed),
42            "cancelled"       => Ok(Self::Cancelled),
43            "pending_review"  => Ok(Self::PendingReview),
44            "request_failed"  => Ok(Self::RequestFailed),
45            "booked_manually" => Ok(Self::BookedManually),
46            _   => Err(de::Error::custom("Unrecognised enum value"))
47        }
48    }
49}
50
51/// Represents a single booking request (quote or order)
52/// 
53/// 
54#[derive(Debug, Serialize, Default)]
55pub struct BookingRequest<'a, T, U>
56where T: Unsigned + ser::Serialize, U: Float + ser::Serialize {
57    pub declared_value: U,
58    pub referrer: String,
59    pub requesting_site: String,
60    pub tailgate_pickup: bool,
61    pub tailgate_delivery: bool,
62    pub items: Vec<Product<T, U>>, // Products may be in a higher scope
63    pub sender: Option<&'a Account>,
64    pub receiver: Option<&'a Account>,
65}
66
67impl<'a, T, U> BookingRequest<'a, T, U>
68where T: Unsigned + ser::Serialize + Default, U: Float + ser::Serialize + Default {
69    /// Creates an empty `BookingRequest`
70    /// 
71    /// Each element will be either empty, 0, or false.
72    /// This provides sensible and convenient defaults for `tailgate_pickup`,
73    /// declared_value, etc.
74    /// 
75    /// # Examples
76    /// 
77    /// ```
78    /// use transdirect::{BookingRequest, Product};
79    ///
80    /// # use transdirect::Account;
81    /// # let person = Account::default();
82    /// 
83    /// let products = vec![Product::new()];
84    /// let breq = BookingRequest {
85    ///     declared_value: 55.0,
86    ///     items: products,
87    ///     sender: Some(&person),
88    ///     receiver: Some(&person),
89    ///     ..BookingRequest::default()
90    /// };
91    /// ```
92    pub fn new() -> Self {
93        Default::default()
94    }
95}
96
97impl<T, U> RestPath<()> for BookingRequest<'_, T, U>
98where T: Unsigned + ser::Serialize, U: Float + ser::Serialize {
99    fn get_path(_: ()) -> Result<String, RestsonError> { Ok("bookings/v4".to_string()) }
100}
101
102// I don't know how to implement generically without running into collisions
103impl<T, U> RestPath<u32> for BookingResponse<T, U>
104where T: Unsigned, U: Float {
105    fn get_path(params: u32) -> Result<String, RestsonError> {
106        Ok(format!("bookings/v4/{params}"))
107    }
108}
109
110/// Represents a response due to a booking request from the server
111/// 
112///
113#[derive(Debug, Deserialize)]
114pub struct BookingResponse<T, U>
115where T: Unsigned, U: Float {
116    pub id: u32,
117    pub status: BookingStatus,
118    #[serde(with = "time::serde::iso8601")]
119    pub booked_at: time::OffsetDateTime,
120    pub booked_by: String, // Expected to be "sender"
121    #[serde(with = "time::serde::iso8601")]
122    pub created_at: time::OffsetDateTime,
123    #[serde(with = "time::serde::iso8601")]
124    pub updated_at: time::OffsetDateTime,
125    pub declared_value: U,
126    pub insured_value: U,
127    pub description: Option<String>,
128    pub items: Vec<Product<T, U>>,
129    pub label: String,
130    pub notifications: HashMap<String, bool>,
131    pub quotes: HashMap<String, Service<U>>,
132    pub sender: Account,
133    pub receiver: Account,
134    pub pickup_window: Vec<String>, // Could be a time::OffsetDateTime
135    pub connote: Option<String>, // With the mock server, this is null => None
136    pub charged_weight: T,
137    pub scanned_weight: T,
138    pub special_instructions: String,
139    pub tailgate_delivery: bool,
140}