Skip to content

Commit

Permalink
feat: made plugin actions return Result
Browse files Browse the repository at this point in the history
Errors on the engine-side are automatically handled and returned through
the CLI, while those on the browser-side are handled through a single
`panic!` that will go to the user's defined browser-side panic handler

Closes #234.

BREAKING CHANGE: all plugin actions must now return `Result<T, Box<dyn
std::error::Error>>` (return some arbitrary error and then use
`.into()`)
BREAKING CHANGE: all plugin actions that previously took
`Rc<perseus::errors::EngineError>` now take `Rc<perseus::errors::Error>`
  • Loading branch information
arctic-hen7 committed Nov 26, 2022
1 parent 9c3444a commit 42f0d99
Show file tree
Hide file tree
Showing 13 changed files with 268 additions and 139 deletions.
7 changes: 4 additions & 3 deletions examples/core/plugins/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ pub fn get_test_plugin<G: perseus::Html>() -> Plugin<G, TestPluginData> {
.register_plugin("test-plugin", |_, _| {
let mut map = std::collections::HashMap::new();
map.insert("/Cargo.toml".to_string(), "Cargo.toml".to_string());
map
Ok(map)
});
actions.settings_actions.add_templates.register_plugin(
"test-plugin",
|_, plugin_data| {
if let Some(plugin_data) = plugin_data.downcast_ref::<TestPluginData>() {
let about_page_greeting = plugin_data.about_page_greeting.to_string();
vec![Template::new("about").template(move |cx| {
Ok(vec![Template::new("about").template(move |cx| {
sycamore::view! { cx, p { (about_page_greeting) } }
})]
})])
} else {
unreachable!()
}
Expand All @@ -38,6 +38,7 @@ pub fn get_test_plugin<G: perseus::Html>() -> Plugin<G, TestPluginData> {
let test = "[package]\name = \"test\"";
let parsed: toml::Value = toml::from_str(test).unwrap();
println!("{}", toml::to_string(&parsed).unwrap());
Ok(())
});
actions
},
Expand Down
28 changes: 23 additions & 5 deletions packages/perseus/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::errors::PluginError;
use crate::{
checkpoint,
plugins::PluginAction,
router::{perseus_router, PerseusRouterProps},
template::TemplateNodeType,
};
use crate::{i18n::TranslationsManager, stores::MutableStore, PerseusAppBase};
use fmterr::fmt_err;
use std::collections::HashMap;
use wasm_bindgen::JsValue;

Expand All @@ -22,7 +24,6 @@ pub fn run_client<M: MutableStore, T: TranslationsManager>(
app: impl Fn() -> PerseusAppBase<TemplateNodeType, M, T>,
) -> Result<(), JsValue> {
let mut app = app();
let plugins = app.get_plugins();
let panic_handler = app.take_panic_handler();

checkpoint("begin");
Expand All @@ -38,27 +39,44 @@ pub fn run_client<M: MutableStore, T: TranslationsManager>(
}
}));

let res = client_core(app);
if let Err(err) = res {
// This will go to the panic handler we defined above
// Unfortunately, at this stage, we really can't do anything else
panic!("plugin error: {}", fmt_err(&err));
}

Ok(())
}

