@@ -4,6 +4,7 @@ use enclose::enclose;
44use futures:: { future, try_join, FutureExt , StreamExt } ;
55use gloo_utils:: format:: JsValueSerdeExt ;
66use once_cell:: sync:: Lazy ;
7+ use serde:: Serialize ;
78use tracing:: { error, info, Level } ;
89use tracing_wasm:: WASMLayerConfigBuilder ;
910use wasm_bindgen:: { prelude:: wasm_bindgen, JsValue , UnwrapThrowExt } ;
@@ -38,6 +39,47 @@ use crate::{
3839static 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) ]
4284pub 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