pub use kittybox_util::micropub::{Config, Error as MicropubError, QueryType}; use soup::prelude::*; #[derive(Debug)] pub struct Client { pub me: String, micropub: String, pub access_token: String, http: soup::Session, } #[derive(Debug, thiserror::Error)] pub enum Error { #[error("glib error: {0}")] Glib(#[from] glib::Error), #[error("json serialization error: {0}")] Json(#[from] serde_json::Error), #[error("micropub error: {0}")] Micropub(#[from] MicropubError), #[error("micropub server did not return a location: header")] NoLocationHeader, } impl Client { pub fn new(http: soup::Session, uri: glib::Uri, token: String, me: String) -> Self { Self { micropub: uri.to_string(), access_token: token, me, http, } } pub async fn config(&self) -> Result { let uri = glib::Uri::parse(&self.micropub, glib::UriFlags::NONE).unwrap(); let uri = super::util::append_query(&uri, [("q".to_string(), "config".to_string())]); let exch = soup::Message::from_uri("GET", &uri); let headers = exch.request_headers().expect("SoupMessage with no headers"); // TODO: create a SoupAuth subclass that allows pasting in a token headers.append("Authorization", &format!("Bearer {}", self.access_token)); let body = self .http .send_and_read_future(&exch, glib::Priority::DEFAULT) .await?; if exch.status() == soup::Status::Unauthorized { return Err( MicropubError::from(kittybox_util::micropub::ErrorKind::NotAuthorized).into(), ); } Ok(serde_json::from_slice(&body)?) } pub async fn send_post(&self, post: microformats::types::Item) -> Result { let uri = glib::Uri::parse(&self.micropub, glib::UriFlags::NONE).unwrap(); let exch = soup::Message::from_uri("POST", &uri); let headers = exch.request_headers().expect("SoupMessage with no headers"); headers.append("Authorization", &format!("Bearer {}", self.access_token)); exch.set_request_body_from_bytes( Some("application/json"), Some(&glib::Bytes::from_owned(serde_json::to_vec(&post).unwrap())), ); let body = self .http .send_and_read_future(&exch, glib::Priority::DEFAULT) .await?; match exch.status() { soup::Status::Created | soup::Status::Accepted => { let response_headers = exch .response_headers() .expect("Successful SoupMessage with no response headers"); let location = response_headers .one("Location") .ok_or(Error::NoLocationHeader)?; Ok(glib::Uri::parse(&location, glib::UriFlags::NONE)?) } soup::Status::InternalServerError | soup::Status::BadGateway | soup::Status::ServiceUnavailable => { todo!("micropub server is down") } soup::Status::Unauthorized => { Err(MicropubError::from(kittybox_util::micropub::ErrorKind::NotAuthorized).into()) } _ => { let error = match serde_json::from_slice::(&body) { Ok(error) => error, Err(err) => { tracing::debug!( "Error serializing body: {}", String::from_utf8_lossy(&body) ); Err(err)? } }; Err(error.into()) } } } }