refactor: request tweaks
This commit is contained in:
parent
9fe18ab1b0
commit
5c018e9f8d
|
@ -3,10 +3,10 @@ pub mod powerwall;
|
||||||
pub mod vehicle;
|
pub mod vehicle;
|
||||||
|
|
||||||
use crate::error::TeslatteError;
|
use crate::error::TeslatteError;
|
||||||
use crate::Data;
|
use crate::ResponseData;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
pub fn print_json<T>(result: Result<Data<T>, TeslatteError>) {
|
pub fn print_json<T>(result: Result<ResponseData<T>, TeslatteError>) {
|
||||||
match result {
|
match result {
|
||||||
Ok(data) => print_json_data(data),
|
Ok(data) => print_json_data(data),
|
||||||
Err(TeslatteError::ServerError { ref body, .. }) if body.is_some() => {
|
Err(TeslatteError::ServerError { ref body, .. }) if body.is_some() => {
|
||||||
|
@ -18,7 +18,7 @@ pub fn print_json<T>(result: Result<Data<T>, TeslatteError>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn print_json_data<T>(data: Data<T>) {
|
pub fn print_json_data<T>(data: ResponseData<T>) {
|
||||||
// TODO: pretty print cli option
|
// TODO: pretty print cli option
|
||||||
print_json_str(data.body());
|
print_json_str(data.body());
|
||||||
}
|
}
|
||||||
|
|
100
src/lib.rs
100
src/lib.rs
|
@ -69,62 +69,57 @@ impl Api {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn get<D>(&self, url: &str) -> Result<Data<D>, TeslatteError>
|
async fn get<D>(&self, url: &str) -> Result<ResponseData<D>, TeslatteError>
|
||||||
where
|
where
|
||||||
D: for<'de> Deserialize<'de> + Debug,
|
D: for<'de> Deserialize<'de> + Debug,
|
||||||
{
|
{
|
||||||
let request_context = || format!("GET {url}");
|
self.request(&RequestData::GET { url }).await
|
||||||
|
|
||||||
let body = self.request(RequestData::GET { url }).await?;
|
|
||||||
|
|
||||||
trace!(?body);
|
|
||||||
|
|
||||||
let data = Self::parse_json::<D, _>(&body, request_context)?;
|
|
||||||
trace!(?data);
|
|
||||||
|
|
||||||
Ok(Data { data, body })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
async fn post<S>(&self, url: &str, body: S) -> Result<Data<PostResponse>, TeslatteError>
|
async fn post<S>(&self, url: &str, body: S) -> Result<ResponseData<PostResponse>, TeslatteError>
|
||||||
where
|
where
|
||||||
S: Serialize + Debug,
|
S: Serialize + Debug,
|
||||||
{
|
{
|
||||||
let request_context = || format!("POST {url}");
|
// let request_context = || format!("POST {url} {payload}");
|
||||||
|
|
||||||
let payload =
|
let payload =
|
||||||
&serde_json::to_string(&body).expect("Should not fail creating the request struct.");
|
&serde_json::to_string(&body).expect("Should not fail creating the request struct.");
|
||||||
|
|
||||||
let body = self.request(RequestData::POST { url, payload }).await?;
|
let request_data = RequestData::POST { url, payload };
|
||||||
|
let data = self.request::<PostResponse>(&request_data).await?;
|
||||||
|
|
||||||
let data = Self::parse_json::<PostResponse, _>(&body, request_context)?;
|
if data.data.result {
|
||||||
trace!(?data);
|
Ok(data)
|
||||||
|
|
||||||
if data.result {
|
|
||||||
Ok(Data { data, body })
|
|
||||||
} else {
|
} else {
|
||||||
Err(TeslatteError::ServerError {
|
Err(TeslatteError::ServerError {
|
||||||
request: request_context(),
|
request: format!("{request_data}"),
|
||||||
msg: data.reason,
|
msg: data.data.reason,
|
||||||
description: None,
|
description: None,
|
||||||
body: Some(body),
|
body: Some(data.body),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request(&self, request_data: RequestData<'_>) -> Result<String, TeslatteError> {
|
async fn request<T>(
|
||||||
|
&self,
|
||||||
|
request_data: &RequestData<'_>,
|
||||||
|
) -> Result<ResponseData<T>, TeslatteError>
|
||||||
|
where
|
||||||
|
T: for<'de> Deserialize<'de> + Debug,
|
||||||
|
{
|
||||||
trace!("{request_data}");
|
trace!("{request_data}");
|
||||||
|
|
||||||
let request_builder = match request_data {
|
let request_builder = match request_data {
|
||||||
RequestData::GET { url } => self.client.get(url),
|
RequestData::GET { url } => self.client.get(*url),
|
||||||
RequestData::POST { url, payload } => self
|
RequestData::POST { url, payload } => self
|
||||||
.client
|
.client
|
||||||
.post(url)
|
.post(*url)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(payload.to_string()),
|
.body(payload.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
request_builder
|
let response_body = request_builder
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.header("Authorization", format!("Bearer {}", self.access_token.0))
|
.header("Authorization", format!("Bearer {}", self.access_token.0))
|
||||||
.send()
|
.send()
|
||||||
|
@ -138,31 +133,36 @@ impl Api {
|
||||||
.map_err(|source| TeslatteError::FetchError {
|
.map_err(|source| TeslatteError::FetchError {
|
||||||
source,
|
source,
|
||||||
request: format!("{request_data}"),
|
request: format!("{request_data}"),
|
||||||
})
|
})?;
|
||||||
|
|
||||||
|
Self::parse_json(request_data, response_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The `request` argument is for additional context in the error.
|
fn parse_json<T>(
|
||||||
fn parse_json<T, F>(body: &str, request: F) -> Result<T, TeslatteError>
|
request_data: &RequestData,
|
||||||
|
response_body: String,
|
||||||
|
) -> Result<ResponseData<T>, TeslatteError>
|
||||||
where
|
where
|
||||||
T: for<'de> Deserialize<'de> + Debug,
|
T: for<'de> Deserialize<'de> + Debug,
|
||||||
F: FnOnce() -> String + Copy,
|
|
||||||
{
|
{
|
||||||
trace!("{}", &body);
|
let response: Response<T> = serde_json::from_str::<ResponseDeserializer<T>>(&response_body)
|
||||||
let r: Response<T> = serde_json::from_str::<ResponseDeserializer<T>>(body)
|
|
||||||
.map_err(|source| TeslatteError::DecodeJsonError {
|
.map_err(|source| TeslatteError::DecodeJsonError {
|
||||||
source,
|
source,
|
||||||
request: request(),
|
request: format!("{request_data}"),
|
||||||
body: body.to_string(),
|
body: response_body.to_string(),
|
||||||
})?
|
})?
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
match r {
|
match response {
|
||||||
Response::Response(r) => Ok(r),
|
Response::Response(data) => Ok(ResponseData {
|
||||||
|
data,
|
||||||
|
body: response_body,
|
||||||
|
}),
|
||||||
Response::Error(e) => Err(TeslatteError::ServerError {
|
Response::Error(e) => Err(TeslatteError::ServerError {
|
||||||
request: request(),
|
request: format!("{request_data}"),
|
||||||
msg: e.error,
|
msg: e.error,
|
||||||
description: e.error_description,
|
description: e.error_description,
|
||||||
body: Some(body.to_owned()),
|
body: Some(response_body.to_owned()),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -212,12 +212,12 @@ struct Empty {}
|
||||||
///
|
///
|
||||||
/// This struct will automatically deref to the data type for better ergonomics.
|
/// This struct will automatically deref to the data type for better ergonomics.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Data<T> {
|
pub struct ResponseData<T> {
|
||||||
data: T,
|
data: T,
|
||||||
body: String,
|
body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Data<T> {
|
impl<T> ResponseData<T> {
|
||||||
pub fn data(&self) -> &T {
|
pub fn data(&self) -> &T {
|
||||||
&self.data
|
&self.data
|
||||||
}
|
}
|
||||||
|
@ -227,7 +227,7 @@ impl<T> Data<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> std::ops::Deref for Data<T> {
|
impl<T> std::ops::Deref for ResponseData<T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
|
@ -240,7 +240,7 @@ macro_rules! get {
|
||||||
($name:ident, $return_type:ty, $url:expr) => {
|
($name:ident, $return_type:ty, $url:expr) => {
|
||||||
pub async fn $name(
|
pub async fn $name(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<crate::Data<$return_type>, crate::error::TeslatteError> {
|
) -> Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
||||||
let url = format!("{}{}", crate::API_URL, $url);
|
let url = format!("{}{}", crate::API_URL, $url);
|
||||||
self.get(&url).await
|
self.get(&url).await
|
||||||
}
|
}
|
||||||
|
@ -256,7 +256,7 @@ macro_rules! get_arg {
|
||||||
pub async fn $name(
|
pub async fn $name(
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
) -> miette::Result<crate::Data<$return_type>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.get(&url).await
|
self.get(&url).await
|
||||||
|
@ -271,7 +271,7 @@ macro_rules! get_args {
|
||||||
pub async fn $name(
|
pub async fn $name(
|
||||||
&self,
|
&self,
|
||||||
values: &$args,
|
values: &$args,
|
||||||
) -> miette::Result<crate::Data<$return_type>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::ResponseData<$return_type>, crate::error::TeslatteError> {
|
||||||
let url = values.format($url);
|
let url = values.format($url);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.get(&url).await
|
self.get(&url).await
|
||||||
|
@ -287,7 +287,7 @@ macro_rules! post_arg {
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
data: &$request_type,
|
data: &$request_type,
|
||||||
) -> miette::Result<crate::Data<crate::PostResponse>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::ResponseData<crate::PostResponse>, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.post(&url, data).await
|
self.post(&url, data).await
|
||||||
|
@ -302,7 +302,7 @@ macro_rules! post_arg_empty {
|
||||||
pub async fn $name(
|
pub async fn $name(
|
||||||
&self,
|
&self,
|
||||||
arg: &$arg_type,
|
arg: &$arg_type,
|
||||||
) -> miette::Result<crate::Data<crate::PostResponse>, crate::error::TeslatteError> {
|
) -> miette::Result<crate::ResponseData<crate::PostResponse>, crate::error::TeslatteError> {
|
||||||
let url = format!($url, arg);
|
let url = format!($url, arg);
|
||||||
let url = format!("{}{}", crate::API_URL, url);
|
let url = format!("{}{}", crate::API_URL, url);
|
||||||
self.post(&url, &Empty {}).await
|
self.post(&url, &Empty {}).await
|
||||||
|
@ -338,7 +338,13 @@ mod tests {
|
||||||
"response": null,
|
"response": null,
|
||||||
"error":{"error": "timeout","error_description": "s"}
|
"error":{"error": "timeout","error_description": "s"}
|
||||||
}"#;
|
}"#;
|
||||||
let e = Api::parse_json::<ChargeState, _>(s, || "req".to_string());
|
|
||||||
|
let request_data = RequestData::POST {
|
||||||
|
url: "https://example.com",
|
||||||
|
payload: "doesn't matter",
|
||||||
|
};
|
||||||
|
|
||||||
|
let e = Api::parse_json::<ChargeState>(&request_data, s.to_string());
|
||||||
if let Err(e) = e {
|
if let Err(e) = e {
|
||||||
if let TeslatteError::ServerError {
|
if let TeslatteError::ServerError {
|
||||||
msg, description, ..
|
msg, description, ..
|
||||||
|
|
|
@ -321,6 +321,7 @@ pub struct SetChargeLimit {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::RequestData;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn json() {
|
fn json() {
|
||||||
|
@ -386,6 +387,10 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
Api::parse_json::<ChargeState, _>(s, || "req".to_string()).unwrap();
|
|
||||||
|
let request_data = RequestData::GET {
|
||||||
|
url: "https://owner-api.teslamotors.com/api/1/vehicles/1234567890/data_request/charge_state",
|
||||||
|
};
|
||||||
|
Api::parse_json::<ChargeState>(&request_data, s.to_string()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue