Merge branch 'master' into fix-links-liveview

This commit is contained in:
Evan Almloff committed 2023-05-24 07:38:04 -05:00
commit 553205dc74
114 files changed
+7669 -368

No files matched your search

+14 -1
View File
@@ -57,4 +57,17 @@ hot-reload = ["dioxus-hot-reload"]
[dev-dependencies]
dioxus-core-macro = { path = "../core-macro" }
dioxus-hooks = { path = "../hooks" }
# image = "0.24.0" # enable this when generating a new desktop image
dioxus = { path = "../dioxus" }
exitcode = "1.1.2"
scraper = "0.16.0"
# These tests need to be run on the main thread, so they cannot use rust's test harness.
[[test]]
name = "check_events"
path = "headless_tests/events.rs"
harness = false
[[test]]
name = "check_rendering"
path = "headless_tests/rendering.rs"
harness = false
+351
View File
@@ -0,0 +1,351 @@
use dioxus::html::geometry::euclid::Vector3D;
use dioxus::prelude::*;
use dioxus_desktop::DesktopContext;
pub(crate) fn check_app_exits(app: Component) {
use dioxus_desktop::tao::window::WindowBuilder;
use dioxus_desktop::Config;
// This is a deadman's switch to ensure that the app exits
let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
let should_panic_clone = should_panic.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(100));
if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
std::process::exit(exitcode::SOFTWARE);
}
});
dioxus_desktop::launch_cfg(
app,
Config::new().with_window(WindowBuilder::new().with_visible(false)),
);
should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
}
pub fn main() {
check_app_exits(app);
}
fn mock_event(cx: &ScopeState, id: &'static str, value: &'static str) {
use_effect(cx, (), |_| {
let desktop_context: DesktopContext = cx.consume_context().unwrap();
async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
desktop_context.eval(&format!(
r#"let element = document.getElementById('{}');
// Dispatch a synthetic event
const event = {};
console.log(element, event);
element.dispatchEvent(event);
"#,
id, value
));
}
});
}
#[allow(deprecated)]
fn app(cx: Scope) -> Element {
let desktop_context: DesktopContext = cx.consume_context().unwrap();
let recieved_events = use_state(cx, || 0);
// button
mock_event(
cx,
"button",
r#"new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
button: 0,
})"#,
);
// mouse_move_div
mock_event(
cx,
"mouse_move_div",
r#"new MouseEvent("mousemove", {
view: window,
bubbles: true,
cancelable: true,
buttons: 2,
})"#,
);
// mouse_click_div
mock_event(
cx,
"mouse_click_div",
r#"new MouseEvent("click", {
view: window,
bubbles: true,
cancelable: true,
buttons: 2,
button: 2,
})"#,
);
// mouse_dblclick_div
mock_event(
cx,
"mouse_dblclick_div",
r#"new MouseEvent("dblclick", {
view: window,
bubbles: true,
cancelable: true,
buttons: 1|2,
button: 2,
})"#,
);
// mouse_down_div
mock_event(
cx,
"mouse_down_div",
r#"new MouseEvent("mousedown", {
view: window,
bubbles: true,
cancelable: true,
buttons: 2,
button: 2,
})"#,
);
// mouse_up_div
mock_event(
cx,
"mouse_up_div",
r#"new MouseEvent("mouseup", {
view: window,
bubbles: true,
cancelable: true,
buttons: 0,
button: 0,
})"#,
);
// wheel_div
mock_event(
cx,
"wheel_div",
r#"new WheelEvent("wheel", {
view: window,
deltaX: 1.0,
deltaY: 2.0,
deltaZ: 3.0,
deltaMode: 0x00,
bubbles: true,
})"#,
);
// key_down_div
mock_event(
cx,
"key_down_div",
r#"new KeyboardEvent("keydown", {
key: "a",
code: "KeyA",
location: 0,
repeat: true,
keyCode: 65,
charCode: 97,
char: "a",
charCode: 0,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
isComposing: false,
which: 65,
bubbles: true,
})"#,
);
// key_up_div
mock_event(
cx,
"key_up_div",
r#"new KeyboardEvent("keyup", {
key: "a",
code: "KeyA",
location: 0,
repeat: false,
keyCode: 65,
charCode: 97,
char: "a",
charCode: 0,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
isComposing: false,
which: 65,
bubbles: true,
})"#,
);
// key_press_div
mock_event(
cx,
"key_press_div",
r#"new KeyboardEvent("keypress", {
key: "a",
code: "KeyA",
location: 0,
repeat: false,
keyCode: 65,
charCode: 97,
char: "a",
charCode: 0,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
isComposing: false,
which: 65,
bubbles: true,
})"#,
);
// focus_in_div
mock_event(
cx,
"focus_in_div",
r#"new FocusEvent("focusin", {bubbles: true})"#,
);
// focus_out_div
mock_event(
cx,
"focus_out_div",
r#"new FocusEvent("focusout",{bubbles: true})"#,
);
if **recieved_events == 12 {
println!("all events recieved");
desktop_context.close();
}
cx.render(rsx! {
div {
button {
id: "button",
onclick: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().is_empty());
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
recieved_events.modify(|x| *x + 1)
},
}
div {
id: "mouse_move_div",
onmousemove: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
recieved_events.modify(|x| *x + 1)
},
}
div {
id: "mouse_click_div",
onclick: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
recieved_events.modify(|x| *x + 1)
},
}
div{
id: "mouse_dblclick_div",
ondblclick: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary));
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
recieved_events.modify(|x| *x + 1)
}
}
div{
id: "mouse_down_div",
onmousedown: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
recieved_events.modify(|x| *x + 1)
}
}
div{
id: "mouse_up_div",
onmouseup: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert!(event.data.held_buttons().is_empty());
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
recieved_events.modify(|x| *x + 1)
}
}
div{
id: "wheel_div",
width: "100px",
height: "100px",
background_color: "red",
onwheel: move |event| {
println!("{:?}", event.data);
let dioxus_html::geometry::WheelDelta::Pixels(delta)= event.data.delta()else{
panic!("Expected delta to be in pixels")
};
assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
recieved_events.modify(|x| *x + 1)
}
}
input{
id: "key_down_div",
onkeydown: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert_eq!(event.data.key().to_string(), "a");
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location, 0);
assert!(event.data.is_auto_repeating());
recieved_events.modify(|x| *x + 1)
}
}
input{
id: "key_up_div",
onkeyup: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert_eq!(event.data.key().to_string(), "a");
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location, 0);
assert!(!event.data.is_auto_repeating());
recieved_events.modify(|x| *x + 1)
}
}
input{
id: "key_press_div",
onkeypress: move |event| {
println!("{:?}", event.data);
assert!(event.data.modifiers().is_empty());
assert_eq!(event.data.key().to_string(), "a");
assert_eq!(event.data.code().to_string(), "KeyA");
assert_eq!(event.data.location, 0);
assert!(!event.data.is_auto_repeating());
recieved_events.modify(|x| *x + 1)
}
}
input{
id: "focus_in_div",
onfocusin: move |event| {
println!("{:?}", event.data);
recieved_events.modify(|x| *x + 1)
}
}
input{
id: "focus_out_div",
onfocusout: move |event| {
println!("{:?}", event.data);
recieved_events.modify(|x| *x + 1)
}
}
}
})
}
@@ -0,0 +1,94 @@
use dioxus::prelude::*;
use dioxus_desktop::DesktopContext;
pub(crate) fn check_app_exits(app: Component) {
use dioxus_desktop::tao::window::WindowBuilder;
use dioxus_desktop::Config;
// This is a deadman's switch to ensure that the app exits
let should_panic = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
let should_panic_clone = should_panic.clone();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(100));
if should_panic_clone.load(std::sync::atomic::Ordering::SeqCst) {
std::process::exit(exitcode::SOFTWARE);
}
});
dioxus_desktop::launch_cfg(
app,
Config::new().with_window(WindowBuilder::new().with_visible(false)),
);
should_panic.store(false, std::sync::atomic::Ordering::SeqCst);
}
fn main() {
check_app_exits(check_html_renders);
}
fn use_inner_html(cx: &ScopeState, id: &'static str) -> Option<String> {
let value: &UseRef<Option<String>> = use_ref(cx, || None);
use_effect(cx, (), |_| {
to_owned![value];
let desktop_context: DesktopContext = cx.consume_context().unwrap();
async move {
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
let html = desktop_context
.eval(&format!(
r#"let element = document.getElementById('{}');
return element.innerHTML;"#,
id
))
.await;
if let Ok(serde_json::Value::String(html)) = html {
println!("html: {}", html);
value.set(Some(html));
}
}
});
value.read().clone()
}
const EXPECTED_HTML: &str = r#"<div id="5" style="width: 100px; height: 100px; color: rgb(0, 0, 0);"><input type="checkbox"><h1>text</h1><div><p>hello world</p></div></div>"#;
fn check_html_renders(cx: Scope) -> Element {
let inner_html = use_inner_html(cx, "main_div");
let desktop_context: DesktopContext = cx.consume_context().unwrap();
if let Some(raw_html) = inner_html.as_deref() {
let fragment = scraper::Html::parse_fragment(raw_html);
println!("fragment: {:?}", fragment.html());
let expected = scraper::Html::parse_fragment(EXPECTED_HTML);
println!("fragment: {:?}", expected.html());
if fragment == expected {
println!("html matches");
desktop_context.close();
}
}
let dyn_value = 0;
let dyn_element = rsx! {
div {
dangerous_inner_html: "<p>hello world</p>",
}
};
render! {
div {
id: "main_div",
div {
width: "100px",
height: "100px",
color: "rgb({dyn_value}, {dyn_value}, {dyn_value})",
id: 5,
input {
"type": "checkbox",
},
h1 {
"text"
}
dyn_element
}
}
}
}
+7 -25
View File
@@ -5,6 +5,7 @@ use std::rc::Weak;
use crate::create_new_window;
use crate::eval::EvalResult;
use crate::events::IpcMessage;
use crate::query::QueryEngine;
use crate::shortcut::IntoKeyCode;
use crate::shortcut::IntoModifersState;
use crate::shortcut::ShortcutId;
@@ -16,7 +17,6 @@ use dioxus_core::ScopeState;
use dioxus_core::VirtualDom;
#[cfg(all(feature = "hot-reload", debug_assertions))]
use dioxus_hot_reload::HotReloadMsg;
use serde_json::Value;
use slab::Slab;
use wry::application::event::Event;
use wry::application::event_loop::EventLoopProxy;
@@ -59,8 +59,8 @@ pub struct DesktopContext {
/// The proxy to the event loop
pub proxy: ProxyType,
/// The receiver for eval results since eval is async
pub(super) eval: tokio::sync::broadcast::Sender<Value>,
/// The receiver for queries about the current window
pub(super) query: QueryEngine,
pub(super) pending_windows: WebviewQueue,
@@ -96,7 +96,7 @@ impl DesktopContext {
webview,
proxy,
event_loop,
eval: tokio::sync::broadcast::channel(8).0,
query: Default::default(),
pending_windows: webviews,
event_handlers,
shortcut_manager,
@@ -210,28 +210,10 @@ impl DesktopContext {
/// Evaluate a javascript expression
pub fn eval(&self, code: &str) -> EvalResult {
// Embed the return of the eval in a function so we can send it back to the main thread
let script = format!(
r#"
window.ipc.postMessage(
JSON.stringify({{
"method":"eval_result",
"params": (
function(){{
{code}
}}
)()
}})
);
"#
);
// the query id lets us keep track of the eval result and send it back to the main thread
let query = self.query.new_query(code, &self.webview);
if let Err(e) = self.webview.evaluate_script(&script) {
// send an error to the eval receiver
log::warn!("Eval script error: {e}");
}
EvalResult::new(self.eval.clone())
EvalResult::new(query)
}
/// Create a wry event handler that listens for wry events.
+123
View File
@@ -0,0 +1,123 @@
use std::rc::Rc;
use dioxus_core::ElementId;
use dioxus_html::{geometry::euclid::Rect, MountedResult, RenderedElementBacking};
use wry::webview::WebView;
use crate::query::QueryEngine;
/// A mounted element passed to onmounted events
pub struct DesktopElement {
id: ElementId,
webview: Rc<WebView>,
query: QueryEngine,
}
impl DesktopElement {
pub(crate) fn new(id: ElementId, webview: Rc<WebView>, query: QueryEngine) -> Self {
Self { id, webview, query }
}
}
impl RenderedElementBacking for DesktopElement {
fn get_raw_element(&self) -> dioxus_html::MountedResult<&dyn std::any::Any> {
Ok(self)
}
fn get_client_rect(
&self,
) -> std::pin::Pin<
Box<
dyn futures_util::Future<
Output = dioxus_html::MountedResult<dioxus_html::geometry::euclid::Rect<f64, f64>>,
>,
>,
> {
let script = format!("return window.interpreter.GetClientRect({});", self.id.0);
let fut = self
.query
.new_query::<Option<Rect<f64, f64>>>(&script, &self.webview)
.resolve();
Box::pin(async move {
match fut.await {
Ok(Some(rect)) => Ok(rect),
Ok(None) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(
Box::new(DesktopQueryError::FailedToQuery),
)),
Err(err) => {
MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))
}
}
})
}
fn scroll_to(
&self,
behavior: dioxus_html::ScrollBehavior,
) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
let script = format!(
"return window.interpreter.ScrollTo({}, {});",
self.id.0,
serde_json::to_string(&behavior).expect("Failed to serialize ScrollBehavior")
);
let fut = self
.query
.new_query::<bool>(&script, &self.webview)
.resolve();
Box::pin(async move {
match fut.await {
Ok(true) => Ok(()),
Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(
Box::new(DesktopQueryError::FailedToQuery),
)),
Err(err) => {
MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))
}
}
})
}
fn set_focus(
&self,
focus: bool,
) -> std::pin::Pin<Box<dyn futures_util::Future<Output = dioxus_html::MountedResult<()>>>> {
let script = format!(
"return window.interpreter.SetFocus({}, {});",
self.id.0, focus
);
let fut = self
.query
.new_query::<bool>(&script, &self.webview)
.resolve();
Box::pin(async move {
match fut.await {
Ok(true) => Ok(()),
Ok(false) => MountedResult::Err(dioxus_html::MountedError::OperationFailed(
Box::new(DesktopQueryError::FailedToQuery),
)),
Err(err) => {
MountedResult::Err(dioxus_html::MountedError::OperationFailed(Box::new(err)))
}
}
})
}
}
#[derive(Debug)]
enum DesktopQueryError {
FailedToQuery,
}
impl std::fmt::Display for DesktopQueryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DesktopQueryError::FailedToQuery => write!(f, "Failed to query the element"),
}
}
}
impl std::error::Error for DesktopQueryError {}
+9 -13
View File
@@ -1,36 +1,32 @@
use std::rc::Rc;
use crate::query::Query;
use crate::query::QueryError;
use crate::use_window;
use dioxus_core::ScopeState;
use serde::de::Error;
use std::future::Future;
use std::future::IntoFuture;
use std::pin::Pin;
/// A future that resolves to the result of a JavaScript evaluation.
pub struct EvalResult {
pub(crate) broadcast: tokio::sync::broadcast::Sender<serde_json::Value>,
pub(crate) query: Query<serde_json::Value>,
}
impl EvalResult {
pub(crate) fn new(sender: tokio::sync::broadcast::Sender<serde_json::Value>) -> Self {
Self { broadcast: sender }
pub(crate) fn new(query: Query<serde_json::Value>) -> Self {
Self { query }
}
}
impl IntoFuture for EvalResult {
type Output = Result<serde_json::Value, serde_json::Error>;
type Output = Result<serde_json::Value, QueryError>;
type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>;
type IntoFuture = Pin<Box<dyn Future<Output = Result<serde_json::Value, QueryError>>>>;
fn into_future(self) -> Self::IntoFuture {
Box::pin(async move {
let mut reciever = self.broadcast.subscribe();
match reciever.recv().await {
Ok(result) => Ok(result),
Err(_) => Err(serde_json::Error::custom("No result returned")),
}
}) as Pin<Box<dyn Future<Output = Result<serde_json::Value, serde_json::Error>>>>
Box::pin(self.query.resolve())
as Pin<Box<dyn Future<Output = Result<serde_json::Value, QueryError>>>>
}
}
+49 -18
View File
@@ -5,22 +5,27 @@
mod cfg;
mod desktop_context;
mod element;
mod escape;
mod eval;
mod events;
mod file_upload;
mod protocol;
mod query;
mod shortcut;
mod waker;
mod webview;
use crate::query::QueryResult;
pub use cfg::Config;
pub use desktop_context::{
use_window, use_wry_event_handler, DesktopContext, WryEventHandler, WryEventHandlerId,
};
use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
use dioxus_core::*;
use dioxus_html::MountedData;
use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
use element::DesktopElement;
pub use eval::{use_eval, EvalResult};
use futures_util::{pin_mut, FutureExt};
use shortcut::ShortcutRegistry;
@@ -221,39 +226,65 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
}
EventData::Ipc(msg) if msg.method() == "user_event" => {
let evt = match serde_json::from_value::<HtmlEvent>(msg.params()) {
let params = msg.params();
let evt = match serde_json::from_value::<HtmlEvent>(params) {
Ok(value) => value,
Err(_) => return,
};
let HtmlEvent {
element,
name,
bubbles,
data,
} = evt;
let view = webviews.get_mut(&event.1).unwrap();
view.dom
.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles);
// check for a mounted event placeholder and replace it with a desktop specific element
let as_any = if let dioxus_html::EventData::Mounted = &data {
let query = view
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query;
let element = DesktopElement::new(element, view.webview.clone(), query);
Rc::new(MountedData::new(element))
} else {
data.into_any()
};
view.dom.handle_event(&name, as_any, element, bubbles);
send_edits(view.dom.render_immediate(), &view.webview);
}
// When the webview sends a query, we need to send it to the query manager which handles dispatching the data to the correct pending query
EventData::Ipc(msg) if msg.method() == "query" => {
let params = msg.params();
if let Ok(result) = serde_json::from_value::<QueryResult>(params) {
let view = webviews.get(&event.1).unwrap();
let query = view
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.query;
query.send(result);
}
}
EventData::Ipc(msg) if msg.method() == "initialize" => {
let view = webviews.get_mut(&event.1).unwrap();
send_edits(view.dom.rebuild(), &view.webview);
}
// When the webview chirps back with the result of the eval, we send it to the active receiver
//
// This currently doesn't perform any targeting to the callsite, so if you eval multiple times at once,
// you might the wrong result. This should be fixed
EventData::Ipc(msg) if msg.method() == "eval_result" => {
webviews[&event.1]
.dom
.base_scope()
.consume_context::<DesktopContext>()
.unwrap()
.eval
.send(msg.params())
.unwrap();
}
EventData::Ipc(msg) if msg.method() == "browser_open" => {
if let Some(temp) = msg.params().as_object() {
if temp.contains_key("href") {
+110
View File
@@ -0,0 +1,110 @@
use std::{cell::RefCell, rc::Rc};
use serde::{de::DeserializeOwned, Deserialize};
use serde_json::Value;
use slab::Slab;
use thiserror::Error;
use tokio::sync::broadcast::error::RecvError;
use wry::webview::WebView;
/// Tracks what query ids are currently active
#[derive(Default, Clone)]
struct SharedSlab {
slab: Rc<RefCell<Slab<()>>>,
}
/// Handles sending and receiving arbitrary queries from the webview. Queries can be resolved non-sequentially, so we use ids to track them.
#[derive(Clone)]
pub(crate) struct QueryEngine {
sender: Rc<tokio::sync::broadcast::Sender<QueryResult>>,
active_requests: SharedSlab,
}
impl Default for QueryEngine {
fn default() -> Self {
let (sender, _) = tokio::sync::broadcast::channel(8);
Self {
sender: Rc::new(sender),
active_requests: SharedSlab::default(),
}
}
}
impl QueryEngine {
/// Creates a new query and returns a handle to it. The query will be resolved when the webview returns a result with the same id.
pub fn new_query<V: DeserializeOwned>(&self, script: &str, webview: &WebView) -> Query<V> {
let request_id = self.active_requests.slab.borrow_mut().insert(());
// start the query
// We embed the return of the eval in a function so we can send it back to the main thread
if let Err(err) = webview.evaluate_script(&format!(
r#"window.ipc.postMessage(
JSON.stringify({{
"method":"query",
"params": {{
"id": {request_id},
"data": (function(){{{script}}})()
}}
}})
);"#
)) {
log::warn!("Query error: {err}");
}
Query {
slab: self.active_requests.clone(),
id: request_id,
reciever: self.sender.subscribe(),
phantom: std::marker::PhantomData,
}
}
/// Send a query result
pub fn send(&self, data: QueryResult) {
let _ = self.sender.send(data);
}
}
pub(crate) struct Query<V: DeserializeOwned> {
slab: SharedSlab,
id: usize,
reciever: tokio::sync::broadcast::Receiver<QueryResult>,
phantom: std::marker::PhantomData<V>,
}
impl<V: DeserializeOwned> Query<V> {
/// Resolve the query
pub async fn resolve(mut self) -> Result<V, QueryError> {
let result = loop {
match self.reciever.recv().await {
Ok(result) => {
if result.id == self.id {
break V::deserialize(result.data).map_err(QueryError::DeserializeError);
}
}
Err(err) => {
break Err(QueryError::RecvError(err));
}
}
};
// Remove the query from the slab
self.slab.slab.borrow_mut().remove(self.id);
result
}
}
#[derive(Error, Debug)]
pub enum QueryError {
#[error("Error receiving query result: {0}")]
RecvError(RecvError),
#[error("Error deserializing query result: {0}")]
DeserializeError(serde_json::Error),
}
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct QueryResult {
id: usize,
data: Value,
}