widget: Implement fromWidget response sending

… and send an error response when the request fails to deserialize.
This commit is contained in:
Jonas Platte
2023-10-18 14:16:12 +02:00
committed by Jonas Platte
parent 9c71c2b733
commit ea36eaa09a
6 changed files with 143 additions and 23 deletions
@@ -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,
}
@@ -65,7 +65,7 @@ pub(super) struct IncomingWidgetMessage {
}
pub(super) enum IncomingWidgetMessageKind {
Request(FromWidgetRequest),
Request(Raw<FromWidgetRequest>),
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)?,
+53 -1
View File
@@ -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<FromWidgetRequest>) {
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<FromWidgetRequest>,
response_data: impl Serialize,
) {
let mut object = match raw_request.deserialize_as::<IndexMap<String, Box<RawJsonValue>>>() {
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<T: ToWidgetRequest>(
&mut self,
@@ -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);
@@ -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());
}
@@ -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)
}