#[derive(Debug)] pub enum Error { // FIXME: can't even return url's ParseError directly......... UrlParseError(String), ReqwestError(reqwest::Error), SubsonicError(SubsonicError), } impl From for Error { fn from(err: reqwest::Error) -> Self { Self::ReqwestError(err) } } #[derive(Debug, serde::Deserialize)] pub struct SubsonicError { pub code: u32, pub message: String, } #[derive(Debug, serde::Deserialize)] pub struct SubsonicResponse { #[serde(rename = "subsonic-response")] inner: SubsonicResponseInner, } #[derive(Debug, serde::Deserialize)] #[serde(tag = "status")] pub enum SubsonicResponseInner { #[serde(rename = "ok")] Ok(T), #[serde(rename = "failed")] Failed { error: SubsonicError }, } impl SubsonicResponseInner { fn fixup(self) -> Result { match self { Self::Ok(t) => Ok(t), Self::Failed { error } => Err(Error::SubsonicError(error)), } } } pub struct Client { client: reqwest::Client, base_url: reqwest::Url, } impl Client { pub fn new(url: &str, username: &str, token: &str, salt: &str) -> Result { let base_url = reqwest::Url::parse_with_params( url, &[ ("u", username), ("t", token), ("s", salt), ("v", "1.16.1"), ("c", "eu.callcc.audrey"), ("f", "json"), ], ) .map_err(|err| Error::UrlParseError(err.to_string()))?; if base_url.scheme() != "http" && base_url.scheme() != "https" { return Err(Error::UrlParseError("Url scheme is not HTTP(s)".into())); } Ok(Client { client: reqwest::Client::builder() .user_agent("audrey/linux") // Audrey.Const.user_agent .build()?, base_url, }) } pub async fn ping(&self) -> Result<(), Error> { let mut url = self.base_url.clone(); url.path_segments_mut() // literally can't fail .unwrap_or_else(|_| unsafe { std::hint::unreachable_unchecked() }) .extend(&["rest", "ping"]); self.client .get(url) .send() .await? .error_for_status()? .json::>() .await? .inner .fixup() } }