-
Notifications
You must be signed in to change notification settings - Fork 252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(WidgetDriver): Pass Matrix API errors to the widget #4241
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -12,20 +12,22 @@ | |||||||
// See the License for the specific language governing permissions and | ||||||||
// limitations under the License. | ||||||||
|
||||||||
use std::fmt; | ||||||||
use std::collections::HashMap; | ||||||||
|
||||||||
use ruma::{ | ||||||||
api::client::delayed_events::{ | ||||||||
delayed_message_event, delayed_state_event, update_delayed_event, | ||||||||
api::client::{ | ||||||||
delayed_events::{delayed_message_event, delayed_state_event, update_delayed_event}, | ||||||||
error::ErrorBody, | ||||||||
}, | ||||||||
events::{AnyTimelineEvent, MessageLikeEventType, StateEventType}, | ||||||||
serde::Raw, | ||||||||
OwnedEventId, OwnedRoomId, | ||||||||
}; | ||||||||
use serde::{Deserialize, Serialize}; | ||||||||
use serde_json::{json, Value}; | ||||||||
|
||||||||
use super::{SendEventRequest, UpdateDelayedEventRequest}; | ||||||||
use crate::widget::StateKeySelector; | ||||||||
use crate::{widget::StateKeySelector, Error, HttpError}; | ||||||||
|
||||||||
#[derive(Deserialize, Debug)] | ||||||||
#[serde(tag = "action", rename_all = "snake_case", content = "data")] | ||||||||
|
@@ -46,17 +48,63 @@ pub(super) struct FromWidgetErrorResponse { | |||||||
error: FromWidgetError, | ||||||||
} | ||||||||
|
||||||||
impl FromWidgetErrorResponse { | ||||||||
pub(super) fn new(e: impl fmt::Display) -> Self { | ||||||||
Self { error: FromWidgetError { message: e.to_string() } } | ||||||||
impl From<&HttpError> for FromWidgetErrorResponse { | ||||||||
fn from(error: &HttpError) -> Self { | ||||||||
Self { | ||||||||
error: FromWidgetError { | ||||||||
message: error.to_string(), | ||||||||
matrix_api_error: error.as_client_api_error().and_then( | ||||||||
|api_error| match &api_error.body { | ||||||||
ErrorBody::Standard { kind, message } => Some(FromWidgetMatrixErrorBody { | ||||||||
http_status: api_error.status_code.as_u16().into(), | ||||||||
http_headers: HashMap::new(), | ||||||||
url: "".to_owned(), | ||||||||
response: json!({"errcode": kind.to_string(), "error": message }), | ||||||||
}), | ||||||||
_ => None, | ||||||||
}, | ||||||||
), | ||||||||
}, | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
impl From<&Error> for FromWidgetErrorResponse { | ||||||||
fn from(error: &Error) -> Self { | ||||||||
match &error { | ||||||||
Error::Http(e) => e.into(), | ||||||||
Error::UnknownError(e) => e.to_string().into(), | ||||||||
_ => error.to_string().into(), | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
impl From<String> for FromWidgetErrorResponse { | ||||||||
fn from(error: String) -> Self { | ||||||||
Self { error: FromWidgetError { message: error, matrix_api_error: None } } | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
impl From<&str> for FromWidgetErrorResponse { | ||||||||
fn from(error: &str) -> Self { | ||||||||
error.to_owned().into() | ||||||||
Comment on lines
+82
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using |
||||||||
} | ||||||||
} | ||||||||
|
||||||||
#[derive(Serialize)] | ||||||||
struct FromWidgetError { | ||||||||
message: String, | ||||||||
matrix_api_error: Option<FromWidgetMatrixErrorBody>, | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This crate is barely documented, which makes it super hard to understand how it works. Let's change that; of course I wouldn't have you change all of it at once, so let's try to do it incrementally. The doc comments don't have to be long; a single short descriptive line is sufficient to give us a rough idea of why/how it's used. Please add a doc comment to:
|
||||||||
} | ||||||||
|
||||||||
#[derive(Serialize)] | ||||||||
struct FromWidgetMatrixErrorBody { | ||||||||
http_status: u32, | ||||||||
// TODO: figure out the which type to use here. | ||||||||
http_headers: HashMap<String, String>, | ||||||||
url: String, | ||||||||
Comment on lines
+103
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If these two fields are never used, let's not introduce them now? |
||||||||
response: Value, | ||||||||
Comment on lines
+101
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want doc comments everywhere here too please :) |
||||||||
} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: always add a newline after a block closing
Suggested change
|
||||||||
#[derive(Serialize)] | ||||||||
pub(super) struct SupportedApiVersionsResponse { | ||||||||
supported_versions: Vec<ApiVersion>, | ||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,7 @@ pub(crate) enum IncomingMessage { | |
request_id: Uuid, | ||
|
||
/// The result of the request: response data or error message. | ||
response: Result<MatrixDriverResponse, String>, | ||
response: Result<MatrixDriverResponse, crate::Error>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The doc comment above needs an update^^ |
||
}, | ||
|
||
/// The `MatrixDriver` notified the `WidgetMachine` of a new matrix event. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ | |
|
||
#![warn(unreachable_pub)] | ||
|
||
use std::{fmt, iter, time::Duration}; | ||
use std::{iter, time::Duration}; | ||
|
||
use driver_req::UpdateDelayedEventRequest; | ||
use from_widget::UpdateDelayedEventResponse; | ||
|
@@ -50,11 +50,11 @@ use self::{ | |
#[cfg(doc)] | ||
use super::WidgetDriver; | ||
use super::{ | ||
capabilities, | ||
capabilities::{SEND_DELAYED_EVENT, UPDATE_DELAYED_EVENT}, | ||
filter::{MatrixEventContent, MatrixEventFilterInput}, | ||
Capabilities, StateKeySelector, | ||
}; | ||
use crate::widget::EventFilter; | ||
use crate::{widget::EventFilter, Error}; | ||
|
||
mod driver_req; | ||
mod from_widget; | ||
|
@@ -203,7 +203,10 @@ impl WidgetMachine { | |
) -> Vec<Action> { | ||
let request = match raw_request.deserialize() { | ||
Ok(r) => r, | ||
Err(e) => return vec![self.send_from_widget_error_response(raw_request, e)], | ||
Err(e) => { | ||
return vec![self | ||
.send_from_widget_error_response(raw_request, (&Error::SerdeJson(e)).into())] | ||
} | ||
}; | ||
|
||
match request { | ||
|
@@ -252,28 +255,29 @@ impl WidgetMachine { | |
let CapabilitiesState::Negotiated(capabilities) = &self.capabilities else { | ||
let text = | ||
"Received send update delayed event request before capabilities were negotiated"; | ||
return vec![self.send_from_widget_error_response(raw_request, text)]; | ||
return vec![self.send_from_widget_error_response(raw_request, text.into())]; | ||
}; | ||
if !capabilities.update_delayed_event { | ||
return vec![self.send_from_widget_error_response( | ||
raw_request, | ||
format!( | ||
"Not allowed: missing the {} capability.", | ||
capabilities::UPDATE_DELAYED_EVENT | ||
), | ||
format!("Not allowed: missing the {UPDATE_DELAYED_EVENT} capability.") | ||
.into(), | ||
)]; | ||
} | ||
let (request, request_action) = | ||
self.send_matrix_driver_request(UpdateDelayedEventRequest { | ||
action: req.action, | ||
delay_id: req.delay_id, | ||
}); | ||
request.then(|res, machine| { | ||
request.then(|result, machine| { | ||
vec![machine.send_from_widget_result_response( | ||
raw_request, | ||
// This is mapped to another type because the update_delay_event::Response | ||
// does not impl Serialize | ||
res.map(Into::<UpdateDelayedEventResponse>::into), | ||
match result { | ||
Ok(response) => Ok(Into::<UpdateDelayedEventResponse>::into(response)), | ||
Err(e) => Err((&e).into()), | ||
}, | ||
)] | ||
}); | ||
request_action.map(|a| vec![a]).unwrap_or_default() | ||
|
@@ -288,7 +292,7 @@ impl WidgetMachine { | |
) -> Option<Action> { | ||
let CapabilitiesState::Negotiated(capabilities) = &self.capabilities else { | ||
let text = "Received read event request before capabilities were negotiated"; | ||
return Some(self.send_from_widget_error_response(raw_request, text)); | ||
return Some(self.send_from_widget_error_response(raw_request, text.into())); | ||
}; | ||
|
||
match request { | ||
|
@@ -297,7 +301,7 @@ impl WidgetMachine { | |
if !capabilities.read.iter().any(filter_fn) { | ||
return Some(self.send_from_widget_error_response( | ||
raw_request, | ||
"Not allowed to read message like event", | ||
"Not allowed to read message like event".into(), | ||
)); | ||
} | ||
|
||
|
@@ -306,16 +310,21 @@ impl WidgetMachine { | |
let request = ReadMessageLikeEventRequest { event_type, limit }; | ||
let (request, action) = self.send_matrix_driver_request(request); | ||
request.then(|result, machine| { | ||
let response = result.and_then(|mut events| { | ||
let CapabilitiesState::Negotiated(capabilities) = &machine.capabilities | ||
else { | ||
let err = "Received read event request before capabilities negotiation"; | ||
return Err(err.into()); | ||
}; | ||
|
||
events.retain(|e| capabilities.raw_event_matches_read_filter(e)); | ||
Ok(ReadEventResponse { events }) | ||
}); | ||
let response = match (result, &machine.capabilities) { | ||
(Ok(mut events), CapabilitiesState::Negotiated(capabilities)) => { | ||
events.retain(|e| capabilities.raw_event_matches_read_filter(e)); | ||
Ok(ReadEventResponse { events }) | ||
} | ||
(Ok(_), CapabilitiesState::Unset) => { | ||
Err("Received read event request before capabilities negotiation" | ||
.into()) | ||
} | ||
(Ok(_), CapabilitiesState::Negotiating) => { | ||
Err("Received read event request while capabilities were negotiating" | ||
.into()) | ||
} | ||
(Err(e), _) => Err((&e).into()), | ||
}; | ||
Comment on lines
+313
to
+327
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is messier like this. Can we keep the |
||
vec![machine.send_from_widget_result_response(raw_request, response)] | ||
}); | ||
action | ||
|
@@ -343,14 +352,16 @@ impl WidgetMachine { | |
let request = ReadStateEventRequest { event_type, state_key }; | ||
let (request, action) = self.send_matrix_driver_request(request); | ||
request.then(|result, machine| { | ||
let response = result.map(|events| ReadEventResponse { events }); | ||
let response = result | ||
.map(|events| ReadEventResponse { events }) | ||
.map_err(|e| (&e).into()); | ||
vec![machine.send_from_widget_result_response(raw_request, response)] | ||
}); | ||
action | ||
} else { | ||
Some(self.send_from_widget_error_response( | ||
raw_request, | ||
"Not allowed to read state event", | ||
"Not allowed to read state event".into(), | ||
)) | ||
} | ||
} | ||
|
@@ -380,15 +391,15 @@ impl WidgetMachine { | |
if !capabilities.send_delayed_event && request.delay.is_some() { | ||
return Some(self.send_from_widget_error_response( | ||
raw_request, | ||
format!( | ||
"Not allowed: missing the {} capability.", | ||
capabilities::SEND_DELAYED_EVENT | ||
), | ||
format!("Not allowed: missing the {SEND_DELAYED_EVENT} capability.").into(), | ||
)); | ||
} | ||
if !capabilities.send.iter().any(|filter| filter.matches(&filter_in)) { | ||
return Some( | ||
self.send_from_widget_error_response(raw_request, "Not allowed to send event"), | ||
self.send_from_widget_error_response( | ||
raw_request, | ||
"Not allowed to send event".into(), | ||
), | ||
); | ||
} | ||
|
||
|
@@ -397,7 +408,8 @@ impl WidgetMachine { | |
if let Ok(r) = result.as_mut() { | ||
r.set_room_id(machine.room_id.clone()); | ||
} | ||
vec![machine.send_from_widget_result_response(raw_request, result)] | ||
vec![machine | ||
.send_from_widget_result_response(raw_request, result.map_err(|e| (&e).into()))] | ||
}); | ||
action | ||
} | ||
|
@@ -439,7 +451,7 @@ impl WidgetMachine { | |
fn process_matrix_driver_response( | ||
&mut self, | ||
request_id: Uuid, | ||
response: Result<MatrixDriverResponse, String>, | ||
response: Result<MatrixDriverResponse, Error>, | ||
) -> Vec<Action> { | ||
match self.pending_matrix_driver_requests.extract(&request_id) { | ||
Ok(request) => request | ||
|
@@ -472,15 +484,15 @@ impl WidgetMachine { | |
fn send_from_widget_error_response( | ||
&self, | ||
raw_request: Raw<FromWidgetRequest>, | ||
error: impl fmt::Display, | ||
error: FromWidgetErrorResponse, | ||
) -> Action { | ||
self.send_from_widget_response(raw_request, FromWidgetErrorResponse::new(error)) | ||
self.send_from_widget_response(raw_request, error) | ||
} | ||
|
||
fn send_from_widget_result_response( | ||
&self, | ||
raw_request: Raw<FromWidgetRequest>, | ||
result: Result<impl Serialize, impl fmt::Display>, | ||
result: Result<impl Serialize, FromWidgetErrorResponse>, | ||
) -> Action { | ||
match result { | ||
Ok(res) => self.send_from_widget_response(raw_request, res), | ||
|
@@ -586,7 +598,7 @@ impl ToWidgetRequestMeta { | |
} | ||
|
||
type MatrixDriverResponseFn = | ||
Box<dyn FnOnce(Result<MatrixDriverResponse, String>, &mut WidgetMachine) -> Vec<Action> + Send>; | ||
Box<dyn FnOnce(Result<MatrixDriverResponse, Error>, &mut WidgetMachine) -> Vec<Action> + Send>; | ||
|
||
pub(crate) struct MatrixDriverRequestMeta { | ||
response_fn: Option<MatrixDriverResponseFn>, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're around, can you add a doc comment to this function please?