1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#![doc = include_str!("../LIBDOC.md")]

/// Helper macro to general API URL at compile time
#[macro_export]
macro_rules! api_url {
    ($e: expr) => {
        concat!("http://datamall2.mytransport.sg/ltaodataservice", $e)
    };
}

pub use crate::r#async::prelude::*;
pub use crate::r#async::LTAClient;
pub use lta_models as models;
use reqwest::StatusCode;
use thiserror::Error;

/// Imports for important structs
pub mod prelude {
    pub use crate::{Bus, Crowd, Facility, Geo, Taxi, Traffic, Train};
}

use crate::models::crowd::passenger_vol::VolType;
pub use reqwest;

/// Internal Async module
pub mod r#async;

/// Internal Blocking module
#[cfg(feature = "blocking")]
pub mod blocking;

/// Type alias for `Result<T, LTAError>`
pub type LTAResult<T> = Result<T, LTAError>;

/// LTAError type, all request using lta-rs returns `Result<T, LTAError>`
#[derive(Error, Debug)]
pub enum LTAError {
    /// Internal error within the client backend, open a PR if this happens
    #[error("Internal error within the client backend, open a PR if this happens!")]
    BackendError(#[from] reqwest::Error),
    
    /// API key is most likely empty
    #[error("Invalid API Key!")]
    InvalidAPIKey,

    /// You have reached the server limit, try again later
    #[error("Server rate limit reached!")]
    RateLimitReached,

    /// Response body can't be parsed to a valid enum
    #[error("Unknown enum variant!")]
    UnknownEnumVariant,

    /// Make sure that your API key is correct and valid
    #[error("HTTP Header Unauthorized")]
    Unauthorized,

    /// HTTP NOTFOUND
    #[error("HTTP Header NotFound")]
    NotFound,

    /// Failed to parse body of response, probably malformed
    #[error("Failed to parse body of response, probably malformed")]
    FailedToParseBody,

    /// Undocumented status code, open an issue if this happens
    #[error("Undocumented status code, open an issue if this happens")]
    UnhandledStatusCode(StatusCode, String),

    /// Custom
    #[error("Custom error: `{0}`")]
    Custom(String),
}

/// A `Client` to make requests with
/// The `Client` holds a connection pool internally, so it is advised that you create one and reuse it
pub trait Client: Sized {
    /// Any backend Client
    type InternalClient;

    /// Any type that can build requests
    type RB;

    /// General constructor for `Self`
    fn new(api_key: impl Into<String>, client: Self::InternalClient) -> Self;

    /// This method not assign the `api_key` in struct if the provided key is empty or whitespaces
    /// Instead, assign `None`
    fn with_api_key(api_key: impl Into<String>) -> LTAResult<Self>;

    /// Returns `Self::RB`
    fn req_builder(&self, url: &str) -> Self::RB;
}

/// Bus type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Bus;

/// Crowd type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Crowd;

/// Taxi type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Taxi;

/// Traffic type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Traffic;

/// Train type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Train;

/// Geo type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Geo;

/// Facility type that implements APIs. Can be either blocking or async
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct Facility;

/// util to map enum to url
pub(crate) const fn vol_type_to_url(vol_type: VolType) -> LTAResult<&'static str> {
    use crate::models::crowd::passenger_vol;

    let url = match vol_type {
        VolType::BusStops => passenger_vol::URL_BY_BUS_STOPS,
        VolType::OdBusStop => passenger_vol::URL_BY_OD_BUS_STOPS,
        VolType::Train => passenger_vol::URL_BY_TRAIN,
        VolType::OdTrain => passenger_vol::URL_BY_OD_TRAIN,
        _ => return Err(LTAError::UnknownEnumVariant),
    };

    Ok(url)
}