use reqwest::header::{AUTHORIZATION, HeaderValue};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use url::Url;
use crate::config::{FyersConfig, SecretString};
use crate::error::{FyersError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RestMethod {
Get,
Post,
Put,
Patch,
Delete,
}
pub(crate) fn join_base_path(base: &Url, path: &str) -> Url {
let mut url = base.clone();
let base_path = base.path().trim_end_matches('/');
let path = path.trim_start_matches('/');
let joined_path = if base_path.is_empty() {
format!("/{path}")
} else if path.is_empty() {
base_path.to_owned()
} else {
format!("{base_path}/{path}")
};
url.set_path(&joined_path);
url
}
pub(crate) fn authorization_header_value(
client_id: &str,
access_token: &SecretString,
) -> Result<HeaderValue> {
HeaderValue::from_str(&format!("{client_id}:{}", access_token.expose_secret()))
.map_err(|err| FyersError::Validation(format!("invalid authorization header value: {err}")))
}
pub(crate) fn required_authorization_header(config: &FyersConfig) -> Result<HeaderValue> {
let access_token = config.access_token().ok_or(FyersError::MissingConfig {
field: "access_token",
})?;
authorization_header_value(config.client_id(), access_token)
}
pub(crate) async fn get_authenticated_json<T>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
) -> Result<T>
where
T: DeserializeOwned,
{
let url = join_base_path(config.api_base_url(), path);
get_authenticated_url_json(http, config, url).await
}
pub(crate) async fn get_authenticated_url_json<T>(
http: &reqwest::Client,
config: &FyersConfig,
url: Url,
) -> Result<T>
where
T: DeserializeOwned,
{
let response = http
.get(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn get_authenticated_base_json<T>(
http: &reqwest::Client,
config: &FyersConfig,
base: &Url,
path: &str,
) -> Result<T>
where
T: DeserializeOwned,
{
let url = join_base_path(base, path);
get_authenticated_url_json(http, config, url).await
}
pub(crate) async fn post_authenticated_json<T, U>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
request: &U,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize + ?Sized,
{
let url = join_base_path(config.api_base_url(), path);
let response = http
.post(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.json(request)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn post_authenticated_base_json<T, U>(
http: &reqwest::Client,
config: &FyersConfig,
base: &Url,
path: &str,
request: &U,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize + ?Sized,
{
let url = join_base_path(base, path);
let response = http
.post(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.json(request)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn put_authenticated_json<T, U>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
request: &U,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize + ?Sized,
{
let url = join_base_path(config.api_base_url(), path);
let response = http
.put(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.json(request)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn patch_authenticated_json<T, U>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
request: &U,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize + ?Sized,
{
let url = join_base_path(config.api_base_url(), path);
let response = http
.patch(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.json(request)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn delete_authenticated_json<T, U>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
request: &U,
) -> Result<T>
where
T: DeserializeOwned,
U: Serialize + ?Sized,
{
let url = join_base_path(config.api_base_url(), path);
let response = http
.delete(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.json(request)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn delete_authenticated_empty_json<T>(
http: &reqwest::Client,
config: &FyersConfig,
path: &str,
) -> Result<T>
where
T: DeserializeOwned,
{
let url = join_base_path(config.api_base_url(), path);
let response = http
.delete(url)
.header(AUTHORIZATION, required_authorization_header(config)?)
.send()
.await?;
decode_json_response(response).await
}
pub(crate) async fn decode_json_response<T>(response: reqwest::Response) -> Result<T>
where
T: DeserializeOwned,
{
let status = response.status();
let retry_after = response
.headers()
.get(reqwest::header::RETRY_AFTER)
.and_then(|value| value.to_str().ok())
.map(|value| value.to_owned().into_boxed_str());
let retry_after_ms = response
.headers()
.get("x-retry-after-ms")
.and_then(|value| value.to_str().ok())
.map(|value| value.to_owned().into_boxed_str());
let body = response.text().await?;
let value = match serde_json::from_str::<Value>(&body) {
Ok(value) => value,
Err(_) if !status.is_success() => {
return Err(FyersError::Broker {
status,
retry_after,
retry_after_ms,
code: None,
s: None,
message: None,
body: body.into_boxed_str(),
});
}
Err(err) => return Err(err.into()),
};
let s = value
.get("s")
.and_then(Value::as_str)
.map(|value| value.to_owned().into_boxed_str());
let code = value.get("code").and_then(Value::as_i64);
let message = value
.get("message")
.and_then(Value::as_str)
.map(|value| value.to_owned().into_boxed_str());
if !status.is_success() || matches!(s.as_deref(), Some("error")) {
return Err(FyersError::Broker {
status,
retry_after,
retry_after_ms,
code,
s,
message,
body: body.into_boxed_str(),
});
}
Ok(serde_json::from_value(value)?)
}