Perseus Starter App
-
+
diff --git a/examples/cli/.perseus/src/lib.rs b/examples/cli/.perseus/src/lib.rs
index 29cad665f6..eeba820ca7 100644
--- a/examples/cli/.perseus/src/lib.rs
+++ b/examples/cli/.perseus/src/lib.rs
@@ -1,9 +1,9 @@
-use app::{get_error_pages, get_locales, get_routes, APP_ROUTE};
+use app::{get_error_pages, get_locales, get_templates_map, APP_ROOT};
use perseus::router::{RouteInfo, RouteVerdict};
-use perseus::{app_shell, detect_locale, ClientTranslationsManager, DomNode};
+use perseus::shell::get_render_cfg;
+use perseus::{app_shell, create_app_route, detect_locale, ClientTranslationsManager, DomNode};
use std::cell::RefCell;
use std::rc::Rc;
-use sycamore::context::{ContextProvider, ContextProviderProps};
use sycamore::prelude::{template, StateHandle};
use sycamore_router::{HistoryIntegration, Router, RouterProps};
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
@@ -18,7 +18,7 @@ pub fn run() -> Result<(), JsValue> {
.unwrap()
.document()
.unwrap()
- .query_selector(APP_ROUTE)
+ .query_selector(APP_ROOT)
.unwrap()
.unwrap();
@@ -27,42 +27,42 @@ pub fn run() -> Result<(), JsValue> {
Rc::new(RefCell::new(ClientTranslationsManager::new(&get_locales())));
// Get the error pages in an `Rc` so we aren't creating hundreds of them
let error_pages = Rc::new(get_error_pages());
- // Get the routes in an `Rc` as well
- let routes = Rc::new(get_routes::());
+
+ // Create the router we'll use for this app, based on the user's app definition
+ create_app_route! {
+ name => AppRoute,
+ // The render configuration is injected verbatim into the HTML shell, so it certainly should be present
+ render_cfg => get_render_cfg().expect("render configuration invalid or not injected"),
+ templates => get_templates_map(),
+ locales => get_locales()
+ }
sycamore::render_to(
|| {
template! {
- // We provide the routes in context (can't provide them directly because of Sycamore trait constraints)
- // BUG: context doesn't exist when link clicked first time, works second time...
- ContextProvider(ContextProviderProps {
- value: Rc::clone(&routes),
- children: || template! {
- Router(RouterProps::new(HistoryIntegration::new(), move |route: StateHandle>| {
- match route.get().as_ref() {
- // Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
- RouteVerdict::Found(RouteInfo {
- path,
- template_fn,
- locale
- }) => app_shell(
- path.clone(),
- template_fn.clone(),
- locale.clone(),
- // We give the app shell a translations manager and let it get the `Rc` itself (because it can do async safely)
- Rc::clone(&translations_manager),
- Rc::clone(&error_pages)
- ),
- // If the user is using i18n, then they'll want to detect the locale on any paths missing a locale
- // Those all go to the same system that redirects to the appropriate locale
- RouteVerdict::LocaleDetection(path) => detect_locale(path.clone(), get_locales()),
- // We handle the 404 for the user for convenience
- // To get a translator here, we'd have to go async and dangerously check the URL
- RouteVerdict::NotFound => get_error_pages().get_template_for_page("", &404, "not found", None),
- }
- }))
+ Router(RouterProps::new(HistoryIntegration::new(), move |route: StateHandle>| {
+ match &route.get().as_ref().0 {
+ // Perseus' custom routing system is tightly coupled to the template system, and returns exactly what we need for the app shell!
+ RouteVerdict::Found(RouteInfo {
+ path,
+ template,
+ locale
+ }) => app_shell(
+ path.clone(),
+ template.clone(),
+ locale.clone(),
+ // We give the app shell a translations manager and let it get the `Rc` itself (because it can do async safely)
+ Rc::clone(&translations_manager),
+ Rc::clone(&error_pages)
+ ),
+ // If the user is using i18n, then they'll want to detect the locale on any paths missing a locale
+ // Those all go to the same system that redirects to the appropriate locale
+ RouteVerdict::LocaleDetection(path) => detect_locale(path.clone(), get_locales()),
+ // We handle the 404 for the user for convenience
+ // To get a translator here, we'd have to go async and dangerously check the URL
+ RouteVerdict::NotFound => get_error_pages().get_template_for_page("", &404, "not found", None),
}
- })
+ }))
}
},
&root,
diff --git a/examples/cli/index.html b/examples/cli/index.html
index dc31105f88..d9b3c52d9d 100644
--- a/examples/cli/index.html
+++ b/examples/cli/index.html
@@ -6,7 +6,7 @@
Perseus Starter App
-
+
diff --git a/examples/cli/src/lib.rs b/examples/cli/src/lib.rs
index 09bffda71c..6207e20556 100644
--- a/examples/cli/src/lib.rs
+++ b/examples/cli/src/lib.rs
@@ -7,8 +7,8 @@ define_app! {
root: "#root",
error_pages: crate::error_pages::get_error_pages(),
templates: [
- "/" => crate::pages::index::get_page::(),
- "/about" => crate::pages::about::get_page::()
+ crate::pages::index::get_page::(),
+ crate::pages::about::get_page::()
],
locales: {
default: "en-US",
diff --git a/examples/i18n/src/lib.rs b/examples/i18n/src/lib.rs
index 9e2f07188e..27c0ad1428 100644
--- a/examples/i18n/src/lib.rs
+++ b/examples/i18n/src/lib.rs
@@ -7,9 +7,8 @@ define_app! {
root: "#root",
error_pages: crate::error_pages::get_error_pages(),
templates: [
- "/about" => crate::templates::about::get_template::(),
- // Note that the index page comes last, otherwise locale detection for `/about` matches `about` as a locale
- "/" => crate::templates::index::get_template::()
+ crate::templates::about::get_template::(),
+ crate::templates::index::get_template::()
],
locales: {
default: "en-US",
diff --git a/examples/showcase/index.html b/examples/showcase/index.html
index 45e62a9131..d7afe211a6 100644
--- a/examples/showcase/index.html
+++ b/examples/showcase/index.html
@@ -6,7 +6,7 @@
Perseus Showcase App
-
+
diff --git a/examples/showcase/src/lib.rs b/examples/showcase/src/lib.rs
index aa84da47b9..b22f536bfa 100644
--- a/examples/showcase/src/lib.rs
+++ b/examples/showcase/src/lib.rs
@@ -7,15 +7,15 @@ define_app! {
root: "#root",
error_pages: crate::error_pages::get_error_pages(),
templates: [
- "/" => crate::templates::index::get_template::(),
- "/about" => crate::templates::about::get_template::(),
- "/post/new" => crate::templates::new_post::get_template::(),
+ crate::templates::index::get_template::(),
+ crate::templates::about::get_template::(),
+ crate::templates::new_post::get_template::(),
// BUG: Sycamore doesn't support dynamic paths before dynamic segments (https://github.com/sycamore-rs/sycamore/issues/228)
- "/post/" => crate::templates::post::get_template::(),
- "/ip" => crate::templates::ip::get_template::(),
- "/time" => crate::templates::time_root::get_template::(),
- "/timeisr/" => crate::templates::time::get_template::(),
- "/amalgamation" => crate::templates::amalgamation::get_template::()
+ crate::templates::post::get_template::(),
+ crate::templates::ip::get_template::(),
+ crate::templates::time_root::get_template::(),
+ crate::templates::time::get_template::(),
+ crate::templates::amalgamation::get_template::()
],
locales: {
default: "en-US",
diff --git a/packages/perseus-actix-web/Cargo.toml b/packages/perseus-actix-web/Cargo.toml
index 0a7bc63f5e..c802324288 100644
--- a/packages/perseus-actix-web/Cargo.toml
+++ b/packages/perseus-actix-web/Cargo.toml
@@ -18,6 +18,7 @@ perseus = { path = "../perseus", version = "0.1.4" }
actix-web = "3.3"
actix-files = "0.5"
urlencoding = "2.1"
+serde = "1"
serde_json = "1"
error-chain = "0.12"
futures = "0.3"
diff --git a/packages/perseus-actix-web/src/configurer.rs b/packages/perseus-actix-web/src/configurer.rs
index cce3dd577f..8e68d23a6c 100644
--- a/packages/perseus-actix-web/src/configurer.rs
+++ b/packages/perseus-actix-web/src/configurer.rs
@@ -1,8 +1,10 @@
use crate::page_data::page_data;
use crate::translations::translations;
use actix_files::NamedFile;
-use actix_web::web;
+use actix_web::{web, HttpResponse};
use perseus::{get_render_cfg, ConfigManager, Locales, SsrNode, TemplateMap, TranslationsManager};
+use std::collections::HashMap;
+use std::fs;
/// The options for setting up the Actix Web integration. This should be literally constructed, as nothing is optional.
#[derive(Clone)]
@@ -22,6 +24,17 @@ pub struct Options {
pub locales: Locales,
}
+async fn render_conf(
+ render_conf: web::Data>,
+) -> web::Json> {
+ web::Json(render_conf.get_ref().clone())
+}
+/// This returns the HTML index file with the render configuration injected as a JS global variable.
+async fn index(index_with_render_cfg: web::Data) -> HttpResponse {
+ HttpResponse::Ok()
+ .content_type("text/html")
+ .body(index_with_render_cfg.get_ref())
+}
async fn js_bundle(opts: web::Data) -> std::io::Result {
NamedFile::open(&opts.js_bundle)
}
@@ -31,9 +44,6 @@ async fn js_init(opts: web::Data) -> std::io::Result {
async fn wasm_bundle(opts: web::Data) -> std::io::Result {
NamedFile::open(&opts.wasm_bundle)
}
-async fn index(opts: web::Data) -> std::io::Result {
- NamedFile::open(&opts.index)
-}
/// Configures an existing Actix Web app for Perseus. This returns a function that does the configuring so it can take arguments.
pub async fn configurer(
@@ -44,6 +54,18 @@ pub async fn configurer",
+ // It's safe to assume that something we just deserialized will serialize again in this case
+ &format!(
+ "\n",
+ serde_json::to_string(&render_cfg).unwrap()
+ ),
+ );
+
move |cfg: &mut web::ServiceConfig| {
cfg
// We implant the render config in the app data for better performance, it's needed on every request
@@ -51,14 +73,17 @@ pub async fn configurer),
diff --git a/packages/perseus-actix-web/src/page_data.rs b/packages/perseus-actix-web/src/page_data.rs
index 850ed9cf69..9f136bf292 100644
--- a/packages/perseus-actix-web/src/page_data.rs
+++ b/packages/perseus-actix-web/src/page_data.rs
@@ -2,18 +2,25 @@ use crate::conv_req::convert_req;
use crate::Options;
use actix_web::{http::StatusCode, web, HttpRequest, HttpResponse};
use perseus::{err_to_status_code, get_page, ConfigManager, TranslationsManager};
-use std::collections::HashMap;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct PageDataReq {
+ pub template_name: String,
+}
/// The handler for calls to `.perseus/page/*`. This will manage returning errors and the like.
pub async fn page_data(
req: HttpRequest,
opts: web::Data,
- render_cfg: web::Data>,
config_manager: web::Data,
translations_manager: web::Data,
+ web::Query(query_params): web::Query,
) -> HttpResponse {
let templates = &opts.templates_map;
let locale = req.match_info().query("locale");
+ // TODO get the template name from the query
+ let template_name = query_params.template_name;
// Check if the locale is supported
if opts.locales.is_supported(locale) {
let path = req.match_info().query("filename");
@@ -30,8 +37,8 @@ pub async fn page_data(
let page_data = get_page(
path,
locale,
+ &template_name,
http_req,
- &render_cfg,
templates,
config_manager.get_ref(),
translations_manager.get_ref(),
diff --git a/packages/perseus/Cargo.toml b/packages/perseus/Cargo.toml
index 4e24ccf3ae..d4dd2d31e0 100644
--- a/packages/perseus/Cargo.toml
+++ b/packages/perseus/Cargo.toml
@@ -31,6 +31,7 @@ http = "0.2"
async-trait = "0.1"
fluent-bundle = { version = "0.15", optional = true }
unic-langid = { version = "0.9", optional = true }
+js-sys = "0.3"
[features]
default = ["translator-fluent", "translator-dflt-fluent"]
diff --git a/packages/perseus/src/macros.rs b/packages/perseus/src/macros.rs
index 34e6bfad2a..65357a6241 100644
--- a/packages/perseus/src/macros.rs
+++ b/packages/perseus/src/macros.rs
@@ -104,7 +104,7 @@ macro_rules! define_app {
root: $root_selector:literal,
error_pages: $error_pages:expr,
templates: [
- $($router_path:literal => $template:expr),+
+ $($template:expr),+
],
// This deliberately enforces verbose i18n definition, and forces developers to consider i18n as integral
locales: {
@@ -117,20 +117,7 @@ macro_rules! define_app {
$(,translations_manager: $translations_manager:expr)?
} => {
/// The CSS selector that will find the app root to render Perseus in.
- pub const APP_ROUTE: &str = $root_selector;
-
- /// Gets the routes for the app in Perseus' custom abstraction over Sycamore's routing logic. This enables tight coupling of
- /// the templates and the routing system. This can be used on the client or server side.
- pub fn get_routes() -> $crate::router::Routes {
- $crate::router::Routes::new(
- vec![
- $(
- ($router_path.to_string(), $template)
- ),+
- ],
- get_locales()
- )
- }
+ pub const APP_ROOT: &str = $root_selector;
/// Gets the config manager to use. This allows the user to conveniently test production managers in development. If nothing is
/// given, the filesystem will be used.
diff --git a/packages/perseus/src/router.rs b/packages/perseus/src/router.rs
index ab80f9ad5b..e97c76497c 100644
--- a/packages/perseus/src/router.rs
+++ b/packages/perseus/src/router.rs
@@ -1,120 +1,111 @@
use crate::Locales;
use crate::Template;
-use std::rc::Rc;
-use sycamore::context::use_context;
+use crate::TemplateMap;
+use std::collections::HashMap;
use sycamore::prelude::GenericNode;
-use sycamore_router::{Route, RoutePath, Segment};
-/// A representation of routes in a Perseus app. This is used internally to match routes. Because this can't be passed directly to
-/// the `RouteVerdict`'s `match_route` function, it should be provided in context instead (through an `Rc`).
-pub struct Routes {
- /// The routes in the app, stored as an *ordered* list of key-value pairs, mapping routing path (e.g. `/post/`) to template.
- /// These will be matched by a loop, so more specific routes should go first in the vector. Even if we're using i18n, this still
- /// stores a routing path without the locale, which is added in during parsing as necessary.
- routes: Vec<(Vec, Template)>,
- /// Whether or not the user is using i18n, which significantly impacts how we match routes (will there be a locale in front of
- /// everything).
- locales: Locales,
-}
-impl Routes {
- /// Creates a new instance of the routes. This takes a vector of key-value pairs of routing path to template functions.
- pub fn new(raw_routes: Vec<(String, Template)>, locales: Locales) -> Self {
- let routes: Vec<(Vec, Template)> = raw_routes
- .iter()
- .map(|(router_path_str_raw, template_fn)| {
- // Handle the landing page (because match systems don't tolerate empty strings well)
- if router_path_str_raw == "/" {
- return (Vec::new(), template_fn.clone());
- }
-
- // Remove leading/trailing `/`s to avoid empty elements (which stuff up path matching)
- let mut router_path_str = router_path_str_raw.clone();
- if router_path_str.starts_with('/') {
- router_path_str.remove(0);
- }
- if router_path_str.ends_with('/') {
- router_path_str.remove(router_path_str.len() - 1);
- }
+/// Determines the template to use for the given path by checking against the render configuration. This houses the central routing
+/// algorithm of Perseus, which is based fully on the fact that we know about every single page except those rendered with ISR, and we
+/// can infer about them based on template root path domains. If that domain system is violated, this routing algorithm will not
+/// behave as expected whatsoever (as far as routing goes, it's undefined behaviour)!
+pub fn get_template_for_path<'a, G: GenericNode>(
+ raw_path: &str,
+ render_cfg: &HashMap,
+ templates: &'a TemplateMap,
+) -> Option<&'a Template> {
+ let mut path = raw_path;
+ // If the path is empty, we're looking for the special `index` page
+ if path.is_empty() {
+ path = "index";
+ }
- let router_path_parts = router_path_str.split('/');
- let router_path: Vec = router_path_parts
- .map(|part| {
- // TODO possibly use Actix Web like syntax here instead and propose to @lukechu10?
- // We need to create a segment out of this part, we'll parse Sycamore's syntax
- // We don't actually need Regex here, so we don't bloat with it
- // If you're familiar with Sycamore's routing system, we don't need to worry about capturing these segments in Perseus because we just return the actual path directly
- /* Variants (in tested order):
- - segment that captures many parameters
- - parameter that captures a single element
- - stuff verbatim stuff
- */
- if part.starts_with('<') && part.ends_with("..>") {
- Segment::DynSegments
- } else if part.starts_with('<') && part.ends_with('>') {
- Segment::DynParam
- } else {
- Segment::Param(part.to_string())
- }
- })
- .collect();
- // Turn the router path into a vector of `Segment`s
- (router_path, template_fn.clone())
- })
- .collect();
+ // Match the path to one of the templates
+ let mut template_name = String::new();
+ // We'll try a direct match first
+ if let Some(template_root_path) = render_cfg.get(path) {
+ template_name = template_root_path.to_string();
+ }
+ // Next, an ISR match (more complex), which we only want to run if we didn't get an exact match above
+ if template_name.is_empty() {
+ // We progressively look for more and more specificity of the path, adding each segment
+ // That way, we're searching forwards rather than backwards, which is more efficient
+ let path_segments: Vec<&str> = path.split('/').collect();
+ for (idx, _) in path_segments.iter().enumerate() {
+ // Make a path out of this and all the previous segments
+ let path_to_try = path_segments[0..(idx + 1)].join("/") + "/*";
- Self { routes, locales }
+ // If we find something, keep going until we don't (maximise specificity)
+ if let Some(template_root_path) = render_cfg.get(&path_to_try) {
+ template_name = template_root_path.to_string();
+ } else {
+ break;
+ }
+ }
+ }
+ // If we still have nothing, then the page doesn't exist
+ if template_name.is_empty() {
+ return None;
}
- /// Matches the given route to an instance of `RouteVerdict`.
- pub fn match_route(&self, raw_path: &[&str]) -> RouteVerdict {
- let path: Vec<&str> = raw_path.to_vec();
- let path_joined = path.join("/"); // This should not have a leading forward slash, it's used for asset fetching by the app shell
- let mut verdict = RouteVerdict::NotFound;
- // There are different logic chains if we're using i18n, so we fork out early
- if self.locales.using_i18n {
- for (segments, template_fn) in &self.routes {
- let route_path_without_locale = RoutePath::new(segments.to_vec());
- let route_path_with_locale = RoutePath::new({
- let mut vec = vec![Segment::DynParam];
- vec.extend(segments.to_vec());
- vec
- });
+ // Get the template to use (the `Option` this returns is perfect) if it exists
+ templates.get(&template_name)
+}
- // First, we'll see if the path matches a translated route
- // If that fails, we'll see if it matches an untranslated route, which becomes a locale detector
- if route_path_with_locale.match_path(&path).is_some() {
- verdict = RouteVerdict::Found(RouteInfo {
- // The asset fetching process deals with the locale separately, and doesn't need a leading `/`
- path: path[1..].to_vec().join("/"),
- template_fn: template_fn.clone(),
- locale: path[0].to_string(),
- });
- break;
- } else if route_path_without_locale.match_path(&path).is_some() {
- // We've now matched that it fits without the locale, which means the user is trying to
- verdict = RouteVerdict::LocaleDetection(path_joined);
- break;
- }
- }
- } else {
- for (segments, template_fn) in &self.routes {
- let route_path = RoutePath::new(segments.to_vec());
+/// Matches the given path to a `RouteVerdict`. This takes a `TemplateMap` to match against, the render configuration to index, and it
+/// needs to know if i18n is being used. The path this takes should be raw, it may or may not have a locale, but should be split into
+/// segments by `/`, with empty ones having been removed.
+pub fn match_route(
+ path_slice: &[&str],
+ render_cfg: HashMap,
+ templates: TemplateMap,
+ locales: Locales,
+) -> RouteVerdict {
+ let path_vec: Vec<&str> = path_slice.to_vec();
+ let path_joined = path_vec.join("/"); // This should not have a leading forward slash, it's used for asset fetching by the app shell
- // We're not using i18n, so we can just match the path directly
- if route_path.match_path(&path).is_some() {
- verdict = RouteVerdict::Found(RouteInfo {
- path: path_joined,
- template_fn: template_fn.clone(),
- // Every page uses the default locale if we aren't using i18n (translators won't be used anyway)
- locale: self.locales.default.to_string(),
- });
- break;
- }
- }
+ let verdict;
+ // There are different logic chains if we're using i18n, so we fork out early
+ if locales.using_i18n && !path_slice.is_empty() {
+ let locale = path_slice[0];
+ // Check if the 'locale' is supported (otherwise it may be the first section of an uni18ned route)
+ if locales.is_supported(locale) {
+ // We'll assume this has already been i18ned (if one of your routes has the same name as a supported locale, ffs)
+ let path_without_locale = path_slice[1..].to_vec().join("/");
+ // Get the template to use
+ let template = get_template_for_path(&path_without_locale, &render_cfg, &templates);
+ verdict = match template {
+ Some(template) => RouteVerdict::Found(RouteInfo {
+ locale: locale.to_string(),
+ // This will be used in asset fetching from the server
+ path: path_without_locale,
+ template: template.clone(),
+ }),
+ None => RouteVerdict::NotFound,
+ };
+ } else {
+ // If the locale isn't supported, we assume that it's part of a route that still needs a locale (we'll detect the user's preferred)
+ // This will result in a redirect, and the actual template to use will be determined after that
+ // We'll just pass through the path to be redirected to (after it's had a locale placed in front)
+ verdict = RouteVerdict::LocaleDetection(path_joined)
}
-
- verdict
+ } else if locales.using_i18n {
+ // If we're here, then we're using i18n, but we're at the root path, which is a locale detection point
+ verdict = RouteVerdict::LocaleDetection(path_joined);
+ } else {
+ // Get the template to use
+ let template = get_template_for_path(&path_joined, &render_cfg, &templates);
+ verdict = match template {
+ Some(template) => RouteVerdict::Found(RouteInfo {
+ locale: locales.default.to_string(),
+ // This will be used in asset fetching from the server
+ path: path_joined,
+ template: template.clone(),
+ }),
+ None => RouteVerdict::NotFound,
+ };
}
+
+ verdict
}
/// Information about a route, which, combined with error pages and a client-side translations manager, allows the initialization of
@@ -122,8 +113,8 @@ impl Routes {
pub struct RouteInfo {
/// The actual path of the route.
pub path: String,
- /// The template that will render the template. The app shell will derive pros and a translator to pass to the template function.
- pub template_fn: Template,
+ /// The template that will be used. The app shell will derive pros and a translator to pass to the template function.
+ pub template: Template,
/// The locale for the template to be rendered in.
pub locale: String,
}
@@ -139,11 +130,26 @@ pub enum RouteVerdict {
/// The given route maps to the locale detector, which will redirect the user to the attached path (in the appropriate locale).
LocaleDetection(String),
}
-impl Route for RouteVerdict {
- fn match_route(path: &[&str]) -> Self {
- // Get an instance of `Routes` by context
- let routes = use_context::>>();
- // Match the path using that
- routes.match_route(path)
- }
+
+/// Creates an app-specific routing `struct`. Sycamore expects an `enum` to do this, so we create a `struct` that behaves similarly. If
+/// we don't do this, we can't get the information necessary for routing into the `enum` at all (context and global variables don't suit
+/// this particular case).
+#[macro_export]
+macro_rules! create_app_route {
+ {
+ name => $name:ident,
+ render_cfg => $render_cfg:expr,
+ templates => $templates:expr,
+ locales => $locales:expr
+ } => {
+ /// The route type for the app, with all routing logic inbuilt through the generation macro.
+ struct $name($crate::router::RouteVerdict);
+ impl ::sycamore_router::Route for $name {
+ fn match_route(path: &[&str]) -> Self {
+ let verdict = $crate::router::match_route(path, $render_cfg, $templates, $locales);
+ // BUG Sycamore doesn't call the route verdict matching logic for some reason, but we get to this point
+ Self(verdict)
+ }
+ }
+ };
}
diff --git a/packages/perseus/src/serve.rs b/packages/perseus/src/serve.rs
index ddfcf03dfb..1bfe750b5d 100644
--- a/packages/perseus/src/serve.rs
+++ b/packages/perseus/src/serve.rs
@@ -162,8 +162,8 @@ pub async fn get_page(
// This must not contain the locale
raw_path: &str,
locale: &str,
+ template_name: &str,
req: Request,
- render_cfg: &HashMap,
templates: &TemplateMap,
config_manager: &impl ConfigManager,
translations_manager: &impl TranslationsManager,
@@ -182,39 +182,11 @@ pub async fn get_page(
// Remove `/` from the path by encoding it as a URL (that's what we store) and add the locale
let path_encoded = format!("{}-{}", locale, urlencoding::encode(path).to_string());
- // Match the path to one of the templates
- let mut template_name = String::new();
- // We'll try a direct match first
- if let Some(template_root_path) = render_cfg.get(path) {
- template_name = template_root_path.to_string();
- }
- // Next, an ISR match (more complex), which we only want to run if we didn't get an exact match above
- if template_name.is_empty() {
- // We progressively look for more and more specificity of the path, adding each segment
- // That way, we're searching forwards rather than backwards, which is more efficient
- let path_segments: Vec<&str> = path.split('/').collect();
- for (idx, _) in path_segments.iter().enumerate() {
- // Make a path out of this and all the previous segments
- let path_to_try = path_segments[0..(idx + 1)].join("/") + "/*";
-
- // If we find something, keep going until we don't (maximise specificity)
- if let Some(template_root_path) = render_cfg.get(&path_to_try) {
- template_name = template_root_path.to_string();
- } else {
- break;
- }
- }
- }
-
- // If we still have nothing, then the page doesn't exist
- if template_name.is_empty() {
- bail!(ErrorKind::PageNotFound(path.to_string()))
- }
-
// Get the template to use
- let template = templates.get(&template_name);
+ let template = templates.get(template_name);
let template = match template {
Some(template) => template,
+ // This shouldn't happen because the client should already have performed checks against the render config, but it's handled anyway
None => bail!(ErrorKind::PageNotFound(path.to_string())),
};
diff --git a/packages/perseus/src/shell.rs b/packages/perseus/src/shell.rs
index 4687ec47f1..9b1496dcee 100644
--- a/packages/perseus/src/shell.rs
+++ b/packages/perseus/src/shell.rs
@@ -13,7 +13,9 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
-pub(crate) async fn fetch(url: &str) -> Result