Skip to content

Commit 2efe98d

Browse files
authored
Merge pull request #900 from Stremio/feat/stremio-core-web-bridge-panicking
feat(core-web): dispatch & get_state return error instead of panicking
2 parents 1465103 + 9b291b2 commit 2efe98d

File tree

1 file changed

+116
-26
lines changed

1 file changed

+116
-26
lines changed

stremio-core-web/src/stremio_core_web.rs

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use enclose::enclose;
44
use futures::{future, try_join, FutureExt, StreamExt};
55
use gloo_utils::format::JsValueSerdeExt;
66
use once_cell::sync::Lazy;
7+
use serde::Serialize;
78
use tracing::{error, info, Level};
89
use tracing_wasm::WASMLayerConfigBuilder;
910
use wasm_bindgen::{prelude::wasm_bindgen, JsValue, UnwrapThrowExt};
@@ -38,6 +39,47 @@ use crate::{
3839
static RUNTIME: Lazy<RwLock<Option<Loadable<Runtime<WebEnv, WebModel>, EnvError>>>> =
3940
Lazy::new(Default::default);
4041

42+
#[derive(Debug, Clone, Serialize)]
43+
pub enum DispatchError {
44+
#[serde(rename_all = "camelCase")]
45+
Action { error: String, json: String },
46+
#[serde(rename_all = "camelCase")]
47+
Field { error: String, json: Option<String> },
48+
#[serde(rename_all = "camelCase")]
49+
State(State),
50+
}
51+
52+
impl From<DispatchError> for JsValue {
53+
fn from(value: DispatchError) -> Self {
54+
<JsValue as JsValueSerdeExt>::from_serde(&value).expect("JsValue from DispatchError")
55+
}
56+
}
57+
#[derive(Debug, Clone, Serialize)]
58+
#[serde(rename_all = "camelCase")]
59+
pub enum GetStateError {
60+
#[serde(rename_all = "camelCase")]
61+
Field { error: String, json: Option<String> },
62+
#[serde(rename_all = "camelCase")]
63+
State(State),
64+
}
65+
66+
impl From<GetStateError> for JsValue {
67+
fn from(value: GetStateError) -> Self {
68+
<JsValue as JsValueSerdeExt>::from_serde(&value).expect("JsValue from GetStateError")
69+
}
70+
}
71+
72+
#[derive(Debug, Clone, Serialize)]
73+
#[serde(rename_all = "camelCase")]
74+
pub enum State {
75+
/// No runtime has been set yet.
76+
/// Please, initialize core first!
77+
RuntimeNotSet,
78+
/// Runtime is Loading or Error
79+
RuntimeNotReady,
80+
ModelReadFailed,
81+
}
82+
4183
#[wasm_bindgen(start)]
4284
pub fn start() {
4385
// print pretty errors in wasm https://github.com/rustwasm/console_error_panic_hook
@@ -180,49 +222,85 @@ pub fn get_debug_state() -> JsValue {
180222
}
181223

182224
#[wasm_bindgen]
183-
pub fn get_state(field: JsValue) -> JsValue {
184-
let field = JsValueSerdeExt::into_serde(&field).expect("get state failed");
225+
pub fn get_state(field: JsValue) -> Result<JsValue, JsValue> {
226+
let field_json = stringify(&field);
227+
228+
let field: WebModelField =
229+
JsValueSerdeExt::into_serde(&field).map_err(|err| GetStateError::Field {
230+
error: err.to_string(),
231+
json: field_json,
232+
})?;
185233
let runtime = RUNTIME.read().expect("runtime read failed");
186234
let runtime = runtime
187235
.as_ref()
188-
.expect("runtime is not ready")
189-
.as_ref()
190-
.expect("runtime is not ready");
191-
let model = runtime.model().expect("model read failed");
192-
model.get_state(&field)
236+
.ok_or(GetStateError::State(State::RuntimeNotSet))?
237+
.ready()
238+
.ok_or(GetStateError::State(State::RuntimeNotReady))?;
239+
240+
let model = runtime
241+
.model()
242+
.map_err(|_| GetStateError::State(State::ModelReadFailed))?;
243+
Ok(model.get_state(&field))
193244
}
194245

195246
#[wasm_bindgen]
196-
pub fn dispatch(action: JsValue, field: JsValue, location_hash: JsValue) {
197-
let action_json = js_sys::JSON::stringify(&action)
198-
.map(String::from)
199-
.unwrap_throw();
200-
let action: Action = JsValueSerdeExt::into_serde(&action)
201-
.inspect_err(|err| {
202-
error!(action_json, "dispatch failed because of Action: {err}");
203-
})
204-
.expect("dispatch failed because of Action");
247+
pub fn dispatch(action: JsValue, field: JsValue, location_hash: JsValue) -> Result<(), JsValue> {
248+
let action_json = stringify(&action).unwrap_or_default();
249+
250+
let field_json = stringify(&field);
251+
252+
let action: Action =
253+
JsValueSerdeExt::into_serde(&action).map_err(|err| DispatchError::Action {
254+
error: err.to_string(),
255+
json: action_json,
256+
})?;
205257
let field: Option<WebModelField> =
206-
JsValueSerdeExt::into_serde(&field).expect("dispatch failed because of Field");
258+
JsValueSerdeExt::into_serde(&field).map_err(|err| DispatchError::Field {
259+
error: err.to_string(),
260+
json: field_json,
261+
})?;
262+
263+
let runtime_action: RuntimeAction<WebEnv, WebModel> = RuntimeAction { action, field };
264+
let location_hash = location_hash.as_string();
265+
Ok(
266+
dispatch_internal(runtime_action, location_hash).map_err(|state_err| {
267+
error!(?state_err, "Failed to dispatch action due to");
268+
269+
DispatchError::State(state_err)
270+
})?,
271+
)
272+
}
273+
274+
fn dispatch_internal(
275+
runtime_action: RuntimeAction<WebEnv, WebModel>,
276+
location_hash: Option<String>,
277+
) -> Result<(), State> {
207278
let runtime = RUNTIME.read().expect("runtime read failed");
208-
let runtime = runtime
209-
.as_ref()
210-
.expect("runtime is not ready - None")
211-
.as_ref()
212-
.expect("runtime is not ready - Loading or Error");
279+
let runtime = runtime.as_ref().ok_or(State::RuntimeNotSet)?;
280+
281+
let runtime = runtime.ready().ok_or(State::RuntimeNotReady)?;
282+
213283
{
214-
let model = runtime.model().expect("model read failed");
284+
let model = runtime.model().map_err(|_| State::ModelReadFailed)?;
215285
let path = location_hash
216-
.as_string()
217286
.and_then(|location_hash| location_hash.split('#').last().map(|path| path.to_owned()))
218287
.unwrap_or_default();
219288
WebEnv::emit_to_analytics(
220-
&WebEvent::CoreAction(Box::new(action.to_owned())),
289+
&WebEvent::CoreAction(Box::new(runtime_action.action.to_owned())),
221290
&model,
222291
&path,
223292
);
224293
}
225-
runtime.dispatch(RuntimeAction { action, field });
294+
295+
// if you need to add the tracing (as it's a bit verbose)
296+
// let runtime_action_clone: RuntimeAction<WebEnv, WebModel> = RuntimeAction {
297+
// action: runtime_action.action.clone(),
298+
// field: runtime_action.field.clone(),
299+
// };
300+
let _result = runtime.dispatch(runtime_action);
301+
// tracing::trace!(?runtime_action_clone, ?_result, "Runtime action dispatched");
302+
303+
Ok(())
226304
}
227305

228306
#[wasm_bindgen]
@@ -255,3 +333,15 @@ pub fn decode_stream(stream: JsValue) -> JsValue {
255333
_ => JsValue::NULL,
256334
}
257335
}
336+
337+
pub fn stringify(js_value: &JsValue) -> Option<String> {
338+
if js_value.is_undefined() {
339+
None
340+
} else {
341+
Some(
342+
js_sys::JSON::stringify(js_value)
343+
.map(String::from)
344+
.unwrap_throw(),
345+
)
346+
}
347+
}

0 commit comments

Comments
 (0)