use crate::device_model::{
Config, ProfileConfig, ProfileInfo, SessionInfo, VerseGripDuplicateMode, VersionResponse,
};
use reqwest::{Client, Method, RequestBuilder};
use serde::de::DeserializeOwned;
use serde::Serialize;
use super::types::{ApiResponse, DevicesResponse, ExpertStatusResponse, SessionListData};
pub struct InverseHttpClient {
pub(crate) client: Client,
pub(crate) base_url: String,
pub(crate) verse_grip_mode: VerseGripDuplicateMode,
}
impl InverseHttpClient {
pub fn new(base_url: &str) -> Self {
Self::with_options(base_url, VerseGripDuplicateMode::default())
}
pub fn with_options(base_url: &str, verse_grip_mode: VerseGripDuplicateMode) -> Self {
Self {
client: Client::new(),
base_url: base_url.trim_end_matches('/').to_string(),
verse_grip_mode,
}
}
pub(crate) fn config_url(&self, device_type: &str, id: &str, setting: &str) -> String {
format!(
"{}/{}/{}/config/{}",
self.base_url, device_type, id, setting
)
}
pub(crate) fn device_url(&self, device_type: &str, id: &str, path: &str) -> String {
format!("{}/{}/{}/{}", self.base_url, device_type, id, path)
}
pub(crate) fn append_session(url: &mut String, session: Option<&str>) {
if let Some(s) = session {
url.push_str("?session=");
url.push_str(s);
}
}
pub(crate) async fn request_envelope<T: DeserializeOwned>(
&self,
request: RequestBuilder,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
let resp = request.send().await?;
let status = resp.status();
let envelope: ApiResponse = resp.json().await?;
if envelope.ok {
let data_value = envelope
.data
.ok_or_else(|| format!("ok=true but no data field (HTTP {})", status))?;
let typed: T = serde_json::from_value(data_value)?;
Ok(typed)
} else {
Err(envelope
.error
.unwrap_or_else(|| format!("HTTP {}", status))
.into())
}
}
pub(crate) async fn request_envelope_void(
&self,
request: RequestBuilder,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let resp = request.send().await?;
let status = resp.status();
let envelope: ApiResponse = resp.json().await?;
if envelope.ok {
Ok(())
} else {
Err(envelope
.error
.unwrap_or_else(|| format!("HTTP {}", status))
.into())
}
}
pub(crate) async fn get_device_config<T: DeserializeOwned>(
&self,
device_type: &str,
id: &str,
setting: &str,
session: Option<&str>,
) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
let mut url = self.config_url(device_type, id, setting);
Self::append_session(&mut url, session);
self.request_envelope(self.client.get(&url)).await
}
pub(crate) async fn set_device_config<B: Serialize>(
&self,
method: Method,
device_type: &str,
id: &str,
setting: &str,
session: Option<&str>,
body: &B,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut url = self.config_url(device_type, id, setting);
Self::append_session(&mut url, session);
self.request_envelope_void(self.client.request(method, &url).json(body))
.await
}
pub(crate) async fn delete_device_config(
&self,
device_type: &str,
id: &str,
setting: &str,
session: Option<&str>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut url = self.config_url(device_type, id, setting);
Self::append_session(&mut url, session);
self.request_envelope_void(self.client.delete(&url)).await
}
pub async fn get_version(
&self,
) -> Result<VersionResponse, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/version", self.base_url);
let resp = self
.client
.get(&url)
.send()
.await?
.json::<VersionResponse>()
.await?;
Ok(resp)
}
pub async fn get_expert_status(
&self,
) -> Result<ExpertStatusResponse, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/expert/status", self.base_url);
let resp = self
.client
.get(&url)
.send()
.await?
.json::<ExpertStatusResponse>()
.await?;
Ok(resp)
}
pub async fn get_devices(
&self,
) -> Result<Vec<Config>, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/devices", self.base_url);
let resp = self
.client
.get(&url)
.send()
.await?
.json::<DevicesResponse>()
.await?;
Self::map_devices_response(resp, self.verse_grip_mode)
}
pub async fn get_devices_for_session(
&self,
session: &str,
) -> Result<Vec<Config>, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/devices?session={}", self.base_url, session);
let resp = self
.client
.get(&url)
.send()
.await?
.json::<DevicesResponse>()
.await?;
Self::map_devices_response(resp, self.verse_grip_mode)
}
fn map_devices_response(
mut resp: DevicesResponse,
mode: VerseGripDuplicateMode,
) -> Result<Vec<Config>, Box<dyn std::error::Error + Send + Sync>> {
match mode {
VerseGripDuplicateMode::PreferCustom => {
let custom_ids: Vec<String> = resp.custom_verse_grip.iter()
.map(|d| d.device_id.clone()).collect();
resp.wireless_verse_grip.retain(|d| !custom_ids.contains(&d.device_id));
}
VerseGripDuplicateMode::PreferWireless => {
let wireless_ids: Vec<String> = resp.wireless_verse_grip.iter()
.map(|d| d.device_id.clone()).collect();
resp.custom_verse_grip.retain(|d| !wireless_ids.contains(&d.device_id));
}
VerseGripDuplicateMode::KeepBoth => {}
}
let mut configs: Vec<Config> = Vec::new();
for d in resp.inverse3 {
let mut config = d.config.ok_or("Missing config for Inverse3 device")?;
config.id = d.device_id.clone();
config.device_info.id = d.device_id;
configs.push(Config::DeviceConfig(config));
}
for d in resp.verse_grip {
let mut config = d.config.ok_or("Missing config for Verse Grip device")?;
config.id = d.device_id;
configs.push(Config::VGConfig(config));
}
for d in resp.wireless_verse_grip {
let mut config = d
.config
.ok_or("Missing config for Wireless Verse Grip device")?;
config.id = d.device_id;
configs.push(Config::WVGConfig(config));
}
for d in resp.custom_verse_grip {
let mut config = d
.config
.ok_or("Missing config for Custom Verse Grip device")?;
config.id = d.device_id;
configs.push(Config::WVGConfig(config));
}
Ok(configs)
}
pub async fn get_sessions(
&self,
) -> Result<SessionListData, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/sessions", self.base_url);
self.request_envelope(self.client.get(&url)).await
}
pub async fn get_session(
&self,
id: u64,
) -> Result<SessionInfo, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/sessions/{}", self.base_url, id);
self.request_envelope(self.client.get(&url)).await
}
pub async fn get_session_profile(
&self,
id: u64,
) -> Result<ProfileInfo, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/sessions/{}/profile", self.base_url, id);
self.request_envelope(self.client.get(&url)).await
}
pub async fn set_session_profile(
&self,
id: u64,
profile: &ProfileConfig,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/sessions/{}/profile", self.base_url, id);
self.request_envelope_void(self.client.post(&url).json(profile))
.await
}
pub async fn delete_session_profile(
&self,
id: u64,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/sessions/{}/profile", self.base_url, id);
self.request_envelope_void(self.client.delete(&url)).await
}
pub async fn get_settings(
&self,
) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/settings", self.base_url);
self.request_envelope(self.client.get(&url)).await
}
pub async fn set_settings(
&self,
settings: &serde_json::Value,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/settings", self.base_url);
self.request_envelope_void(self.client.post(&url).json(settings))
.await
}
pub async fn get_setting(
&self,
key: &str,
) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/settings/{}", self.base_url, key);
let resp = self
.client
.get(&url)
.send()
.await?
.json::<serde_json::Value>()
.await?;
Ok(resp)
}
pub async fn set_setting(
&self,
key: &str,
value: &serde_json::Value,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/settings/{}", self.base_url, key);
self.request_envelope_void(self.client.post(&url).json(value))
.await
}
pub async fn delete_setting(
&self,
key: &str,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let url = format!("{}/settings/{}", self.base_url, key);
self.request_envelope_void(self.client.delete(&url)).await
}
}