Skip to content

Commit

Permalink
feat: ✨ allowed user render functions to return errors
Browse files Browse the repository at this point in the history
  • Loading branch information
arctic-hen7 committed Aug 13, 2021
1 parent c9df616 commit fa50d4c
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 37 deletions.
6 changes: 3 additions & 3 deletions examples/showcase/app/src/pages/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_static_props(_path: String) -> String {
serde_json::to_string(
pub fn get_static_props(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&IndexPageProps {
greeting: "Hello World!".to_string()
}
).unwrap()
).unwrap())
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Expand Down
6 changes: 3 additions & 3 deletions examples/showcase/app/src/pages/ip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_request_state(_path: String) -> String {
serde_json::to_string(
pub fn get_request_state(_path: String) -> Result<String, String> {
Ok(serde_json::to_string(
&IpPageProps {
ip: "x.x.x.x".to_string()
}
).unwrap()
).unwrap())
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Expand Down
12 changes: 6 additions & 6 deletions examples/showcase/app/src/pages/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.template(template_fn())
}

pub fn get_static_props(path: String) -> String {
pub fn get_static_props(path: String) -> Result<String, String> {
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<String> {
vec![
pub fn get_static_paths() -> Result<Vec<String>, String> {
Ok(vec![
"test".to_string()
]
])
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Expand Down
12 changes: 6 additions & 6 deletions examples/showcase/app/src/pages/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
.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<String, String> {
Ok(serde_json::to_string(
&TimePageProps {
time: format!("{:?}", std::time::SystemTime::now())
}
).unwrap()
).unwrap())
}

pub fn get_build_paths() -> Vec<String> {
vec![
pub fn get_build_paths() -> Result<Vec<String>, String> {
Ok(vec![
"test".to_string()
]
])
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Expand Down
8 changes: 4 additions & 4 deletions examples/showcase/app/src/pages/time_root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ pub fn get_page<G: GenericNode>() -> Template<G> {
// 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<String, String> {
Ok(serde_json::to_string(
&TimePageProps {
time: format!("{:?}", std::time::SystemTime::now())
}
).unwrap()
).unwrap())
}

pub fn template_fn<G: GenericNode>() -> perseus::template::TemplateFn<G> {
Expand Down
4 changes: 4 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
48 changes: 33 additions & 15 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> = std::result::Result<T, String>;

// A series of closure types that should not be typed out more than once
pub type TemplateFn<G> = Box<dyn Fn(Option<String>) -> SycamoreTemplate<G>>;
pub type GetBuildPathsFn = Box<dyn Fn() -> Vec<String>>;
pub type GetBuildStateFn = Box<dyn Fn(String) -> String>;
pub type GetRequestStateFn = Box<dyn Fn(String) -> String>;
pub type ShouldRevalidateFn = Box<dyn Fn() -> bool>;
pub type AmalgamateStatesFn = Box<dyn Fn(States) -> Option<String>>;
pub type GetBuildPathsFn = Box<dyn Fn() -> StringResult<Vec<String>>>;
pub type GetBuildStateFn = Box<dyn Fn(String) -> StringResult<String>>;
pub type GetRequestStateFn = Box<dyn Fn(String) -> StringResult<String>>;
pub type ShouldRevalidateFn = Box<dyn Fn() -> StringResult<bool>>;
pub type AmalgamateStatesFn = Box<dyn Fn(States) -> StringResult<Option<String>>>;

/// 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.
Expand Down Expand Up @@ -114,8 +117,11 @@ impl<G: GenericNode> Template<G> {
/// Gets the list of templates that should be prerendered for at build-time.
pub fn get_build_paths(&self) -> Result<Vec<String>> {
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()))
}
Expand All @@ -124,8 +130,11 @@ impl<G: GenericNode> Template<G> {
/// `.get_build_paths()`.
pub fn get_build_state(&self, path: String) -> Result<String> {
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()))
}
Expand All @@ -134,17 +143,23 @@ impl<G: GenericNode> Template<G> {
/// `.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<String> {
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()))
}
}
/// Amalagmates given request and build states.
pub fn amalgamate_states(&self, states: States) -> Result<Option<String>> {
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()))
}
Expand All @@ -153,8 +168,11 @@ impl<G: GenericNode> Template<G> {
/// network access etc., and can really do whatever it likes.
pub fn should_revalidate(&self) -> Result<bool> {
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()))
}
Expand Down

0 comments on commit fa50d4c

Please sign in to comment.