eskom_se_push_api/
ureq_client.rs

1//! A blocking client using the `ureq` http client.
2//!
3//! # Optional
4//! Requires the `ureq` feature to be enabled
5
6use serde::de::DeserializeOwned;
7
8use crate::{
9  allowance::{AllowanceCheck, AllowanceCheckURL},
10  area_info::{AreaInfo, AreaInfoURLBuilder},
11  area_nearby::{AreaNearby, AreasNearbyURLBuilder},
12  area_search::{AreaSearch, AreaSearchURLBuilder},
13  errors::{APIError, HttpError},
14  get_token_from_env,
15  status::{EskomStatus, EskomStatusUrl},
16  topics_nearby::{TopicsNearby, TopicsNearbyUrlBuilder},
17  Endpoint,
18};
19
20pub struct UreqClient {
21  token: String,
22}
23
24impl UreqClient {
25  /// Create new client using the `ureq` Http client
26  /// `token` is the Eskom API token
27  pub fn new(token: String) -> Self {
28    UreqClient { token }
29  }
30
31  /// Creates new instance of Eskom API using token as a env variable.
32  /// Uses the [dotenv](https://crates.io/crates/dotenv) crate so it will load .env files if available.
33  /// `Note`: The default variable name is `ESKOMSEPUSH_API_KEY` if var_name is set to `None`.
34  /// `Note`: It will panic the env variable doesn't exist.
35  pub fn new_with_env(var_name: Option<&str>) -> Self {
36    match get_token_from_env(var_name) {
37      Ok(val) => UreqClient { token: val },
38      Err(e) => panic!("Error: {}", e),
39    }
40  }
41
42  /// The current and next loadshedding statuses for South Africa and (Optional) municipal overrides
43  /// `eskom` is the National status
44  /// Other keys in the `status` refer to different municipalities and potential overrides from the National status; most typically present is the key for `capetown`
45  pub fn get_load_shedding_status(&self) -> Result<EskomStatus, HttpError> {
46    let c = EskomStatusUrl::default();
47    c.ureq(&self.token)
48  }
49
50  /// Obtain the `area_id` from Area Find or Area Search and use with this request. This single request has everything you need to monitor upcoming loadshedding events for the chosen suburb.
51  pub fn get_area_info(&self, area_id: &str) -> Result<AreaInfo, HttpError> {
52    let t = AreaInfoURLBuilder::default()
53      .area_id(area_id.to_owned())
54      .build()
55      .map_err(|_| HttpError::AreaIdNotSet)?;
56    t.ureq(&self.token)
57  }
58
59  /// Find areas based on GPS coordinates (latitude and longitude).
60  /// The first area returned is typically the best choice for the coordinates - as it's closest to the GPS coordinates provided. However it could be that you are in the second or third area.
61  pub fn areas_nearby(&self, lat: f32, long: f32) -> Result<AreaNearby, HttpError> {
62    let t = AreasNearbyURLBuilder::default()
63      .latitude(lat)
64      .longitude(long)
65      .build()
66      .map_err(|_| HttpError::LongitudeOrLatitudeNotSet {
67        longitude: lat,
68        latitude: long,
69      })?;
70    t.ureq(&self.token)
71  }
72
73  /// Search area based on text
74  pub fn areas_search(&self, search_term: &str) -> Result<AreaSearch, HttpError> {
75    let t = AreaSearchURLBuilder::default()
76      .search_term(search_term)
77      .build()
78      .map_err(|_| HttpError::SearchTextNotSet)?;
79    t.ureq(&self.token)
80  }
81
82  /// Find topics created by users based on GPS coordinates (latitude and longitude). Can use this to detect if there is a potential outage/problem nearby
83  pub fn topics_nearby(&self, lat: f32, long: f32) -> Result<TopicsNearby, HttpError> {
84    let t = TopicsNearbyUrlBuilder::default()
85      .latitude(lat)
86      .longitude(long)
87      .build()
88      .map_err(|_| HttpError::LongitudeOrLatitudeNotSet {
89        longitude: lat,
90        latitude: long,
91      })?;
92    t.ureq(&self.token)
93  }
94
95  /// Check allowance allocated for token
96  /// `NOTE`: This call doesn't count towards your quota.
97  pub fn check_allowance(&self) -> Result<AllowanceCheck, HttpError> {
98    let t = AllowanceCheckURL::default();
99    t.ureq(&self.token)
100  }
101}
102
103/// A response handler for `ureq` to map the response to the given structure or relevant error
104/// ```rust
105/// let statusUrl = EskomStatusUrlBuilder::default().build().unwrap();
106///
107/// let api_response  = ureq::get(statusUrl.url().unwrap().as_str()).set(TOKEN_KEY, "YOUR-TOKEN").call();
108/// let response = handle_ureq_response::<EskomStatus>(api_response);
109/// ```
110/// `response` is the ureq API response
111/// NOTE
112/// Requires the `ureq` feature to be enabled
113pub fn handle_ureq_response<T: DeserializeOwned>(
114  response: Result<ureq::Response, ureq::Error>,
115) -> Result<T, HttpError> {
116  match response {
117    Ok(resp) => resp
118      .into_json::<T>()
119      .map_err(|e| HttpError::UnknownError(e.to_string())),
120    Err(ureq::Error::Status(code, response)) => match code {
121      400 => Err(HttpError::APIError(APIError::BadRequest)),
122      403 => Err(HttpError::APIError(APIError::Forbidden)),
123      404 => Err(HttpError::APIError(APIError::NotFound)),
124      429 => Err(HttpError::APIError(APIError::TooManyRequests)),
125      500..=509 => Err(HttpError::APIError(APIError::ServerError(
126        response.into_string().unwrap(),
127      ))),
128      _a => Err(HttpError::Unknown),
129    },
130    Err(_) => Err(HttpError::NoInternet),
131  }
132}