diff --git a/crates/matrix-sdk/src/widget/machine/from_widget.rs b/crates/matrix-sdk/src/widget/machine/from_widget.rs index 81fa98609..ecca3aaa6 100644 --- a/crates/matrix-sdk/src/widget/machine/from_widget.rs +++ b/crates/matrix-sdk/src/widget/machine/from_widget.rs @@ -12,8 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use serde::Deserialize; +use std::fmt; + +use serde::{Deserialize, Serialize}; #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub(super) enum FromWidgetRequest {} + +#[derive(Serialize)] +pub(super) struct FromWidgetErrorResponse { + error: FromWidgetError, +} + +impl FromWidgetErrorResponse { + pub(super) fn new(e: impl fmt::Display) -> Self { + Self { error: FromWidgetError { message: e.to_string() } } + } +} + +#[derive(Serialize)] +struct FromWidgetError { + message: String, +} diff --git a/crates/matrix-sdk/src/widget/machine/incoming.rs b/crates/matrix-sdk/src/widget/machine/incoming.rs index fca7a5447..c78511f1d 100644 --- a/crates/matrix-sdk/src/widget/machine/incoming.rs +++ b/crates/matrix-sdk/src/widget/machine/incoming.rs @@ -65,7 +65,7 @@ pub(super) struct IncomingWidgetMessage { } pub(super) enum IncomingWidgetMessageKind { - Request(FromWidgetRequest), + Request(Raw), Response(ToWidgetResponse), } @@ -95,9 +95,7 @@ impl<'de> Deserialize<'de> for IncomingWidgetMessage { serde_json::from_str(raw.get()).map_err(de::Error::custom)?; let kind = match api { - ApiTag::FromWidget => serde_json::from_str(raw.get()) - .map(IncomingWidgetMessageKind::Request) - .map_err(de::Error::custom)?, + ApiTag::FromWidget => IncomingWidgetMessageKind::Request(Raw::from_json(raw)), ApiTag::ToWidget => serde_json::from_str(raw.get()) .map(IncomingWidgetMessageKind::Response) .map_err(de::Error::custom)?, diff --git a/crates/matrix-sdk/src/widget/machine/mod.rs b/crates/matrix-sdk/src/widget/machine/mod.rs index cec8b6040..88ddc640a 100644 --- a/crates/matrix-sdk/src/widget/machine/mod.rs +++ b/crates/matrix-sdk/src/widget/machine/mod.rs @@ -17,6 +17,7 @@ #![warn(unreachable_pub)] use indexmap::{map::Entry, IndexMap}; +use ruma::serde::Raw; use serde::Serialize; use serde_json::value::RawValue as RawJsonValue; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; @@ -25,6 +26,7 @@ use uuid::Uuid; use self::{ driver_req::{AcquirePermissions, MatrixDriverRequest, MatrixDriverRequestHandle}, + from_widget::{FromWidgetErrorResponse, FromWidgetRequest}, incoming::{IncomingWidgetMessage, IncomingWidgetMessageKind}, to_widget::{ NotifyPermissionsChanged, RequestPermissions, ToWidgetRequest, ToWidgetRequestHandle, @@ -113,13 +115,28 @@ impl WidgetMachine { } match message.kind { - IncomingWidgetMessageKind::Request(req) => match req {}, + IncomingWidgetMessageKind::Request(request) => { + self.process_from_widget_request(request); + } IncomingWidgetMessageKind::Response(response) => { self.process_to_widget_response(message.request_id, response); } } } + #[instrument(skip_all, fields(request_id))] + fn process_from_widget_request(&mut self, raw_request: Raw) { + let request = match raw_request.deserialize() { + Ok(r) => r, + Err(e) => { + self.send_from_widget_response(raw_request, FromWidgetErrorResponse::new(e)); + return; + } + }; + + match request {} + } + #[instrument(skip_all, fields(?request_id))] fn process_to_widget_response(&mut self, request_id: String, response: ToWidgetResponse) { let Ok(request_id) = Uuid::parse_str(&request_id) else { @@ -173,6 +190,41 @@ impl WidgetMachine { } } + #[instrument(skip_all, fields(request_id))] + fn send_from_widget_response( + &self, + raw_request: Raw, + response_data: impl Serialize, + ) { + let mut object = match raw_request.deserialize_as::>>() { + Ok(o) => o, + Err(e) => { + error!("Failed to converted FromWidgetRequest to object representation: {e}"); + return; + } + }; + let response_data = match serde_json::value::to_raw_value(&response_data) { + Ok(d) => d, + Err(e) => { + error!("Failed to serialize response data: {e}"); + return; + } + }; + object.insert("response".to_owned(), response_data); + + let serialized = match serde_json::to_string(&object) { + Ok(s) => s, + Err(e) => { + error!("Failed to serialize response: {e}"); + return; + } + }; + + if let Err(e) = self.actions_sender.send(Action::SendToWidget(serialized)) { + error!("Failed to send action: {e}"); + } + } + #[instrument(skip_all, fields(action = T::ACTION))] fn send_to_widget_request( &mut self, diff --git a/crates/matrix-sdk/src/widget/machine/tests/capabilities.rs b/crates/matrix-sdk/src/widget/machine/tests/capabilities.rs index 2529b3ad4..5013e881f 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/capabilities.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/capabilities.rs @@ -13,28 +13,13 @@ // limitations under the License. use assert_matches::assert_matches; -use ruma::serde::JsonObject; -use serde_json::{from_value, json, Value as JsonValue}; -use uuid::Uuid; +use serde_json::{from_value, json}; +use super::{parse_msg, WIDGET_ID}; use crate::widget::machine::{ incoming::MatrixDriverResponse, Action, IncomingMessage, MatrixDriverRequestData, WidgetMachine, }; -const WIDGET_ID: &str = "test-widget"; - -/// Create a JSON string from a [`json!`][serde_json::json] "literal". -#[macro_export] -macro_rules! json_string { - ($( $tt:tt )*) => { ::serde_json::json!( $($tt)* ).to_string() }; -} - -fn parse_msg(msg: &str) -> (JsonValue, Uuid) { - let mut deserialized: JsonObject = serde_json::from_str(msg).unwrap(); - let request_id = from_value(deserialized.remove("requestId").unwrap()).unwrap(); - (JsonValue::Object(deserialized), request_id) -} - #[test] fn machine_can_negotiate_capabilities_immediately() { let (mut machine, mut actions_recv) = WidgetMachine::new(WIDGET_ID.to_owned(), false); diff --git a/crates/matrix-sdk/src/widget/machine/tests/error.rs b/crates/matrix-sdk/src/widget/machine/tests/error.rs new file mode 100644 index 000000000..3f29c4662 --- /dev/null +++ b/crates/matrix-sdk/src/widget/machine/tests/error.rs @@ -0,0 +1,47 @@ +// Copyright 2023 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use assert_matches::assert_matches; +use serde_json::json; + +use super::{parse_msg, WIDGET_ID}; +use crate::widget::machine::{Action, IncomingMessage, WidgetMachine}; + +#[test] +fn machine_sends_error_for_unknown_request() { + let (mut machine, mut actions_recv) = WidgetMachine::new(WIDGET_ID.to_owned(), true); + + // No messages from the machine at first + assert_matches!(actions_recv.try_recv(), Err(_)); + + machine.process(IncomingMessage::WidgetMessage(json_string!({ + "api": "fromWidget", + "widgetId": WIDGET_ID, + "requestId": "invalid-req", + "action": "I AM ERROR", + "data": { + "some": "field", + }, + }))); + + let action = actions_recv.try_recv().unwrap(); + let msg = assert_matches!(action, Action::SendToWidget(msg) => msg); + let (msg, request_id) = parse_msg(&msg); + assert_eq!(request_id, "invalid-req"); + assert_eq!(msg["api"], "fromWidget"); + assert_eq!(msg["widgetId"], WIDGET_ID); + assert_eq!(msg["action"], "I AM ERROR"); + assert_eq!(msg["data"], json!({ "some": "field" })); + assert!(msg["response"]["error"]["message"].is_string()); +} diff --git a/crates/matrix-sdk/src/widget/machine/tests/mod.rs b/crates/matrix-sdk/src/widget/machine/tests/mod.rs index 797ae862b..5be64a29c 100644 --- a/crates/matrix-sdk/src/widget/machine/tests/mod.rs +++ b/crates/matrix-sdk/src/widget/machine/tests/mod.rs @@ -12,4 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +use assert_matches::assert_matches; +use ruma::serde::JsonObject; +use serde_json::Value as JsonValue; + +/// Create a JSON string from a [`json!`][serde_json::json] "literal". +#[macro_export] +macro_rules! json_string { + ($( $tt:tt )*) => { ::serde_json::json!( $($tt)* ).to_string() }; +} + mod capabilities; +mod error; + +const WIDGET_ID: &str = "test-widget"; + +fn parse_msg(msg: &str) -> (JsonValue, String) { + let mut deserialized: JsonObject = serde_json::from_str(msg).unwrap(); + let request_id = + assert_matches!(deserialized.remove("requestId").unwrap(), JsonValue::String(s) => s); + (JsonValue::Object(deserialized), request_id) +}