Private
Public Access
1
0

More logging, delete, new convo

This commit is contained in:
2026-04-01 17:16:58 -07:00
parent c2a697f2c1
commit 28679a1dd1
7 changed files with 331 additions and 45 deletions

View File

@@ -65,7 +65,15 @@ impl std::error::Error for Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
match self {
Error::ClientError(message) => write!(f, "{}", message),
Error::HTTPError(err) => write!(f, "HTTP transport error: {}", err),
Error::SerdeError(err) => write!(f, "JSON error: {}", err),
Error::DecodeError(message) => write!(f, "Decode error: {}", message),
Error::PongError(err) => write!(f, "WebSocket error: {}", err),
Error::URLError => write!(f, "Invalid URL"),
Error::Unauthorized => write!(f, "Unauthorized"),
}
}
}
@@ -284,6 +292,17 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
Ok(())
}
async fn delete_conversation(
&mut self,
conversation_id: &ConversationID,
) -> Result<(), Self::Error> {
// SERVER JANK: This should be DELETE or POST, but it's GET for some reason.
let endpoint = format!("delete?guid={}", conversation_id);
self.response_with_body_retry(&endpoint, Method::GET, Body::empty, true)
.await?;
Ok(())
}
async fn get_messages(
&mut self,
conversation_id: &ConversationID,
@@ -313,6 +332,25 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
&mut self,
outgoing_message: &OutgoingMessage,
) -> Result<SendMessageResponse, Self::Error> {
match &outgoing_message.target {
OutgoingMessageTarget::Conversation(conversation_id) => {
log::debug!(
"Sending message to conversation {} (body_length={}, attachment_count={})",
conversation_id,
outgoing_message.text.len(),
outgoing_message.file_transfer_guids.len()
);
}
OutgoingMessageTarget::Handles(handle_ids) => {
log::debug!(
"Sending message to resolved handles {:?} (body_length={}, attachment_count={})",
handle_ids,
outgoing_message.text.len(),
outgoing_message.file_transfer_guids.len()
);
}
}
let message: SendMessageResponse = self
.deserialized_response_with_body("sendMessage", Method::POST, || {
Self::send_message_request_body(outgoing_message)
@@ -326,6 +364,7 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
&mut self,
handle_id: &str,
) -> Result<ResolveHandleResponse, Self::Error> {
log::debug!("Resolving handle {}", handle_id);
let endpoint = format!("resolveHandle?id={}", urlencoding::encode(handle_id));
let response: ResolveHandleResponse =
self.deserialized_response(&endpoint, Method::GET).await?;
@@ -542,6 +581,18 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
}
}
fn log_transport_error(method: &Method, target: &str, err: &hyper::Error) {
log::error!("HTTP transport error for {} {}: {}", method, target, err);
if format!("{:?}", err).contains("IncompleteMessage") {
log::error!(
"The server closed the connection before a complete response was received for {} {}.",
method,
target
);
}
}
async fn deserialized_response<T: DeserializeOwned>(
&mut self,
endpoint: &str,
@@ -575,15 +626,26 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
T: DeserializeOwned,
{
let response = self
.response_with_body_retry(endpoint, method, body_fn, retry_auth)
.response_with_body_retry(endpoint, method.clone(), body_fn, retry_auth)
.await?;
// Read and parse response body
let body = hyper::body::to_bytes(response.into_body()).await?;
let body = match hyper::body::to_bytes(response.into_body()).await {
Ok(body) => body,
Err(err) => {
Self::log_transport_error(&method, endpoint, &err);
return Err(Error::HTTPError(err));
}
};
let parsed: T = match serde_json::from_slice(&body) {
Ok(result) => Ok(result),
Err(json_err) => {
log::error!("Error deserializing JSON: {:?}", json_err);
log::error!(
"Error deserializing JSON for {} {}: {:?}",
method,
endpoint,
json_err
);
log::error!("Body: {:?}", String::from_utf8_lossy(&body));
// If JSON deserialization fails, try to interpret it as plain text
@@ -606,7 +668,8 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
use hyper::StatusCode;
let uri = self.uri_for_endpoint(endpoint, None)?;
log::debug!("Requesting {:?} {:?}", method, uri);
let uri_string = uri.to_string();
log::debug!("Requesting {} {}", method, uri_string);
let mut build_request = |auth: &Option<String>| {
let body = body_fn();
@@ -620,13 +683,24 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
log::trace!("Obtaining token from auth store");
let token = self.auth_store.get_token().await;
log::trace!("Token: {:?}", token);
log::trace!("Token present: {}", token.is_some());
let request = build_request(&token);
log::trace!("Request: {:?}. Sending request...", request);
log::trace!(
"Sending request: method={} uri={} authenticated={}",
method,
uri_string,
token.is_some()
);
let mut response = self.client.request(request).await?;
log::debug!("-> Response: {:}", response.status());
let mut response = match self.client.request(request).await {
Ok(response) => response,
Err(err) => {
Self::log_transport_error(&method, &uri_string, &err);
return Err(Error::HTTPError(err));
}
};
log::debug!("-> Response: {}", response.status());
match response.status() {
StatusCode::OK => { /* cool */ }
@@ -645,7 +719,19 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
let new_token = self.authenticate(credentials.clone()).await?;
let request = build_request(&Some(new_token.to_string()));
response = self.client.request(request).await?;
log::trace!(
"Retrying request after authentication: method={} uri={} authenticated=true",
method,
uri_string
);
response = match self.client.request(request).await {
Ok(response) => response,
Err(err) => {
Self::log_transport_error(&method, &uri_string, &err);
return Err(Error::HTTPError(err));
}
};
log::debug!("-> Retry response: {}", response.status());
} else {
return Err(Error::ClientError(
"Unauthorized, no credentials provided".into(),

View File

@@ -79,6 +79,12 @@ pub trait APIInterface {
conversation_id: &ConversationID,
) -> Result<(), Self::Error>;
// (GET) /delete
async fn delete_conversation(
&mut self,
conversation_id: &ConversationID,
) -> Result<(), Self::Error>;
// (WS) /updates
async fn open_event_socket(
&mut self,

View File

@@ -52,4 +52,20 @@ pub mod api_interface {
assert_eq!(sent.message.text, "hello");
assert_eq!(sent.conversation_id, None);
}
#[tokio::test]
async fn test_delete_conversation() {
let mut client = TestClient::new();
let test_convo = Conversation::builder()
.display_name("Delete Me")
.build();
client.conversations.push(test_convo.clone());
client.delete_conversation(&test_convo.guid).await.unwrap();
let conversations = client.get_conversations().await.unwrap();
assert!(conversations.is_empty());
}
}

View File

@@ -187,4 +187,19 @@ impl APIInterface for TestClient {
) -> Result<(), Self::Error> {
Ok(())
}
async fn delete_conversation(
&mut self,
conversation_id: &ConversationID,
) -> Result<(), Self::Error> {
let previous_len = self.conversations.len();
self.conversations.retain(|c| &c.guid != conversation_id);
self.messages.remove(conversation_id);
if self.conversations.len() == previous_len {
return Err(TestError::ConversationNotFound);
}
Ok(())
}
}