/// This executes the actual underlying browser-side logic, including
/// instantiating the user's app. This is broken out due to plugin fallibility.
fn client_core<M: MutableStore, T: TranslationsManager>(
app: PerseusAppBase<TemplateNodeType, M, T>,
) -> Result<(), PluginError> {
let plugins = app.get_plugins();

plugins
.functional_actions
.client_actions
.start
.run((), plugins.get_plugin_data());
.run((), plugins.get_plugin_data())?;
checkpoint("initial_plugins_complete");

// Get the root we'll be injecting the router into
let root = web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector(&format!("#{}", app.get_root()))
.query_selector(&format!("#{}", app.get_root()?))
.unwrap()
.unwrap();

// Set up the properties we'll pass to the router
let router_props = PerseusRouterProps {
locales: app.get_locales(),
locales: app.get_locales()?,
error_pages: app.get_error_pages(),
templates: app.get_templates_map(),
templates: app.get_templates_map()?,
render_cfg: get_render_cfg().expect("render configuration invalid or not injected"),
pss_max_size: app.get_pss_max_size(),
};
Expand Down
34 changes: 20 additions & 14 deletions packages/perseus/src/engine/build.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use crate::build::{build_app, BuildProps};
use crate::errors::Error;
use crate::{
errors::{EngineError, ServerError},
i18n::TranslationsManager,
plugins::PluginAction,
stores::MutableStore,
errors::ServerError, i18n::TranslationsManager, plugins::PluginAction, stores::MutableStore,
PerseusAppBase, SsrNode,
};
use std::rc::Rc;
Expand All @@ -15,37 +13,43 @@ use std::rc::Rc;
/// Note that this expects to be run in the root of the project.
pub async fn build<M: MutableStore, T: TranslationsManager>(
app: PerseusAppBase<SsrNode, M, T>,
) -> Result<(), Rc<EngineError>> {
) -> Result<(), Rc<Error>> {
let plugins = app.get_plugins();

plugins
.functional_actions
.build_actions
.before_build
.run((), plugins.get_plugin_data());
.run((), plugins.get_plugin_data())
.map_err(|err| Rc::new(err.into()))?;

let immutable_store = app.get_immutable_store();
let immutable_store = app
.get_immutable_store()
.map_err(|err| Rc::new(err.into()))?;
let mutable_store = app.get_mutable_store();
let locales = app.get_locales();
let locales = app.get_locales().map_err(|err| Rc::new(err.into()))?;
// Generate the global state
let gsc = app.get_global_state_creator();
let global_state = match gsc.get_build_state().await {
Ok(global_state) => global_state,
Err(err) => {
let err: Rc<EngineError> = Rc::new(ServerError::GlobalStateError(err).into());
let err: Rc<Error> = Rc::new(ServerError::GlobalStateError(err).into());
plugins
.functional_actions
.build_actions
.after_failed_global_state_creation
.run(err.clone(), plugins.get_plugin_data());
.run(err.clone(), plugins.get_plugin_data())
.map_err(|err| Rc::new(err.into()))?;
return Err(err);
}
};

// Build the site for all the common locales (done in parallel)
// All these parameters can be modified by `PerseusApp` and plugins, so there's
// no point in having a plugin opportunity here
let templates_map = app.get_atomic_templates_map();
let templates_map = app
.get_atomic_templates_map()
.map_err(|err| Rc::new(err.into()))?;

// We have to get the translations manager last, because it consumes everything
let translations_manager = app.get_translations_manager().await;
Expand All @@ -61,20 +65,22 @@ pub async fn build<M: MutableStore, T: TranslationsManager>(
})
.await;
if let Err(err) = res {
let err: Rc<EngineError> = Rc::new(err.into());
let err: Rc<Error> = Rc::new(err.into());
plugins
.functional_actions
.build_actions
.after_failed_build
.run(err.clone(), plugins.get_plugin_data());
.run(err.clone(), plugins.get_plugin_data())
.map_err(|err| Rc::new(err.into()))?;

Err(err)
} else {
plugins
.functional_actions
.build_actions
.after_successful_build
.run((), plugins.get_plugin_data());
.run((), plugins.get_plugin_data())
.map_err(|err| Rc::new(err.into()))?;

Ok(())
}
Expand Down
20 changes: 14 additions & 6 deletions packages/perseus/src/engine/dflt_engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,13 @@ where
EngineOperation::Serve => {
// To reduce friction for default servers and user-made servers, we
// automatically do the boilerplate that all servers would have to do
let props = get_props(app());
let props = match get_props(app()) {
Ok(props) => props,
Err(err) => {
eprintln!("{}", fmt_err(&err));
return 1;
}
};
// This returns a `(String, u16)` of the host and port for maximum compatibility
let addr = get_host_and_port();
// In production, give the user a heads up that something's actually happening
Expand All @@ -123,10 +129,12 @@ where
serve_fn(props, addr).await;
0
}
EngineOperation::Tinker => {
// This is infallible (though plugins could panic)
super::engine_tinker(app());
0
}
EngineOperation::Tinker => match super::engine_tinker(app()) {
Ok(_) => 0,
Err(err) => {
eprintln!("{}", fmt_err(&err));
1
}
},
}
}
Loading

0 comments on commit 42f0d99

Please sign in to comment.