From dbe8207eaac86e32fedacc0816c0b1a48512709d Mon Sep 17 00:00:00 2001 From: arctic_hen7 Date: Thu, 2 Jun 2022 20:14:06 +1000 Subject: [PATCH] feat: add axum integration (#146) * 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 15187b55c8913c636b5d121be7e293dbc4081e86. * 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` 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 `` (#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 * 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> --- bonnie.toml | 5 +- .../core/basic/.perseus/server/Cargo.toml | 4 +- .../core/basic/.perseus/server/src/main.rs | 20 +++ packages/perseus-axum/Cargo.toml | 28 ++++ packages/perseus-axum/README.md | 5 + packages/perseus-axum/README.proj.md | 1 + packages/perseus-axum/src/initial_load.rs | 140 ++++++++++++++++++ packages/perseus-axum/src/lib.rs | 18 +++ packages/perseus-axum/src/page_data.rs | 112 ++++++++++++++ packages/perseus-axum/src/router.rs | 131 ++++++++++++++++ packages/perseus-axum/src/translations.rs | 26 ++++ packages/perseus-cli/src/parse.rs | 3 + packages/perseus-warp/src/initial_load.rs | 2 +- packages/perseus-warp/src/lib.rs | 2 +- 14 files changed, 493 insertions(+), 4 deletions(-) create mode 100644 packages/perseus-axum/Cargo.toml create mode 100644 packages/perseus-axum/README.md create mode 120000 packages/perseus-axum/README.proj.md create mode 100644 packages/perseus-axum/src/initial_load.rs create mode 100644 packages/perseus-axum/src/lib.rs create mode 100644 packages/perseus-axum/src/page_data.rs create mode 100644 packages/perseus-axum/src/router.rs create mode 100644 packages/perseus-axum/src/translations.rs diff --git a/bonnie.toml b/bonnie.toml index f05be6638f..2952fee551 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -179,7 +179,8 @@ test.subcommands.example.args = [ "category", "example", "integration" ] test.subcommands.example.desc = "tests a single example with the given integration (assumes geckodriver running in background), use `--headless` to run headlessly" test.subcommands.example-all-integrations.cmd = [ "rust-script scripts/test.rs %category %example actix-web %%", - "rust-script scripts/test.rs %category %example warp %%" + "rust-script scripts/test.rs %category %example warp %%", + "rust-script scripts/test.rs %category %example axum %%" ] test.subcommands.example-all-integrations.args = [ "category", "example" ] test.subcommands.example-all-integrations.desc = "tests a single example with all integrations (assumes geckodriver running in background), use `--headless` to run headlessly" @@ -221,6 +222,8 @@ publish.cmd = [ "cd ../perseus-actix-web", "cargo publish %%", "cd ../perseus-warp", + "cargo publish %%", + "cd ../perseus-axum", "cargo publish %%" ] publish.desc = "publishes all packages to crates.io (needs branch 'stable', Linux only)" diff --git a/examples/core/basic/.perseus/server/Cargo.toml b/examples/core/basic/.perseus/server/Cargo.toml index b86caaf355..cf27923545 100644 --- a/examples/core/basic/.perseus/server/Cargo.toml +++ b/examples/core/basic/.perseus/server/Cargo.toml @@ -12,19 +12,21 @@ edition = "2021" perseus = { path = "../../../../../packages/perseus", features = [ "server-side" ] } perseus-actix-web = { path = "../../../../../packages/perseus-actix-web", optional = true } perseus-warp = { path = "../../../../../packages/perseus-warp", optional = true } +perseus-axum = { path = "../../../../../packages/perseus-axum", optional = true } perseus-engine = { path = "../" } actix-web = { version = "=4.0.0-rc.3", optional = true } actix-http = { version = "=3.0.0-rc.2", optional = true } # Without this, Actix can introduce breaking changes in a dependency tree # actix-router = { version = "=0.5.0-rc.3", optional = true } futures = "0.3" warp = { package = "warp-fix-171", version = "0.3", optional = true } -# TODO Choose features here tokio = { version = "1", optional = true, features = [ "macros", "rt-multi-thread" ] } # We don't need this for Actix Web +axum = { version = "0.5", optional = true } # This binary can use any of the server integrations [features] integration-actix-web = [ "perseus-actix-web", "actix-web", "actix-http" ] integration-warp = [ "perseus-warp", "warp", "tokio" ] +integration-axum = [ "perseus-axum", "axum", "tokio" ] default = [ "integration-warp" ] diff --git a/examples/core/basic/.perseus/server/src/main.rs b/examples/core/basic/.perseus/server/src/main.rs index 46904b9a3f..51913506ac 100644 --- a/examples/core/basic/.perseus/server/src/main.rs +++ b/examples/core/basic/.perseus/server/src/main.rs @@ -48,6 +48,26 @@ async fn main() { warp::serve(routes).run(addr).await; } +// Integration: Axum +#[cfg(feature = "integration-axum")] +#[tokio::main] +async fn main() { + use perseus_axum::get_router; + use std::net::SocketAddr; + + let is_standalone = get_standalone_and_act(); + let props = get_props(is_standalone); + let (host, port) = get_host_and_port(); + let addr: SocketAddr = format!("{}:{}", host, port) + .parse() + .expect("Invalid address provided to bind to."); + let app = block_on(get_router(props)); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + /// Determines whether or not we're operating in standalone mode, and acts accordingly. This MUST be executed in the parent thread, as it switches the current directory. fn get_standalone_and_act() -> bool { // So we don't have to define a different `FsConfigManager` just for the server, we shift the execution context to the same level as everything else diff --git a/packages/perseus-axum/Cargo.toml b/packages/perseus-axum/Cargo.toml new file mode 100644 index 0000000000..16e8bfc8e0 --- /dev/null +++ b/packages/perseus-axum/Cargo.toml @@ -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 "] +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" diff --git a/packages/perseus-axum/README.md b/packages/perseus-axum/README.md new file mode 100644 index 0000000000..554673666f --- /dev/null +++ b/packages/perseus-axum/README.md @@ -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. diff --git a/packages/perseus-axum/README.proj.md b/packages/perseus-axum/README.proj.md new file mode 120000 index 0000000000..fe84005413 --- /dev/null +++ b/packages/perseus-axum/README.proj.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/perseus-axum/src/initial_load.rs b/packages/perseus-axum/src/initial_load.rs new file mode 100644 index 0000000000..8cd054d16f --- /dev/null +++ b/packages/perseus-axum/src/initial_load.rs @@ -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>, + error_pages: &ErrorPages, + html_shell: &HtmlShell, +) -> (StatusCode, HeaderMap, Html) { + 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( + http_req: perseus::http::Request, + opts: Arc, + html_shell: Arc, + render_cfg: Arc>, + immutable_store: Arc, + mutable_store: Arc, + translations_manager: Arc, + global_state: Arc>, +) -> (StatusCode, HeaderMap, Html) { + 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:: { + 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"), + } +} diff --git a/packages/perseus-axum/src/lib.rs b/packages/perseus-axum/src/lib.rs new file mode 100644 index 0000000000..3b07eba330 --- /dev/null +++ b/packages/perseus-axum/src/lib.rs @@ -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; diff --git a/packages/perseus-axum/src/page_data.rs b/packages/perseus-axum/src/page_data.rs new file mode 100644 index 0000000000..8b75d3501b --- /dev/null +++ b/packages/perseus-axum/src/page_data.rs @@ -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( + Path(path_parts): Path>, // 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, + // This works without any conversion because Axum allows us to directly get an `http::Request` out! + http_req: perseus::http::Request, + opts: Arc, + immutable_store: Arc, + mutable_store: Arc, + translations_manager: Arc, + global_state: Arc>, +) -> (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::>() + .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:: { + 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(), + ) + } +} diff --git a/packages/perseus-axum/src/router.rs b/packages/perseus-axum/src/router.rs new file mode 100644 index 0000000000..0b68c6e4ae --- /dev/null +++ b/packages/perseus-axum/src/router.rs @@ -0,0 +1,131 @@ +use crate::initial_load::initial_load_handler; +use crate::page_data::page_handler; +use crate::translations::translations_handler; +use axum::{ + http::StatusCode, + response::IntoResponse, + routing::{get, get_service}, + Router, +}; +use closure::closure; +use perseus::internal::serve::{get_render_cfg, ServerProps}; +use perseus::{internal::i18n::TranslationsManager, stores::MutableStore}; +use std::sync::Arc; +use tower_http::services::{ServeDir, ServeFile}; + +/// Gets the `Router` needed to configure an existing Axum app for Perseus, and should be provided after any other routes, as they include a wildcard +/// route. +pub async fn get_router( + ServerProps { + opts, + immutable_store, + mutable_store, + translations_manager, + global_state_creator, + }: ServerProps, +) -> Router { + let render_cfg = get_render_cfg(&immutable_store) + .await + .expect("Couldn't get render configuration!"); + let index_with_render_cfg = opts.html_shell.clone(); + // Generate the global state + let global_state = global_state_creator + .get_build_state() + .await + .expect("Couldn't generate global state."); + + let immutable_store = Arc::new(immutable_store); + let mutable_store = Arc::new(mutable_store); + let translations_manager = Arc::new(translations_manager); + let html_shell = Arc::new(index_with_render_cfg); + let render_cfg = Arc::new(render_cfg); + let global_state = Arc::new(global_state); + + let static_dir = opts.static_dir.clone(); + let static_aliases = opts.static_aliases.clone(); + + let router = Router::new() + .route( + "/.perseus/bundle.js", + get_service(ServeFile::new(opts.js_bundle.clone())).handle_error(handle_fs_error), + ) + .route( + "/.perseus/bundle.wasm", + get_service(ServeFile::new(opts.wasm_bundle.clone())).handle_error(handle_fs_error), + ) + .route( + "/.perseus/bundle.wasm.js", + get_service(ServeFile::new(opts.wasm_js_bundle.clone())).handle_error(handle_fs_error), + ) + .route( + "/.perseus/snippets/*path", + get_service(ServeDir::new(opts.snippets.clone())).handle_error(handle_fs_error), + ); + let opts = Arc::new(opts); + let mut router = router + .route( + "/.perseus/translations/:locale", + get(closure!(clone opts, clone translations_manager, |path| translations_handler::(path, opts, translations_manager))), + ) + .route("/.perseus/page/:locale/*tail", get( + closure!( + clone opts, + clone immutable_store, + clone mutable_store, + clone translations_manager, + clone global_state, + |path, query, http_req| + page_handler::( + path, + query, + http_req, + opts, + immutable_store, + mutable_store, + translations_manager, + global_state + ) + ) + )); + // Only add the static content directory route if such a directory is being used + if let Some(static_dir) = static_dir { + router = router.nest( + "/.perseus/static", + get_service(ServeDir::new(static_dir)).handle_error(handle_fs_error), + ) + } + // Now add support for serving static aliases + for (url, static_path) in static_aliases.iter() { + // Note that `static_path` is already relative to the right place (`.perseus/server/`) + router = router.route( + url, // This comes with a leading forward slash! + get_service(ServeFile::new(static_path)).handle_error(handle_fs_error), + ); + } + // And add the fallback for initial loads + router.fallback(get(closure!( + clone opts, + clone html_shell, + clone render_cfg, + clone immutable_store, + clone mutable_store, + clone translations_manager, + clone global_state, + |http_req| + initial_load_handler::( + http_req, + opts, + html_shell, + render_cfg, + immutable_store, + mutable_store, + translations_manager, + global_state + ) + ))) +} + +// TODO Review if there's anything more to do here +async fn handle_fs_error(_err: std::io::Error) -> impl IntoResponse { + (StatusCode::INTERNAL_SERVER_ERROR, "Couldn't serve file.") +} diff --git a/packages/perseus-axum/src/translations.rs b/packages/perseus-axum/src/translations.rs new file mode 100644 index 0000000000..a7c0c22d1e --- /dev/null +++ b/packages/perseus-axum/src/translations.rs @@ -0,0 +1,26 @@ +use axum::{extract::Path, http::StatusCode}; +use fmterr::fmt_err; +use perseus::internal::{i18n::TranslationsManager, serve::ServerOptions}; +use std::sync::Arc; + +pub async fn translations_handler( + Path(locale): Path, + opts: Arc, + translations_manager: Arc, +) -> (StatusCode, String) { + // Check if the locale is supported + if opts.locales.is_supported(&locale) { + // We know that the locale is supported, so any failure to get translations is a 500 + let translations = translations_manager + .get_translations_str_for_locale(locale.to_string()) + .await; + let translations = match translations { + Ok(translations) => translations, + Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, fmt_err(&err)), + }; + + (StatusCode::OK, translations) + } else { + (StatusCode::NOT_FOUND, "locale not supported".to_string()) + } +} diff --git a/packages/perseus-cli/src/parse.rs b/packages/perseus-cli/src/parse.rs index 9838b6267b..6c67f7cf5d 100644 --- a/packages/perseus-cli/src/parse.rs +++ b/packages/perseus-cli/src/parse.rs @@ -22,6 +22,7 @@ pub struct Opts { pub enum Integration { ActixWeb, Warp, + Axum, } // We use an `enum` for this so we don't get errors from Cargo about non-existent feature flags, overly verbose but fails quickly impl std::str::FromStr for Integration { @@ -31,6 +32,7 @@ impl std::str::FromStr for Integration { match s { "actix-web" => Ok(Self::ActixWeb), "warp" => Ok(Self::Warp), + "axum" => Ok(Self::Axum), _ => Err("invalid integration name".into()), } } @@ -40,6 +42,7 @@ impl ToString for Integration { match self { Self::ActixWeb => "actix-web".to_string(), Self::Warp => "warp".to_string(), + Self::Axum => "axum".to_string(), } } } diff --git a/packages/perseus-warp/src/initial_load.rs b/packages/perseus-warp/src/initial_load.rs index de4cd6d66b..8fdb2d5d6b 100644 --- a/packages/perseus-warp/src/initial_load.rs +++ b/packages/perseus-warp/src/initial_load.rs @@ -107,7 +107,7 @@ pub async fn initial_load_handler( // 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 Response::builder() - .status(200) + .status(302) // NOTE: Changed this from 200 (I think that was a mistake...) .body( html_shell .as_ref() diff --git a/packages/perseus-warp/src/lib.rs b/packages/perseus-warp/src/lib.rs index db5088c473..b2bdd1f506 100644 --- a/packages/perseus-warp/src/lib.rs +++ b/packages/perseus-warp/src/lib.rs @@ -2,7 +2,7 @@ /*! ## Packages -This is the API documentation for the `perseus-actix-web` package, which allows Perseus apps to run on Actix Web. Note that Perseus mostly uses [the book](https://arctic-hen7.github.io/perseus/en-US) for +This is the API documentation for the `perseus-warp` package, which allows Perseus apps to run on Warp. 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). */