amber_client/
rest_client.rs

1use anyhow::Result;
2use iso8601_timestamp::Timestamp;
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5use thiserror::Error;
6
7/// Struct type that matches the resulting data from the Amber "/sites" REST endpoint.
8#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
9#[serde(rename_all = "camelCase")]
10pub struct SiteDetails {
11    pub active_from: Timestamp,
12    pub channels: Vec<SiteChannels>,
13    pub id: String,
14    pub network: String,
15    pub nmi: String,
16    pub status: String,
17}
18/// Struct type that matches resulting data from "channels" section of the Amber "/sites" endpoint.
19#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
20pub struct SiteChannels {
21    pub identifier: String,
22    pub tariff: String,
23    // type is a reserved word, so rename it.
24    #[serde(rename = "type")]
25    pub tariff_type: String,
26}
27
28/// Struct type that matches the resulting data from the Amber "/prices" REST endpoint.
29#[derive(Serialize, Deserialize, Debug, Clone)]
30#[serde(rename_all = "camelCase")]
31pub struct PriceData {
32    // type is a reserved word, so rename it.
33    #[serde(rename = "type")]
34    pub interval_type: String,
35    pub date: Timestamp,
36    pub duration: u8,
37    pub start_time: Timestamp,
38    pub end_time: Timestamp,
39    pub nem_time: Timestamp,
40    pub per_kwh: f32,
41    pub renewables: f32,
42    pub spot_per_kwh: f32,
43    pub channel_type: String,
44    pub spike_status: String,
45    pub tariff_information: TariffInformation,
46    pub descriptor: String,
47    pub estimate: Option<bool>,
48}
49
50/// Struct type that matches the resulting data from the Amber "/usage" REST endpoint.
51#[derive(Serialize, Deserialize, Debug, Clone)]
52#[serde(rename_all = "camelCase")]
53pub struct UsageData {
54    #[serde(rename = "type")]
55    pub price_type: String,
56    pub duration: u8,
57    pub date: Timestamp,
58    pub end_time: Timestamp,
59    pub quality: String,
60    pub kwh: f32,
61    pub nem_time: Timestamp,
62    pub per_kwh: f32,
63    pub channel_type: String,
64    pub channel_identifier: String,
65    pub cost: f32,
66    pub renewables: f32,
67    pub spot_per_kwh: f32,
68    pub start_time: Timestamp,
69    pub spike_status: String,
70    pub tariff_information: TariffInformation,
71    pub descriptor: String,
72}
73
74/// Struct type that matches the "taiff_information" from the "/prices" and "/usage" endpoint.
75#[derive(Serialize, Deserialize, Debug, Clone)]
76pub struct TariffInformation {
77    pub period: String,
78}
79
80/// Struct type that matches the resulting data from the Amber "/renewables" REST endpoint.
81#[derive(Serialize, Deserialize, Debug, Clone)]
82#[serde(rename_all = "camelCase")]
83pub struct RenewablesData {
84    #[serde(rename = "type")]
85    pub price_type: String,
86    pub duration: u8,
87    pub date: Timestamp,
88    pub start_time: Timestamp,
89    pub end_time: Timestamp,
90    pub renewables: f32,
91    pub descriptor: String,
92}
93
94/// Struct type that provides options for our implementation of a reqwest client.
95#[derive(Clone)]
96pub struct RestClient {
97    pub url: String,
98    pub auth_token: String,
99    pub client: reqwest::Client,
100}
101
102/// Enum type to descibe and handle the error types enounced when using the RestClient.
103#[derive(Error, Debug)]
104pub enum Error {
105    #[error("HTTP Request failed: {0}")]
106    ReqwestError(#[from] reqwest::Error),
107
108    #[error("Serde failed to decode json: {0}")]
109    SerdeJsonError(#[from] serde_json::Error),
110
111    #[error("Received a non 200 status code of {status_code:?} with message body: {body:?} ")]
112    HttpNon200Status { status_code: String, body: String },
113}
114
115/// Implementation of our client to interact with the Amber REST API endpoints.
116impl RestClient {
117    pub fn new_client(url: String, auth_token: String) -> Self {
118        Self {
119            url,
120            auth_token,
121            client: Client::new(),
122        }
123    }
124
125    /// RestClient function to request data from the Amber "/sites" endpoint.
126    pub async fn get_site_data(&mut self) -> Result<Vec<SiteDetails>, Error> {
127        let auth_token_header = format!("Bearer {}", &self.auth_token);
128
129        let response = self
130            .client
131            .get(&self.url)
132            .header("AUTHORIZATION", auth_token_header)
133            .header("CONTENT_TYPE", "application/json")
134            .header("ACCEPT", "application/json")
135            .send()
136            .await?;
137        match response.status() {
138            reqwest::StatusCode::OK => {
139                let response = response.json::<Vec<SiteDetails>>().await?;
140                Ok(response)
141            }
142            _ => Err(Error::HttpNon200Status {
143                status_code: (response.status().to_string()),
144                body: (response.text().await)?,
145            }),
146        }
147    }
148
149    /// RestClient function to request data from the Amber "/prices" endpoint.
150    pub async fn get_price_data(&mut self) -> Result<Vec<PriceData>> {
151        let auth_token_header = format!("Bearer {}", &self.auth_token);
152
153        let response = self
154            .client
155            .get(&self.url)
156            .header("AUTHORIZATION", auth_token_header)
157            .header("CONTENT_TYPE", "application/json")
158            .header("ACCEPT", "application/json")
159            .send()
160            .await?
161            .json::<Vec<PriceData>>()
162            .await?;
163
164        Ok(response)
165    }
166
167    /// RestClient function to request data from the Amber "/usage" endpoint.
168    pub async fn get_usage_data(&mut self) -> Result<Vec<UsageData>> {
169        let auth_token_header = format!("Bearer {}", &self.auth_token);
170
171        let response = self
172            .client
173            .get(&self.url)
174            .header("AUTHORIZATION", auth_token_header)
175            .header("CONTENT_TYPE", "application/json")
176            .header("ACCEPT", "application/json")
177            .send()
178            .await?
179            .json::<Vec<UsageData>>()
180            .await?;
181
182        Ok(response)
183    }
184
185    /// RustClient function to request data from the Amber "/renewables" endpoint.
186    pub async fn get_renewables_data(&self) -> Result<Vec<RenewablesData>> {
187        let auth_token_header = format!("Bearer {}", &self.auth_token);
188
189        let response = self
190            .client
191            .get(&self.url)
192            .header("AUTHORIZATION", auth_token_header)
193            .header("CONTENT_TYPE", "application/json")
194            .header("ACCEPT", "application/json")
195            .send()
196            .await?
197            .json::<Vec<RenewablesData>>()
198            .await?;
199
200        Ok(response)
201    }
202}