Skip to content

Commit c28d83e

Browse files
committed
Fix cancellable methods in perspective-viewer
Signed-off-by: Andrew Stein <steinlink@gmail.com>
1 parent 47f6cbe commit c28d83e

File tree

13 files changed

+193
-39
lines changed

13 files changed

+193
-39
lines changed

cpp/perspective/src/cpp/server.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,10 @@ ServerResources::get_table_for_view(const t_id& view_id) {
423423
ServerResources::t_id
424424
ServerResources::get_table_id_for_view(const t_id& view_id) {
425425
PSP_READ_LOCK(m_write_lock);
426+
if (!m_view_to_table.contains(view_id)) {
427+
throw PerspectiveViewNotFoundException();
428+
}
429+
426430
return m_view_to_table.at(view_id);
427431
}
428432

@@ -440,6 +444,10 @@ ServerResources::get_view_ids(const t_id& table_id) {
440444
std::shared_ptr<ErasedView>
441445
ServerResources::get_view(const t_id& id) {
442446
PSP_READ_LOCK(m_write_lock);
447+
if (!m_views.contains(id)) {
448+
throw PerspectiveViewNotFoundException();
449+
}
450+
443451
return m_views.at(id);
444452
}
445453

@@ -680,6 +688,13 @@ ProtoServer::handle_request(
680688
auto* err = resp.mutable_server_error()->mutable_message();
681689
*err = std::string(e.what());
682690
responses.emplace_back(std::move(resp));
691+
} catch (const PerspectiveViewNotFoundException& e) {
692+
proto::Response resp;
693+
auto* err = resp.mutable_server_error();
694+
err->set_status_code(proto::StatusCode::VIEW_NOT_FOUND);
695+
auto* msg = err->mutable_message();
696+
*msg = std::string(e.what());
697+
responses.emplace_back(std::move(resp));
683698
} catch (const std::exception& e) {
684699
proto::Response resp;
685700
auto* err = resp.mutable_server_error()->mutable_message();

cpp/perspective/src/include/perspective/exception.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,16 @@ class PERSPECTIVE_EXPORT PerspectiveException : public std::exception {
2828
std::string message;
2929
};
3030

31+
class PERSPECTIVE_EXPORT PerspectiveViewNotFoundException
32+
: public std::exception {
33+
public:
34+
explicit PerspectiveViewNotFoundException() {}
35+
36+
[[nodiscard]]
37+
const char*
38+
what() const noexcept override {
39+
return "View not found";
40+
}
41+
};
42+
3143
} // namespace perspective

cpp/protos/perspective.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@ option optimize_for = LITE_RUNTIME;
2222
//
2323
// Common
2424

25+
enum StatusCode {
26+
SERVER_ERROR = 0;
27+
VIEW_NOT_FOUND = 1;
28+
}
29+
2530
// Recoverable, user-readable error reporting from the engine.
2631
message ServerError {
2732
string message = 1;
33+
StatusCode status_code = 2;
2834
}
2935

3036
message Schema {

rust/perspective-client/src/rust/utils/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ use crate::proto;
2222

2323
#[derive(Error, Debug)]
2424
pub enum ClientError {
25+
#[error("View not found")]
26+
ViewNotFound,
27+
2528
#[error("Abort(): {0}")]
2629
Internal(String),
2730

@@ -64,7 +67,10 @@ pub type ClientResult<T> = Result<T, ClientError>;
6467
impl From<proto::response::ClientResp> for ClientError {
6568
fn from(value: proto::response::ClientResp) -> Self {
6669
match value {
67-
proto::response::ClientResp::ServerError(x) => ClientError::Internal(x.message),
70+
proto::response::ClientResp::ServerError(x) => match x.status_code() {
71+
proto::StatusCode::ServerError => ClientError::Internal(x.message),
72+
proto::StatusCode::ViewNotFound => ClientError::ViewNotFound,
73+
},
6874
x => ClientError::ResponseFailed(Box::new(x)),
6975
}
7076
}

rust/perspective-js/src/rust/table.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl Table {
186186

187187
#[doc = inherit_docs!("table/delete.md")]
188188
#[wasm_bindgen]
189-
pub async fn delete(self) -> ApiResult<()> {
189+
pub async fn delete(&self) -> ApiResult<()> {
190190
self.0.delete().await?;
191191
Ok(())
192192
}

rust/perspective-js/src/rust/utils/errors.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
use std::fmt::Display;
1414

15+
use perspective_client::ClientError;
1516
use wasm_bindgen::prelude::*;
1617

1718
/// A bespoke error class for chaining a litany of various error types with the
@@ -78,7 +79,6 @@ define_api_error!(
7879
serde_json::Error,
7980
rmp_serde::encode::Error,
8081
rmp_serde::decode::Error,
81-
perspective_client::ClientError,
8282
&str,
8383
String,
8484
futures::channel::oneshot::Canceled,
@@ -87,6 +87,16 @@ define_api_error!(
8787
prost::DecodeError
8888
);
8989

90+
#[wasm_bindgen(inline_js = r#"
91+
export class PerspectiveViewNotFoundError extends Error {}
92+
"#)]
93+
extern "C" {
94+
pub type PerspectiveViewNotFoundError;
95+
96+
#[wasm_bindgen(constructor)]
97+
fn new() -> PerspectiveViewNotFoundError;
98+
}
99+
90100
/// Explicit conversion methods for `ApiResult<T>`, for situations where
91101
/// error-casting through the `?` operator is insufficient.
92102
pub trait ToApiError<T> {
@@ -105,3 +115,12 @@ impl ToApiError<JsValue> for Result<(), ApiResult<JsValue>> {
105115
self.map_or_else(|x| x, |()| Ok(JsValue::UNDEFINED))
106116
}
107117
}
118+
119+
impl From<perspective_client::ClientError> for ApiError {
120+
fn from(value: ClientError) -> Self {
121+
match value {
122+
ClientError::ViewNotFound => ApiError(PerspectiveViewNotFoundError::new().into()),
123+
err => ApiError(JsError::new(format!("{}", err).as_str()).into()),
124+
}
125+
}
126+
}

rust/perspective-js/src/rust/utils/futures.rs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,11 @@ where
8484
{
8585
fn from(fut: ApiFuture<T>) -> Self {
8686
future_to_promise(async move {
87-
let x = Ok(fut.0.await?);
88-
let y = Ok(x.into_js_result()?);
89-
Ok(y.ignore_view_delete()?)
87+
fut.0
88+
.await
89+
.map_err(|x| x.into())
90+
.into_js_result()
91+
.ignore_view_delete()
9092
})
9193
}
9294
}
@@ -146,7 +148,7 @@ where
146148
static CANCELLED_MSG: &str = "View method cancelled";
147149

148150
#[extend::ext]
149-
pub impl Result<JsValue, ApiError> {
151+
pub impl Result<JsValue, JsValue> {
150152
/// Wraps an error `JsValue` return from a caught JavaScript exception,
151153
/// checking for the explicit error type indicating that a
152154
/// `JsPerspectiveView` call has been cancelled due to it already being
@@ -160,35 +162,22 @@ pub impl Result<JsValue, ApiError> {
160162
/// silently be replaced with `Ok`. The message itself is returned in this
161163
/// case (instead of whatever the `async` returns), which is helpful for
162164
/// detecting this condition when debugging.
165+
fn ignore_view_delete(self) -> Result<JsValue, JsValue> {
166+
self.or_else(|x| match x.dyn_ref::<PerspectiveViewNotFoundError>() {
167+
Some(_) => Ok(js_intern::js_intern!(CANCELLED_MSG).clone()),
168+
None => Err(x),
169+
})
170+
}
171+
}
172+
173+
#[extend::ext]
174+
pub impl Result<JsValue, ApiError> {
163175
fn ignore_view_delete(self) -> Result<JsValue, ApiError> {
164176
self.or_else(|x| {
165177
let f: JsValue = x.clone().into();
166-
match f.dyn_ref::<js_sys::Error>() {
167-
Some(err) => {
168-
if err.message() != CANCELLED_MSG {
169-
Err(x)
170-
} else {
171-
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
172-
}
173-
},
174-
_ => match f.as_string() {
175-
Some(x) if x == CANCELLED_MSG => {
176-
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
177-
},
178-
Some(_) => Err(x),
179-
_ => {
180-
if js_sys::Reflect::get(&f, js_intern::js_intern!("message"))
181-
.unwrap()
182-
.as_string()
183-
.unwrap_or_else(|| "".to_owned())
184-
== CANCELLED_MSG
185-
{
186-
Ok(js_intern::js_intern!(CANCELLED_MSG).clone())
187-
} else {
188-
Err(x)
189-
}
190-
},
191-
},
178+
match f.dyn_ref::<PerspectiveViewNotFoundError>() {
179+
Some(_) => Ok(js_intern::js_intern!(CANCELLED_MSG).clone()),
180+
None => Err(x),
192181
}
193182
})
194183
}

rust/perspective-js/src/rust/view.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ impl View {
7070

7171
#[doc = inherit_docs!("view/delete.md")]
7272
#[wasm_bindgen]
73-
pub async fn delete(self) -> ApiResult<()> {
73+
pub async fn delete(&self) -> ApiResult<()> {
7474
self.0.delete().await?;
7575
Ok(())
7676
}

rust/perspective-viewer/src/rust/custom_elements/debug_plugin.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,18 @@ impl PerspectiveDebugPluginElement {
6565
JsValue::UNDEFINED
6666
}
6767

68-
pub fn update(&self, view: perspective_js::View) -> ApiFuture<()> {
68+
pub fn update(&self, view: &perspective_js::View) -> ApiFuture<()> {
6969
self.draw(view)
7070
}
7171

72-
pub fn draw(&self, view: perspective_js::View) -> ApiFuture<()> {
72+
/// # Notes
73+
///
74+
/// When you pass a `wasm_bindgen` wrapped type _into_ Rust, it acts like a
75+
/// move. Ergo, if you replace the `&` in the `view` argument, the JS copy
76+
/// of the `View` will be invalid
77+
pub fn draw(&self, view: &perspective_js::View) -> ApiFuture<()> {
7378
let css = "margin:0;overflow:scroll;position:absolute;width:100%;height:100%";
74-
clone!(self.elem);
79+
clone!(self.elem, view);
7580
ApiFuture::new(async move {
7681
let csv = view.to_csv(None).await?;
7782
elem.style().set_property("background-color", "#fff")?;

rust/perspective-viewer/src/ts/plugin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export interface IPerspectiveViewerPlugin {
122122
* }
123123
* ```
124124
*/
125-
// draw(view: perspective.View): Promise<void>;
125+
draw(view: View): Promise<void>;
126126

127127
/**
128128
* Draw under the assumption that the `ViewConfig` has not changed since
@@ -136,7 +136,7 @@ export interface IPerspectiveViewerPlugin {
136136
* }
137137
* ```
138138
*/
139-
// update(view: perspective.View): Promise<void>;
139+
update(view: View): Promise<void>;
140140

141141
/**
142142
* Clear this plugin, though it is up to the discretion of the plugin

0 commit comments

Comments
 (0)