-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: updated versions and editions Sycamore v0.8 only runs on Rust 2021. * fix: fixed some rust 2021 syntax issues * fix: fixed easy errors in `perseus` crate Still have to do: - `router/` - `template/` - `shell.rs` - One tricky error in `error_pages.rs` * fix: fixed all errors in `perseus` crate I removed the troublesome function in `error_pages.rs`, so that may bite me soon. * fix: fixed clippy lints * fix: fixed macros and made `basic` example work * fix: partial fixes for `rx_state` Still have problems with `bind:value` (as we will in all the state platform examples for now). * fix: updated state examples (errors persist) The remaining errors will be fixed after `make_rx` can work with non-`Rc` `Signal`s. * fix: updated all examples (lifetime errors persist) * fix: fixed engine Nothing works without hydration disabled though... * refactor: made renderers use top-level router context This *should* make the state platform work with lifetimes. * feat: rewrote render context to use `Signal`s * fix: made macros work with the new render context logic * fix: updated unreactive macros and example * chore: tmp commit before rollback * revert: revert to before axing `RcSignal`s I have been shown a much better way of achieving the same outcome. * revert: return to previous changes It will be easier to manually undo changes to make sure we preserve some good things. This reverts commit 15187b5. * feat: moved back to `RcSignal`s This avoids a huge number of lifetime issues, and actually ends up being more performant, without compromising on ergonomics. * feat: made `struct` given to user's template use `&'a RcSignal<T>` This should make Perseus several orders of magnitude more ergonomic, in line with Sycamore's new no-clones system! * fix: fixed global state functionality in the macros This requires an irritating change to import practices unfortunately, but the convenience and ergonomics are worth it. * fix: fixed lifetimes errors in all examples * fix: fixed all lifetimes issues This also involved some minor changes to the macros to fix some nested state issues. * fix: fixed nested state references This improves ergonomics and makes the auth example compile. * fix: fixed hydration by not inserting hydration keys in `<head>` (#137) * refactor: simplify provide_context_signal_replace Also slightly improves performance in only making a single call to use_context * fix: do not insert hydration keys in the head string * chore: remove perseus/hydrate feature from Cargo.toml * chore: merge imports for consistent code style * fix: update sycamore to v0.8.0-beta.5 and remove workaround Co-authored-by: arctic_hen7 <arctic_hen7@pm.me> * chore: updated deps after #137 These were just for the demos that weren't ready at the time of the PR. * chore: re-added `hydrate` feature to `basic` example Hydration still doesn't work in the `auth` example. * chore: removed unused dep * fix: fixed doc tests issue * fix: fixed naming of `PerseusRoot` (was wrongly `perseus_root`) * feat: updated `index_view` example * feat: added axum integration This is all untested as yet, but everything *should* work. * chore: updated to latest sycamore beta This should fix the issues with the `body` element. * fix: fixed an imports issue with latest sycamore beta * fix: fixed types to make i18n work * test: fixed `rx_state` tests for slightly updated structure * fix: ignored a failing doctest * docs: updated security.md for next beta version * docs: added new docs for v0.4.x Also locked the old v0.3.4-5 docs to a specific commit hash, which keeps the examples there safe to use. * feat: integrated axum with other integrations There are still some issues with shared state though that make this completely unusable. * fix: fixed shared state issues No clue why using extensions didn't work, but now we're using closure captures, which are compile-time checked anyway. * fix: made static content work Just some simple errors made this fail. Hopefully, all tests should now pass... Co-authored-by: Luke Chu <37006668+lukechu10@users.noreply.github.com>
- Loading branch information
1 parent
51f2b2f
commit dbe8207
Showing
14 changed files
with
493 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[package] | ||
name = "perseus-axum" | ||
version = "0.3.5" | ||
edition = "2021" | ||
description = "An integration that makes the Perseus frontend framework easy to use with Axum." | ||
authors = ["arctic_hen7 <arctic_hen7@pm.me>"] | ||
license = "MIT" | ||
repository = "https://github.com/arctic-hen7/perseus" | ||
homepage = "https://arctic-hen7.github.io/perseus" | ||
readme = "./README.md" | ||
keywords = ["wasm", "frontend", "webdev", "ssg", "ssr"] | ||
categories = ["wasm", "web-programming::http-server", "development-tools", "asynchronous", "gui"] | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
perseus = { path = "../perseus", version = "0.4.0-beta.1" } | ||
axum = "0.5" | ||
tower = "0.4" | ||
tower-http = { version = "0.3", features = [ "fs" ] } | ||
urlencoding = "2.1" | ||
serde = "1" | ||
serde_json = "1" | ||
thiserror = "1" | ||
fmterr = "0.1" | ||
futures = "0.3" | ||
sycamore = { version = "=0.8.0-beta.6", features = ["ssr"] } | ||
closure = "0.3" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Perseus Axum Integration | ||
|
||
This is the official [Perseus](https://github.com/arctic-hen7/perseus) integration for making serving your apps on [Axum](https://docs.rs/axum) significantly easier! | ||
|
||
If you're new to Perseus, you should check out [the core package](https://github.com/arctic-hen7/perseus) first. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../../README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
use axum::{ | ||
body::Body, | ||
http::{HeaderMap, StatusCode}, | ||
response::Html, | ||
}; | ||
use fmterr::fmt_err; | ||
use perseus::{ | ||
errors::err_to_status_code, | ||
internal::{ | ||
get_path_prefix_server, | ||
i18n::{TranslationsManager, Translator}, | ||
router::{match_route_atomic, RouteInfoAtomic, RouteVerdictAtomic}, | ||
serve::{ | ||
build_error_page, get_page_for_template, get_path_slice, GetPageProps, HtmlShell, | ||
ServerOptions, | ||
}, | ||
}, | ||
stores::{ImmutableStore, MutableStore}, | ||
ErrorPages, Request, SsrNode, | ||
}; | ||
use std::{collections::HashMap, rc::Rc, sync::Arc}; | ||
|
||
/// Builds on the internal Perseus primitives to provide a utility function that returns a `Response` automatically. | ||
fn return_error_page( | ||
url: &str, | ||
status: u16, | ||
// This should already have been transformed into a string (with a source chain etc.) | ||
err: &str, | ||
translator: Option<Rc<Translator>>, | ||
error_pages: &ErrorPages<SsrNode>, | ||
html_shell: &HtmlShell, | ||
) -> (StatusCode, HeaderMap, Html<String>) { | ||
let html = build_error_page(url, status, err, translator, error_pages, html_shell); | ||
( | ||
StatusCode::from_u16(status).unwrap(), | ||
HeaderMap::new(), | ||
Html(html), | ||
) | ||
} | ||
|
||
/// The handler for calls to any actual pages (first-time visits), which will render the appropriate HTML and then interpolate it into | ||
/// the app shell. | ||
#[allow(clippy::too_many_arguments)] // As for `page_data_handler`, we don't have a choice | ||
pub async fn initial_load_handler<M: MutableStore, T: TranslationsManager>( | ||
http_req: perseus::http::Request<Body>, | ||
opts: Arc<ServerOptions>, | ||
html_shell: Arc<HtmlShell>, | ||
render_cfg: Arc<HashMap<String, String>>, | ||
immutable_store: Arc<ImmutableStore>, | ||
mutable_store: Arc<M>, | ||
translations_manager: Arc<T>, | ||
global_state: Arc<Option<String>>, | ||
) -> (StatusCode, HeaderMap, Html<String>) { | ||
let path = http_req.uri().path().to_string(); | ||
let http_req = Request::from_parts(http_req.into_parts().0, ()); | ||
|
||
let templates = &opts.templates_map; | ||
let error_pages = &opts.error_pages; | ||
let path_slice = get_path_slice(&path); | ||
// Create a closure to make returning error pages easier (most have the same data) | ||
let html_err = |status: u16, err: &str| { | ||
return return_error_page(&path, status, err, None, error_pages, html_shell.as_ref()); | ||
}; | ||
|
||
// Run the routing algorithms on the path to figure out which template we need | ||
let verdict = match_route_atomic(&path_slice, render_cfg.as_ref(), templates, &opts.locales); | ||
match verdict { | ||
// If this is the outcome, we know that the locale is supported and the like | ||
// Given that all this is valid from the client, any errors are 500s | ||
RouteVerdictAtomic::Found(RouteInfoAtomic { | ||
path, // Used for asset fetching, this is what we'd get in `page_data` | ||
template, // The actual template to use | ||
locale, | ||
was_incremental_match, | ||
}) => { | ||
// Actually render the page as we would if this weren't an initial load | ||
let page_data = get_page_for_template( | ||
GetPageProps::<M, T> { | ||
raw_path: &path, | ||
locale: &locale, | ||
was_incremental_match, | ||
req: http_req, | ||
global_state: &global_state, | ||
immutable_store: &immutable_store, | ||
mutable_store: &mutable_store, | ||
translations_manager: &translations_manager, | ||
}, | ||
template, | ||
) | ||
.await; | ||
let page_data = match page_data { | ||
Ok(page_data) => page_data, | ||
// We parse the error to return an appropriate status code | ||
Err(err) => { | ||
return html_err(err_to_status_code(&err), &fmt_err(&err)); | ||
} | ||
}; | ||
|
||
let final_html = html_shell | ||
.as_ref() | ||
.clone() | ||
.page_data(&page_data, &global_state) | ||
.to_string(); | ||
|
||
// http_res.content_type("text/html"); | ||
// Generate and add HTTP headers | ||
let mut header_map = HeaderMap::new(); | ||
for (key, val) in template.get_headers(page_data.state) { | ||
header_map.insert(key.unwrap(), val); | ||
} | ||
|
||
(StatusCode::OK, header_map, Html(final_html)) | ||
} | ||
// For locale detection, we don't know the user's locale, so there's not much we can do except send down the app shell, which will do the rest and fetch from `.perseus/page/...` | ||
RouteVerdictAtomic::LocaleDetection(path) => { | ||
// We use a `302 Found` status code to indicate a redirect | ||
// We 'should' generate a `Location` field for the redirect, but it's not RFC-mandated, so we can use the app shell | ||
( | ||
StatusCode::FOUND, | ||
HeaderMap::new(), | ||
Html( | ||
html_shell | ||
.as_ref() | ||
.clone() | ||
.locale_redirection_fallback( | ||
// We'll redirect the user to the default locale | ||
&format!( | ||
"{}/{}/{}", | ||
get_path_prefix_server(), | ||
opts.locales.default, | ||
path | ||
), | ||
) | ||
.to_string(), | ||
), | ||
) | ||
} | ||
RouteVerdictAtomic::NotFound => html_err(404, "page not found"), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#![doc = include_str!("../README.proj.md")] | ||
/*! | ||
## Packages | ||
This is the API documentation for the `perseus-axum` package, which allows Perseus apps to run on Axum. Note that Perseus mostly uses [the book](https://arctic-hen7.github.io/perseus/en-US) for | ||
documentation, and this should mostly be used as a secondary reference source. You can also find full usage examples [here](https://github.com/arctic-hen7/perseus/tree/main/examples). | ||
*/ | ||
|
||
#![deny(missing_docs)] | ||
|
||
// This integration doesn't need to convert request types, because we can get them straight out of Axum and then just delete the bodies | ||
mod initial_load; | ||
mod page_data; | ||
mod router; | ||
mod translations; | ||
|
||
pub use crate::router::get_router; | ||
pub use perseus::internal::serve::ServerOptions; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
use axum::{ | ||
body::Body, | ||
extract::{Path, Query}, | ||
http::{HeaderMap, StatusCode}, | ||
}; | ||
use fmterr::fmt_err; | ||
use perseus::{ | ||
errors::err_to_status_code, | ||
internal::{ | ||
i18n::TranslationsManager, | ||
serve::{get_page_for_template, GetPageProps, ServerOptions}, | ||
}, | ||
stores::{ImmutableStore, MutableStore}, | ||
Request, | ||
}; | ||
use serde::Deserialize; | ||
use std::sync::Arc; | ||
|
||
// Note: this is the same as for the Actix Web integration, but other frameworks may handle parsing query parameters differntly, so this shouldn't be integrated into the core library | ||
#[derive(Deserialize)] | ||
pub struct PageDataReq { | ||
pub template_name: String, | ||
pub was_incremental_match: bool, | ||
} | ||
|
||
#[allow(clippy::too_many_arguments)] // Because of how Axum extractors work, we don't exactly have a choice | ||
pub async fn page_handler<M: MutableStore, T: TranslationsManager>( | ||
Path(path_parts): Path<Vec<String>>, // From this, we can extract the locale and the path tail (the page path, which *does* have slashes) | ||
Query(PageDataReq { | ||
template_name, | ||
was_incremental_match, | ||
}): Query<PageDataReq>, | ||
// This works without any conversion because Axum allows us to directly get an `http::Request` out! | ||
http_req: perseus::http::Request<Body>, | ||
opts: Arc<ServerOptions>, | ||
immutable_store: Arc<ImmutableStore>, | ||
mutable_store: Arc<M>, | ||
translations_manager: Arc<T>, | ||
global_state: Arc<Option<String>>, | ||
) -> (StatusCode, HeaderMap, String) { | ||
// Separate the locale from the rest of the page name | ||
let locale = &path_parts[0]; | ||
let path = path_parts[1..] | ||
.iter() | ||
.map(|x| x.as_str()) | ||
.collect::<Vec<&str>>() | ||
.join("/"); | ||
// Axum's paths have leading slashes | ||
let path = path.strip_prefix('/').unwrap(); | ||
|
||
let templates = &opts.templates_map; | ||
// Check if the locale is supported | ||
if opts.locales.is_supported(locale) { | ||
// Warp doesn't let us specify that all paths should end in `.json`, so we'll manually strip that | ||
let path = path.strip_suffix(".json").unwrap(); | ||
// Get the template to use | ||
let template = templates.get(&template_name); | ||
let template = match template { | ||
Some(template) => template, | ||
None => { | ||
// We know the template has been pre-routed and should exist, so any failure here is a 500 | ||
return ( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
HeaderMap::new(), | ||
"template not found".to_string(), | ||
); | ||
} | ||
}; | ||
// Convert the request into one palatable for Perseus (which doesn't have the body attached) | ||
let http_req = Request::from_parts(http_req.into_parts().0, ()); | ||
let page_data = get_page_for_template( | ||
GetPageProps::<M, T> { | ||
raw_path: path, | ||
locale, | ||
was_incremental_match, | ||
req: http_req, | ||
global_state: &global_state, | ||
immutable_store: &immutable_store, | ||
mutable_store: &mutable_store, | ||
translations_manager: &translations_manager, | ||
}, | ||
template, | ||
) | ||
.await; | ||
match page_data { | ||
Ok(page_data) => { | ||
// http_res.content_type("text/html"); | ||
// Generate and add HTTP headers | ||
let mut header_map = HeaderMap::new(); | ||
for (key, val) in template.get_headers(page_data.state.clone()) { | ||
header_map.insert(key.unwrap(), val); | ||
} | ||
|
||
let page_data_str = serde_json::to_string(&page_data).unwrap(); | ||
|
||
(StatusCode::OK, header_map, page_data_str) | ||
} | ||
// We parse the error to return an appropriate status code | ||
Err(err) => ( | ||
StatusCode::from_u16(err_to_status_code(&err)).unwrap(), | ||
HeaderMap::new(), | ||
fmt_err(&err), | ||
), | ||
} | ||
} else { | ||
( | ||
StatusCode::NOT_FOUND, | ||
HeaderMap::new(), | ||
"locale not supported".to_string(), | ||
) | ||
} | ||
} |
Oops, something went wrong.