Ok(client)
}
+pub struct TransactionLogEntry {
+ method: reqwest::Method,
+ url: reqwest::Url,
+ req_headers: Vec<(String, String)>,
+ status: reqwest::StatusCode,
+ rsp_headers: Vec<(String, String)>,
+}
+
+impl TransactionLogEntry {
+ fn translate_header(h: (&reqwest::header::HeaderName,
+ &reqwest::header::HeaderValue))
+ -> (String, String)
+ {
+ let (key, value) = h;
+ let value = if key == reqwest::header::AUTHORIZATION {
+ "[elided]".to_owned()
+ } else {
+ match value.to_str() {
+ Ok(s) => s.to_owned(),
+ Err(..) => format!("invalid UTF-8: {:?}", value.as_bytes()),
+ }
+ };
+ (key.as_str().to_owned(), value)
+ }
+
+ fn reason(&self) -> &'static str {
+ self.status.canonical_reason().unwrap_or("[untranslatable]")
+ }
+
+ pub fn format_short(&self) -> String {
+ format!("{0} {1} -> {2} {3}", self.method.as_str(), self.url.as_str(),
+ self.status.as_str(), self.reason())
+ }
+
+ pub fn format_full(&self) -> Vec<String> {
+ let mut lines = Vec::new();
+
+ lines.push(format!("Request: {0} {1}",
+ self.method.as_str(), self.url.as_str()));
+ for (key, value) in &self.req_headers {
+ lines.push(format!(" {0}: {1}", key, value));
+ }
+ lines.push(format!("Response: {0} {1}",
+ self.status.as_str(), self.reason()));
+ for (key, value) in &self.rsp_headers {
+ lines.push(format!(" {0}: {1}", key, value));
+ }
+ lines.push("".to_owned());
+
+ lines
+ }
+
+pub fn execute_and_log_request(client: &reqwest::blocking::Client,
+ req: reqwest::blocking::Request) ->
+ Result<(reqwest::blocking::Response, TransactionLogEntry), ClientError>
+{
+ let method = req.method().clone();
+ let url = req.url().clone();
+ let mut req_headers = Vec::new();
+ for h in req.headers() {
+ req_headers.push(TransactionLogEntry::translate_header(h));
+ }
+
+ let rsp = client.execute(req)?;
+ let status = rsp.status();
+ let mut rsp_headers = Vec::new();
+ for h in rsp.headers() {
+ rsp_headers.push(TransactionLogEntry::translate_header(h));
+ }
+
+ let log = TransactionLogEntry { method, url, req_headers,
+ status, rsp_headers };
+
+ Ok((rsp, log))
+}
+
impl Client {
pub fn new(cfgloc: &ConfigLocation) -> Result<Self, ClientError> {
Ok(Client {
self.fq(&self.auth.username)
}
+ fn consume_transaction_log(&mut self, log: TransactionLogEntry) {
+ }
+
fn api_request_cl(&self, client: &reqwest::blocking::Client, req: Req) ->
Result<(String, reqwest::blocking::RequestBuilder), ClientError>
{
req.build(&base_url, client, Some(&self.auth.user_token))
}
- fn api_request(&self, req: Req) ->
+ fn api_request(&mut self, req: Req) ->
Result<(String, reqwest::blocking::Response), ClientError>
{
- let (url, req) = self.api_request_cl(&self.client, req)?;
- let rsp = req.send()?;
- Ok((url, rsp))
+ let (urlstr, req) = self.api_request_cl(&self.client, req)?;
+ let (rsp, log) = execute_and_log_request(
+ &self.client, req.build()?)?;
+ self.consume_transaction_log(log);
+ Ok((urlstr, rsp))
}
pub fn instance(&mut self) -> Result<Instance, ClientError> {
let client = reqwest_client()?;
let (url, mut req) = self.api_request_cl(&client, req)?;
- let mut rsp = req.send()?;
+ let (rsp, log) = execute_and_log_request(
+ &self.client, req.build()?)?;
+ self.consume_transaction_log(log);
+ let mut rsp = rsp;
if rsp.status().is_redirection() {
// We follow one redirection here, and we permit it to be
// to precisely the (host, port) pair that was specified
}
req = client.request(method, newurl)
.bearer_auth(&self.auth.user_token);
- rsp = req.send()?;
+ let (newrsp, log) = execute_and_log_request(
+ &self.client, req.build()?)?;
+ self.consume_transaction_log(log);
+ rsp = newrsp;
}
}
};
use super::TopLevelError;
use super::auth::{AuthConfig, AuthError};
use super::config::ConfigLocation;
-use super::client::{reqwest_client, Req, ClientError};
+use super::client::{reqwest_client, Req, ClientError, execute_and_log_request};
use super::types::{Account, Application, Instance, Token};
struct Login {
})
}
+ fn execute_request(&self, req: reqwest::blocking::RequestBuilder)
+ -> Result<reqwest::blocking::Response, ClientError>
+ {
+ let (rsp, _log) = execute_and_log_request(
+ &self.client, req.build()?)?;
+ Ok(rsp)
+ }
+
fn register_client(&self) -> Result<Application, ClientError> {
let (url, req) = Req::post("/api/v1/apps")
.param("redirect_uris", REDIRECT_MAGIC_STRING)
.param("scopes", "read write push")
.param("website", "https://www.chiark.greenend.org.uk/~sgtatham/mastodonochrome/")
.build(&self.instance_url, &self.client, None)?;
- let rsp = req.send()?;
+ let rsp = self.execute_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
Err(ClientError::UrlError(url.clone(), rspstatus.to_string()))
.param("scope", "read write push"),
};
let (url, req) = req.build(&self.instance_url, &self.client, None)?;
- let rsp = req.send()?;
+ let rsp = self.execute_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
Err(ClientError::UrlError(url.clone(), rspstatus.to_string()))
{
let (url, req) = Req::get(path)
.build(&self.instance_url, &self.client, Some(token))?;
- let rsp = req.send()?;
+ let rsp = self.execute_request(req)?;
let rspstatus = rsp.status();
if !rspstatus.is_success() {
Err(ClientError::UrlError(url.clone(), rspstatus.to_string()))