diff --git a/examples/showcase/app/src/pages/index.rs b/examples/showcase/app/src/pages/index.rs index d23f66ee10..c6d440ff0b 100644 --- a/examples/showcase/app/src/pages/index.rs +++ b/examples/showcase/app/src/pages/index.rs @@ -21,12 +21,12 @@ pub fn get_page() -> Template { .template(template_fn()) } -pub fn get_static_props(_path: String) -> String { - serde_json::to_string( +pub fn get_static_props(_path: String) -> Result { + Ok(serde_json::to_string( &IndexPageProps { greeting: "Hello World!".to_string() } - ).unwrap() + ).unwrap()) } pub fn template_fn() -> perseus::template::TemplateFn { diff --git a/examples/showcase/app/src/pages/ip.rs b/examples/showcase/app/src/pages/ip.rs index 4cc840c1ac..f6c270e260 100644 --- a/examples/showcase/app/src/pages/ip.rs +++ b/examples/showcase/app/src/pages/ip.rs @@ -26,12 +26,12 @@ pub fn get_page() -> Template { .template(template_fn()) } -pub fn get_request_state(_path: String) -> String { - serde_json::to_string( +pub fn get_request_state(_path: String) -> Result { + Ok(serde_json::to_string( &IpPageProps { ip: "x.x.x.x".to_string() } - ).unwrap() + ).unwrap()) } pub fn template_fn() -> perseus::template::TemplateFn { diff --git a/examples/showcase/app/src/pages/post.rs b/examples/showcase/app/src/pages/post.rs index 1427695011..cc9e9d0efc 100644 --- a/examples/showcase/app/src/pages/post.rs +++ b/examples/showcase/app/src/pages/post.rs @@ -30,25 +30,25 @@ pub fn get_page() -> Template { .template(template_fn()) } -pub fn get_static_props(path: String) -> String { +pub fn get_static_props(path: String) -> Result { let path_vec: Vec<&str> = path.split('/').collect(); let title_slug = path_vec[path_vec.len() - 1]; // This is just an example let title = urlencoding::decode(title_slug).unwrap(); let content = format!("This is a post entitled '{}'. Its original slug was '{}'.", title, title_slug); - serde_json::to_string( + Ok(serde_json::to_string( &PostPageProps { title: title.to_string(), content } - ).unwrap() + ).unwrap()) } // TODO -pub fn get_static_paths() -> Vec { - vec![ +pub fn get_static_paths() -> Result, String> { + Ok(vec![ "test".to_string() - ] + ]) } pub fn template_fn() -> perseus::template::TemplateFn { diff --git a/examples/showcase/app/src/pages/time.rs b/examples/showcase/app/src/pages/time.rs index 5599feb7a7..e20ba7b78a 100644 --- a/examples/showcase/app/src/pages/time.rs +++ b/examples/showcase/app/src/pages/time.rs @@ -24,18 +24,18 @@ pub fn get_page() -> Template { .build_paths_fn(Box::new(get_build_paths)) } -pub fn get_build_state(_path: String) -> String { - serde_json::to_string( +pub fn get_build_state(_path: String) -> Result { + Ok(serde_json::to_string( &TimePageProps { time: format!("{:?}", std::time::SystemTime::now()) } - ).unwrap() + ).unwrap()) } -pub fn get_build_paths() -> Vec { - vec![ +pub fn get_build_paths() -> Result, String> { + Ok(vec![ "test".to_string() - ] + ]) } pub fn template_fn() -> perseus::template::TemplateFn { diff --git a/examples/showcase/app/src/pages/time_root.rs b/examples/showcase/app/src/pages/time_root.rs index 7fdfe3e273..d2ddcc84fb 100644 --- a/examples/showcase/app/src/pages/time_root.rs +++ b/examples/showcase/app/src/pages/time_root.rs @@ -21,17 +21,17 @@ pub fn get_page() -> Template { // Try changing this to a week, even though the below custom logic says to always revalidate, we'll only do it weekly .revalidate_after("5s".to_string()) .should_revalidate_fn(Box::new(|| { - true + Ok(true) })) .build_state_fn(Box::new(get_build_state)) } -pub fn get_build_state(_path: String) -> String { - serde_json::to_string( +pub fn get_build_state(_path: String) -> Result { + Ok(serde_json::to_string( &TimePageProps { time: format!("{:?}", std::time::SystemTime::now()) } - ).unwrap() + ).unwrap()) } pub fn template_fn() -> perseus::template::TemplateFn { diff --git a/src/errors.rs b/src/errors.rs index 29c82bdbed..cb82b80e93 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -33,6 +33,10 @@ error_chain! { description("both build and request states were defined for a template when only one or fewer were expected") display("both build and request states were defined for a template when only one or fewer were expected") } + RenderFnFailed(fn_name: String, template: String, err_str: String) { + description("error while calling render function") + display("an error occurred while calling render function '{}' on template '{}': '{}'", fn_name, template, err_str) + } } links { ConfigManager(crate::config_manager::Error, crate::config_manager::ErrorKind); diff --git a/src/template.rs b/src/template.rs index 7868efe9f9..7efac6fb02 100644 --- a/src/template.rs +++ b/src/template.rs @@ -39,13 +39,16 @@ impl States { } } +/// A generic error type that mandates a string error. This sidesteps horrible generics while maintaining DX. +pub type StringResult = std::result::Result; + // A series of closure types that should not be typed out more than once pub type TemplateFn = Box) -> SycamoreTemplate>; -pub type GetBuildPathsFn = Box Vec>; -pub type GetBuildStateFn = Box String>; -pub type GetRequestStateFn = Box String>; -pub type ShouldRevalidateFn = Box bool>; -pub type AmalgamateStatesFn = Box Option>; +pub type GetBuildPathsFn = Box StringResult>>; +pub type GetBuildStateFn = Box StringResult>; +pub type GetRequestStateFn = Box StringResult>; +pub type ShouldRevalidateFn = Box StringResult>; +pub type AmalgamateStatesFn = Box StringResult>>; /// This allows the specification of all the template templates in an app and how to render them. If no rendering logic is provided at all, /// the template will be prerendered at build-time with no state. All closures are stored on the heap to avoid hellish lifetime specification. @@ -114,8 +117,11 @@ impl Template { /// Gets the list of templates that should be prerendered for at build-time. pub fn get_build_paths(&self) -> Result> { if let Some(get_build_paths) = &self.get_build_paths { - // TODO support error handling for render functions - Ok(get_build_paths()) + let res = get_build_paths(); + match res { + Ok(res) => Ok(res), + Err(err) => bail!(ErrorKind::RenderFnFailed("get_build_paths".to_string(), self.get_path(), err.to_string())) + } } else { bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "build_paths".to_string())) } @@ -124,8 +130,11 @@ impl Template { /// `.get_build_paths()`. pub fn get_build_state(&self, path: String) -> Result { if let Some(get_build_state) = &self.get_build_state { - // TODO support error handling for render functions - Ok(get_build_state(path)) + let res = get_build_state(path); + match res { + Ok(res) => Ok(res), + Err(err) => bail!(ErrorKind::RenderFnFailed("get_build_state".to_string(), self.get_path(), err.to_string())) + } } else { bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "build_state".to_string())) } @@ -134,8 +143,11 @@ impl Template { /// `.get_build_paths()` though, this will be passed information about the request that triggered the render. pub fn get_request_state(&self, path: String) -> Result { if let Some(get_request_state) = &self.get_request_state { - // TODO support error handling for render functions - Ok(get_request_state(path)) + let res = get_request_state(path); + match res { + Ok(res) => Ok(res), + Err(err) => bail!(ErrorKind::RenderFnFailed("get_request_state".to_string(), self.get_path(), err.to_string())) + } } else { bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "request_state".to_string())) } @@ -143,8 +155,11 @@ impl Template { /// Amalagmates given request and build states. pub fn amalgamate_states(&self, states: States) -> Result> { if let Some(amalgamate_states) = &self.amalgamate_states { - // TODO support error handling for render functions - Ok(amalgamate_states(states)) + let res = amalgamate_states(states); + match res { + Ok(res) => Ok(res), + Err(err) => bail!(ErrorKind::RenderFnFailed("amalgamate_states".to_string(), self.get_path(), err.to_string())) + } } else { bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "request_state".to_string())) } @@ -153,8 +168,11 @@ impl Template { /// network access etc., and can really do whatever it likes. pub fn should_revalidate(&self) -> Result { if let Some(should_revalidate) = &self.should_revalidate { - // TODO support error handling for render functions - Ok(should_revalidate()) + let res = should_revalidate(); + match res { + Ok(res) => Ok(res), + Err(err) => bail!(ErrorKind::RenderFnFailed("should_revalidate".to_string(), self.get_path(), err.to_string())) + } } else { bail!(ErrorKind::TemplateFeatureNotEnabled(self.path.clone(), "should_revalidate".to_string())) }