From 67d91643afe4a02debc5e0ed59cc042298cb8541 Mon Sep 17 00:00:00 2001 From: Miroito Date: Tue, 7 Mar 2023 18:43:49 +0100 Subject: [PATCH 01/13] perseus custom rocket server --- packages/perseus-integration/Cargo.toml | 2 + packages/perseus-integration/src/lib.rs | 2 + packages/perseus-rocket/Cargo.toml | 24 ++ packages/perseus-rocket/README.proj.md | 1 + packages/perseus-rocket/src/lib.rs | 329 ++++++++++++++++++++++++ 5 files changed, 358 insertions(+) create mode 100644 packages/perseus-rocket/Cargo.toml create mode 120000 packages/perseus-rocket/README.proj.md create mode 100644 packages/perseus-rocket/src/lib.rs diff --git a/packages/perseus-integration/Cargo.toml b/packages/perseus-integration/Cargo.toml index 6d8ff90038..fd55a1cb8e 100644 --- a/packages/perseus-integration/Cargo.toml +++ b/packages/perseus-integration/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" perseus-actix-web = { path = "../perseus-actix-web", features = [ "dflt-server" ], optional = true } perseus-warp = { path = "../perseus-warp", features = [ "dflt-server" ], optional = true } perseus-axum = { path = "../perseus-axum", features = [ "dflt-server" ], optional = true } +perseus-rocket = { path = "../perseus-rocket", features = [ "dflt-server" ], optional = true } [features] default = [ "warp" ] @@ -16,3 +17,4 @@ default = [ "warp" ] actix-web = [ "perseus-actix-web" ] warp = [ "perseus-warp" ] axum = [ "perseus-axum" ] +rocket = [ "perseus-rocket" ] diff --git a/packages/perseus-integration/src/lib.rs b/packages/perseus-integration/src/lib.rs index 8f67d7ef1d..3a2046cf72 100644 --- a/packages/perseus-integration/src/lib.rs +++ b/packages/perseus-integration/src/lib.rs @@ -4,5 +4,7 @@ pub use perseus_actix_web::dflt_server; #[cfg(feature = "axum")] pub use perseus_axum::dflt_server; +#[cfg(feature = "rocket")] +pub use perseus_rocket::dflt_server; #[cfg(feature = "warp")] pub use perseus_warp::dflt_server; diff --git a/packages/perseus-rocket/Cargo.toml b/packages/perseus-rocket/Cargo.toml new file mode 100644 index 0000000000..32ee25a606 --- /dev/null +++ b/packages/perseus-rocket/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "perseus-rocket" +version = "0.4.0-beta.21" +edition = "2021" +description = "An integration that makes the Perseus framework easy to use with Rocket." +authors = ["Miroito "] +license = "MIT" +repository = "https://github.com/framesurge/perseus" +homepage = "https://framesurge.sh/perseus" +readme = "./README.md" +keywords = ["wasm", "frontend", "webdev", "ssg", "ssr"] +categories = ["wasm", "web-programming::http-server", "development-tools", "asynchronous", "gui"] + +[dependencies] +perseus = { path = "../perseus", version = "0.4.0-beta.21"} +rocket = "0.5.0-rc.2" + +[features] +# Enables the default server configuration, which provides a convenience function if you're not adding any extra routes +dflt-server = [] + +[package.metadata.docs.rs] +rustc-args = ["--cfg=engine"] +rustdoc-args = ["--cfg=engine"] diff --git a/packages/perseus-rocket/README.proj.md b/packages/perseus-rocket/README.proj.md new file mode 120000 index 0000000000..fe84005413 --- /dev/null +++ b/packages/perseus-rocket/README.proj.md @@ -0,0 +1 @@ +../../README.md \ No newline at end of file diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs new file mode 100644 index 0000000000..88d7adfce8 --- /dev/null +++ b/packages/perseus-rocket/src/lib.rs @@ -0,0 +1,329 @@ +#![doc = include_str!("../README.proj.md")] + +/*! +## Packages + +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://framesurge.sh/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/framesurge/perseus/tree/main/examples). +*/ + +#![cfg(engine)] +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] + +use std::{io::Cursor, path::Path, sync::Arc}; + +use perseus::{ + i18n::TranslationsManager, + path::PathMaybeWithLocale, + server::ServerOptions, + stores::MutableStore, + turbine::{ApiResponse as PerseusApiResponse, Turbine}, +}; +use rocket::{ + fs::{FileServer, NamedFile}, + get, + http::{Method, Status}, + response::Responder, + route::{Handler, Outcome}, + routes, + tokio::fs::File, + Build, Data, Request, Response, Rocket, Route, State, +}; + +// ----- Newtype wrapper for response implementation ----- + +#[derive(Debug)] +struct ApiResponse(PerseusApiResponse); +impl From for ApiResponse { + fn from(val: PerseusApiResponse) -> Self { + Self(val) + } +} +impl<'r> Responder<'r, 'static> for ApiResponse { + fn respond_to(self, _request: &'r rocket::Request<'_>) -> rocket::response::Result<'static> { + let mut resp_build = Response::build(); + resp_build + .status(rocket::http::Status { + code: self.0.status.into(), + }) + .sized_body(self.0.body.len(), Cursor::new(self.0.body)); + + for h in self.0.headers.iter() { + // Headers that contain non-visible ascii characters are chopped off here in order to make the conversion + if let Ok(value) = h.1.to_str() { + resp_build.raw_header(h.0.to_string(), value.to_string()); + } + } + + resp_build.ok() + } +} + +// ----- Simple routes ----- + +#[get("/.perseus/bundle.js")] +async fn get_js_bundle(opts: &State) -> std::io::Result { + NamedFile::open(opts.js_bundle.clone()).await +} + +#[get("/.perseus/bundle.wasm")] +async fn get_wasm_bundle(opts: &State) -> std::io::Result { + NamedFile::open(opts.wasm_bundle.clone()).await +} + +#[get("/.perseus/bundle.wasm.js")] +async fn get_wasm_js_bundle(opts: &State) -> std::io::Result { + NamedFile::open(opts.wasm_js_bundle.clone()).await +} + +// ----- Turbine dependant route handlers ----- + +async fn perseus_locale<'r, M, T>(req: &'r Request<'_>, turbine: Arc<&Turbine>) -> Outcome<'r> +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + match req.routed_segment(0) { + Some(locale) => Outcome::from(req, ApiResponse(turbine.get_translations(locale).await)), + _ => Outcome::Failure(Status::BadRequest), + } +} + +async fn perseus_initial_load_handler<'r, M, T>( + req: &'r Request<'_>, + turbine: Arc<&Turbine>, +) -> Outcome<'r> +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + // Since this is a fallback handler, we have to do everything from the request + // itself + let path = req.uri().path().to_string(); + + let mut http_req = rocket::http::hyper::Request::builder(); + http_req = http_req.method("GET"); + for h in req.headers().iter() { + http_req = http_req.header(h.name.to_string(), h.value.to_string()); + } + + match http_req.body(()) { + Ok(r) => Outcome::from( + req, + ApiResponse(turbine.get_initial_load(PathMaybeWithLocale(path), r).await), + ), + _ => Outcome::Failure(Status::BadRequest), + } +} + +async fn perseus_subsequent_load_handler<'r, M, T>( + req: &'r Request<'_>, + turbine: Arc<&Turbine>, +) -> Outcome<'r> +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + let locale_opt = req.routed_segment(0); + let entity_name_opt = req + .query_value::<&str>("entity_name") + .and_then(|res| res.ok()); + let was_incremental_match_opt = req + .query_value::("was_incremental_match") + .and_then(|res| res.ok()); + + if entity_name_opt.is_none() || locale_opt.is_none() || was_incremental_match_opt.is_none() { + return Outcome::Failure(Status::BadRequest); + } + + // All unwraps are guarded by the condition above + let entity_name = entity_name_opt.unwrap().to_string(); + let locale = locale_opt.unwrap().to_string(); + let was_incremental_match = was_incremental_match_opt.unwrap(); + + let raw_path = req.routed_segments(1..).collect::>().join("/"); + + let mut http_req = rocket::http::hyper::Request::builder(); + http_req = http_req.method("GET"); + for h in req.headers().iter() { + http_req = http_req.header(h.name.to_string(), h.value.to_string()); + } + + match http_req.body(()) { + Ok(r) => Outcome::from( + req, + ApiResponse( + turbine + .get_subsequent_load( + perseus::path::PathWithoutLocale(raw_path), + locale, + entity_name, + was_incremental_match, + r, + ) + .await, + ), + ), + _ => Outcome::Failure(Status::BadRequest), + } +} + +// ----- Rocket Hanlder trait implementation ----- + +#[derive(Clone)] +enum PerseusRouteKind<'a> { + Locale, + StaticAlias(&'a String), + IntialLoadHandler, + SubsequentLoadHandler, +} + +#[derive(Clone)] +struct RocketHandlerWithTurbine<'a, M, T> +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + turbine: Arc<&'a Turbine>, + perseus_route: PerseusRouteKind<'a>, +} + +#[rocket::async_trait] +impl Handler for RocketHandlerWithTurbine<'static, M, T> +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + async fn handle<'r>(&self, req: &'r Request<'_>, _data: Data<'r>) -> Outcome<'r> { + match self.perseus_route { + PerseusRouteKind::Locale => perseus_locale(req, self.turbine.clone()).await, + PerseusRouteKind::StaticAlias(static_alias) => { + perseus_static_alias(req, static_alias).await + } + PerseusRouteKind::IntialLoadHandler => { + perseus_initial_load_handler(req, self.turbine.clone()).await + } + PerseusRouteKind::SubsequentLoadHandler => { + perseus_subsequent_load_handler(req, self.turbine.clone()).await + } + } + } +} + +async fn perseus_static_alias<'r>(req: &'r Request<'_>, static_alias: &String) -> Outcome<'r> { + match File::open(static_alias).await { + Ok(file) => Outcome::from(req, file), + _ => Outcome::Failure(Status::NotFound), + } +} + +// ----- Integration code ----- + +/// Configures an Rocket Web app for Perseus. +/// This returns a rocket at the build stage that can be built upon further with more routes, fairings etc... +pub async fn perseus_base_app( + turbine: &'static Turbine, + opts: ServerOptions, +) -> Rocket +where + M: MutableStore + 'static, + T: TranslationsManager + 'static, +{ + let arc_turbine = Arc::new(turbine); + + let get_locale = Route::new( + Method::Get, + "/.perseus/translations/", + RocketHandlerWithTurbine { + turbine: arc_turbine.clone(), + perseus_route: PerseusRouteKind::Locale, + }, + ); + + // Since this route matches everything, its rank has been set to 0, + // That means that it will be used after routes that have a rank inferior to 0 forward + // see https://rocket.rs/v0.5-rc/guide/requests/#default-ranking + let get_initial_load_handler = Route::ranked( + 0, + Method::Get, + "/", + RocketHandlerWithTurbine { + turbine: arc_turbine.clone(), + perseus_route: PerseusRouteKind::IntialLoadHandler, + }, + ); + + let get_subsequent_load_handler = Route::new( + Method::Get, + "/.perseus/page/", + RocketHandlerWithTurbine { + turbine: arc_turbine.clone(), + perseus_route: PerseusRouteKind::SubsequentLoadHandler, + }, + ); + + let mut app = rocket::build() + .manage(opts.clone()) + .mount( + "/", + routes![get_js_bundle, get_wasm_bundle, get_wasm_js_bundle], + ) + .mount( + "/", + vec![ + get_locale, + get_subsequent_load_handler, + get_initial_load_handler, + ], + ); + + if Path::new(&opts.snippets).exists() { + app = app.mount("/.perseus/snippets", FileServer::from(opts.snippets)) + } + + if turbine.static_dir.exists() { + app = app.mount("/.perseus/static", FileServer::from(&turbine.static_dir)) + } + + let mut static_aliases: Vec = vec![]; + + for (url, static_path) in turbine.static_aliases.iter() { + let route = Route::new( + Method::Get, + url, + RocketHandlerWithTurbine { + turbine: arc_turbine.clone(), + perseus_route: PerseusRouteKind::StaticAlias(static_path), + }, + ); + static_aliases.push(route) + } + + app = app.mount("/", static_aliases); + + app +} + +// ----- Default server ----- + +/// Creates and starts the default Perseus server with Rocket. This should be run +/// in a `main` function annotated with `#[tokio::main]` (which requires the +/// `macros` and `rt-multi-thread` features on the `tokio` dependency). +#[cfg(feature = "dflt-server")] +pub async fn dflt_server( + turbine: &'static Turbine, + opts: ServerOptions, + (_host, port): (String, u16), +) { + let mut app = perseus_base_app(turbine, opts).await; + + let mut config = rocket::Config::default(); + config.port = port; + app = app.configure(config); + + match app.launch().await { + Err(e) => println!("Error lauching rocket app: {}", e), + _ => (), + } +} From c5445eaffe99e6cdbafabc2c73f3139ed47e80c6 Mon Sep 17 00:00:00 2001 From: Miroito Date: Tue, 7 Mar 2023 18:51:26 +0100 Subject: [PATCH 02/13] Add example --- .../custom_server_rocket/.integration_locked | 0 examples/core/custom_server_rocket/Cargo.toml | 22 ++++++++++ examples/core/custom_server_rocket/README.md | 3 ++ .../core/custom_server_rocket/src/main.rs | 43 +++++++++++++++++++ .../src/templates/index.rs | 23 ++++++++++ .../custom_server_rocket/src/templates/mod.rs | 1 + packages/perseus-rocket/README.md | 5 +++ 7 files changed, 97 insertions(+) create mode 100644 examples/core/custom_server_rocket/.integration_locked create mode 100644 examples/core/custom_server_rocket/Cargo.toml create mode 100644 examples/core/custom_server_rocket/README.md create mode 100644 examples/core/custom_server_rocket/src/main.rs create mode 100644 examples/core/custom_server_rocket/src/templates/index.rs create mode 100644 examples/core/custom_server_rocket/src/templates/mod.rs create mode 100644 packages/perseus-rocket/README.md diff --git a/examples/core/custom_server_rocket/.integration_locked b/examples/core/custom_server_rocket/.integration_locked new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/core/custom_server_rocket/Cargo.toml b/examples/core/custom_server_rocket/Cargo.toml new file mode 100644 index 0000000000..49c8aed12e --- /dev/null +++ b/examples/core/custom_server_rocket/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "custom_server_rocket" +version = "0.4.0-beta.21" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +perseus = { path = "../../../packages/perseus", features = ["hydrate"] } +sycamore = "^0.8.1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[target.'cfg(engine)'.dev-dependencies] +fantoccini = "0.17" + +[target.'cfg(engine)'.dependencies] +tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread"] } +perseus-rocket = { path = "../../../packages/perseus-rocket", features = [ "dflt-server" ] } +rocket = "0.5.0-rc.2" + +[target.'cfg(client)'.dependencies] diff --git a/examples/core/custom_server_rocket/README.md b/examples/core/custom_server_rocket/README.md new file mode 100644 index 0000000000..7f4f84015f --- /dev/null +++ b/examples/core/custom_server_rocket/README.md @@ -0,0 +1,3 @@ +# Custom Server Rocket Example + +This is an example of setting up a custom server using rocket with Perseus, with one example api route. diff --git a/examples/core/custom_server_rocket/src/main.rs b/examples/core/custom_server_rocket/src/main.rs new file mode 100644 index 0000000000..7a3e6a600e --- /dev/null +++ b/examples/core/custom_server_rocket/src/main.rs @@ -0,0 +1,43 @@ +mod templates; + +use perseus::prelude::*; + +#[cfg(engine)] +mod api { + use rocket::get; + + #[get("/hello")] + pub fn hello() -> String { + "Hello from an api".to_string() + } +} + +#[cfg(engine)] +pub async fn dflt_server< + M: perseus::stores::MutableStore + 'static, + T: perseus::i18n::TranslationsManager + 'static, +>( + turbine: &'static perseus::turbine::Turbine, + opts: perseus::server::ServerOptions, + (_host, port): (String, u16), +) { + use perseus_rocket::perseus_base_app; + use rocket::routes; + let mut app = perseus_base_app(turbine, opts).await; + app = app.mount("/api", routes![api::hello]); + let mut config = rocket::Config::default(); + config.port = port; + app = app.configure(config); + + match app.launch().await { + Err(e) => println!("Error lauching rocket app: {}", e), + _ => (), + } +} + +#[perseus::main(dflt_server)] +pub fn main() -> PerseusApp { + PerseusApp::new() + .template(crate::templates::index::get_template()) + .error_views(ErrorViews::unlocalized_development_default()) +} diff --git a/examples/core/custom_server_rocket/src/templates/index.rs b/examples/core/custom_server_rocket/src/templates/index.rs new file mode 100644 index 0000000000..3f9f9706b6 --- /dev/null +++ b/examples/core/custom_server_rocket/src/templates/index.rs @@ -0,0 +1,23 @@ +use perseus::prelude::*; +use sycamore::prelude::*; + +fn index_page(cx: Scope) -> View { + let counter = create_signal(cx, 0); + view! { cx, + (counter.get()) + br () {} + button (on:click=move |_| {counter.set(*counter.get() - 1)}) { "remove 1"} + button (on:click=move |_| {counter.set(*counter.get() + 1)}) { "Add 1"} + } +} + +#[engine_only_fn] +fn head(cx: Scope) -> View { + view! { cx, + title { "Index Page" } + } +} + +pub fn get_template() -> Template { + Template::build("index").view(index_page).head(head).build() +} diff --git a/examples/core/custom_server_rocket/src/templates/mod.rs b/examples/core/custom_server_rocket/src/templates/mod.rs new file mode 100644 index 0000000000..33edc959c9 --- /dev/null +++ b/examples/core/custom_server_rocket/src/templates/mod.rs @@ -0,0 +1 @@ +pub mod index; diff --git a/packages/perseus-rocket/README.md b/packages/perseus-rocket/README.md new file mode 100644 index 0000000000..23b6565587 --- /dev/null +++ b/packages/perseus-rocket/README.md @@ -0,0 +1,5 @@ +# Perseus Rocket Integration + +This is the official [Perseus](https://github.com/arctic-hen7/perseus) integration for making serving your apps on [Rocket](https://rocket.rs/) significantly easier! + +If you're new to Perseus, you should check out [the core package](https://github.com/arctic-hen7/perseus) first. From b1437502f6416e151777ef81029c40b239eabf43 Mon Sep 17 00:00:00 2001 From: Miroito Date: Thu, 9 Mar 2023 12:58:13 +0100 Subject: [PATCH 03/13] Fix issues with routes rank --- bonnie.toml | 3 +- packages/perseus-rocket/src/lib.rs | 52 ++++++++++++++---------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/bonnie.toml b/bonnie.toml index 4c7bf9d6af..21916304ca 100644 --- a/bonnie.toml +++ b/bonnie.toml @@ -197,7 +197,8 @@ test.subcommands.cli.desc = "runs the cli tests (all are long-running, this will test.subcommands.example-all-integrations.cmd = [ "EXAMPLE_INTEGRATION=actix-web bonnie dev example %category %example test", "EXAMPLE_INTEGRATION=warp bonnie dev example %category %example test", - "EXAMPLE_INTEGRATION=axum bonnie dev example %category %example test" + "EXAMPLE_INTEGRATION=axum bonnie dev example %category %example test", + "EXAMPLE_INTEGRATION=rocket bonnie dev example %category %example test" ] 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)" diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 88d7adfce8..07f947906b 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -50,7 +50,8 @@ impl<'r> Responder<'r, 'static> for ApiResponse { .sized_body(self.0.body.len(), Cursor::new(self.0.body)); for h in self.0.headers.iter() { - // Headers that contain non-visible ascii characters are chopped off here in order to make the conversion + // Headers that contain non-visible ascii characters are chopped off here in + // order to make the conversion if let Ok(value) = h.1.to_str() { resp_build.raw_header(h.0.to_string(), value.to_string()); } @@ -62,17 +63,17 @@ impl<'r> Responder<'r, 'static> for ApiResponse { // ----- Simple routes ----- -#[get("/.perseus/bundle.js")] +#[get("/bundle.js")] async fn get_js_bundle(opts: &State) -> std::io::Result { NamedFile::open(opts.js_bundle.clone()).await } -#[get("/.perseus/bundle.wasm")] +#[get("/bundle.wasm")] async fn get_wasm_bundle(opts: &State) -> std::io::Result { NamedFile::open(opts.wasm_bundle.clone()).await } -#[get("/.perseus/bundle.wasm.js")] +#[get("/bundle.wasm.js")] async fn get_wasm_js_bundle(opts: &State) -> std::io::Result { NamedFile::open(opts.wasm_js_bundle.clone()).await } @@ -84,7 +85,7 @@ where M: MutableStore + 'static, T: TranslationsManager + 'static, { - match req.routed_segment(0) { + match req.routed_segment(1) { Some(locale) => Outcome::from(req, ApiResponse(turbine.get_translations(locale).await)), _ => Outcome::Failure(Status::BadRequest), } @@ -125,7 +126,7 @@ where M: MutableStore + 'static, T: TranslationsManager + 'static, { - let locale_opt = req.routed_segment(0); + let locale_opt = req.routed_segment(1); let entity_name_opt = req .query_value::<&str>("entity_name") .and_then(|res| res.ok()); @@ -142,7 +143,7 @@ where let locale = locale_opt.unwrap().to_string(); let was_incremental_match = was_incremental_match_opt.unwrap(); - let raw_path = req.routed_segments(1..).collect::>().join("/"); + let raw_path = req.routed_segments(2..).collect::>().join("/"); let mut http_req = rocket::http::hyper::Request::builder(); http_req = http_req.method("GET"); @@ -221,7 +222,8 @@ async fn perseus_static_alias<'r>(req: &'r Request<'_>, static_alias: &String) - // ----- Integration code ----- /// Configures an Rocket Web app for Perseus. -/// This returns a rocket at the build stage that can be built upon further with more routes, fairings etc... +/// This returns a rocket at the build stage that can be built upon further with +/// more routes, fairings etc... pub async fn perseus_base_app( turbine: &'static Turbine, opts: ServerOptions, @@ -234,18 +236,18 @@ where let get_locale = Route::new( Method::Get, - "/.perseus/translations/", + "/translations/", RocketHandlerWithTurbine { turbine: arc_turbine.clone(), perseus_route: PerseusRouteKind::Locale, }, ); - // Since this route matches everything, its rank has been set to 0, - // That means that it will be used after routes that have a rank inferior to 0 forward - // see https://rocket.rs/v0.5-rc/guide/requests/#default-ranking + // Since this route matches everything, its rank has been set to 100, + // That means that it will be used after routes that have a rank inferior to 100 + // forward, see https://rocket.rs/v0.5-rc/guide/requests/#default-ranking let get_initial_load_handler = Route::ranked( - 0, + 100, Method::Get, "/", RocketHandlerWithTurbine { @@ -256,27 +258,21 @@ where let get_subsequent_load_handler = Route::new( Method::Get, - "/.perseus/page/", + "/page/", RocketHandlerWithTurbine { turbine: arc_turbine.clone(), perseus_route: PerseusRouteKind::SubsequentLoadHandler, }, ); + let mut perseus_routes: Vec = + routes![get_js_bundle, get_wasm_js_bundle, get_wasm_bundle]; + perseus_routes.append(&mut vec![get_locale, get_subsequent_load_handler]); + let mut app = rocket::build() .manage(opts.clone()) - .mount( - "/", - routes![get_js_bundle, get_wasm_bundle, get_wasm_js_bundle], - ) - .mount( - "/", - vec![ - get_locale, - get_subsequent_load_handler, - get_initial_load_handler, - ], - ); + .mount("/.perseus/", perseus_routes) + .mount("/", vec![get_initial_load_handler]); if Path::new(&opts.snippets).exists() { app = app.mount("/.perseus/snippets", FileServer::from(opts.snippets)) @@ -307,8 +303,8 @@ where // ----- Default server ----- -/// Creates and starts the default Perseus server with Rocket. This should be run -/// in a `main` function annotated with `#[tokio::main]` (which requires the +/// Creates and starts the default Perseus server with Rocket. This should be +/// run in a `main` function annotated with `#[tokio::main]` (which requires the /// `macros` and `rt-multi-thread` features on the `tokio` dependency). #[cfg(feature = "dflt-server")] pub async fn dflt_server( From 47f7f42666e82471c9b1921b27fd7ac3ed3ed78a Mon Sep 17 00:00:00 2001 From: Miroito Date: Fri, 10 Mar 2023 19:32:36 +0100 Subject: [PATCH 04/13] Change unwraps to more idiomatic rust syntax --- packages/perseus-rocket/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 07f947906b..3b96c99a12 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -134,14 +134,11 @@ where .query_value::("was_incremental_match") .and_then(|res| res.ok()); - if entity_name_opt.is_none() || locale_opt.is_none() || was_incremental_match_opt.is_none() { - return Outcome::Failure(Status::BadRequest); - } - - // All unwraps are guarded by the condition above - let entity_name = entity_name_opt.unwrap().to_string(); - let locale = locale_opt.unwrap().to_string(); - let was_incremental_match = was_incremental_match_opt.unwrap(); + let (locale, entity_name, was_incremental_match) = + match (locale_opt, entity_name_opt, was_incremental_match_opt) { + (Some(l), Some(e), Some(w)) => (l.to_string(), e.to_string(), w), + _ => return Outcome::Failure(Status::BadRequest), + }; let raw_path = req.routed_segments(2..).collect::>().join("/"); From 3a08f491af8eda352b35e04e00fc10fd012d0c00 Mon Sep 17 00:00:00 2001 From: Miroito Date: Sun, 12 Mar 2023 19:08:07 +0100 Subject: [PATCH 05/13] Configure host in default function --- packages/perseus-rocket/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 3b96c99a12..8ef1994c90 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -307,12 +307,15 @@ where pub async fn dflt_server( turbine: &'static Turbine, opts: ServerOptions, - (_host, port): (String, u16), + (host, port): (String, u16), ) { + let addr = host.parse().expect("Invalid address provided to bind to."); + let mut app = perseus_base_app(turbine, opts).await; let mut config = rocket::Config::default(); config.port = port; + config.address = addr; app = app.configure(config); match app.launch().await { From 86f3b70019ed367a32c9edac384680fc17f1fbca Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 20 Mar 2023 16:31:50 +0100 Subject: [PATCH 06/13] remove unnecessary Arc wrapper --- packages/perseus-rocket/src/lib.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 8ef1994c90..2373ca0261 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -11,7 +11,7 @@ documentation, and this should mostly be used as a secondary reference source. Y #![deny(missing_docs)] #![deny(missing_debug_implementations)] -use std::{io::Cursor, path::Path, sync::Arc}; +use std::{io::Cursor, path::Path}; use perseus::{ i18n::TranslationsManager, @@ -80,7 +80,7 @@ async fn get_wasm_js_bundle(opts: &State) -> std::io::Result(req: &'r Request<'_>, turbine: Arc<&Turbine>) -> Outcome<'r> +async fn perseus_locale<'r, M, T>(req: &'r Request<'_>, turbine: &Turbine) -> Outcome<'r> where M: MutableStore + 'static, T: TranslationsManager + 'static, @@ -93,7 +93,7 @@ where async fn perseus_initial_load_handler<'r, M, T>( req: &'r Request<'_>, - turbine: Arc<&Turbine>, + turbine: &Turbine, ) -> Outcome<'r> where M: MutableStore + 'static, @@ -120,7 +120,7 @@ where async fn perseus_subsequent_load_handler<'r, M, T>( req: &'r Request<'_>, - turbine: Arc<&Turbine>, + turbine: &Turbine, ) -> Outcome<'r> where M: MutableStore + 'static, @@ -183,7 +183,7 @@ where M: MutableStore + 'static, T: TranslationsManager + 'static, { - turbine: Arc<&'a Turbine>, + turbine: &'a Turbine, perseus_route: PerseusRouteKind<'a>, } @@ -229,13 +229,11 @@ where M: MutableStore + 'static, T: TranslationsManager + 'static, { - let arc_turbine = Arc::new(turbine); - let get_locale = Route::new( Method::Get, "/translations/", RocketHandlerWithTurbine { - turbine: arc_turbine.clone(), + turbine: &turbine, perseus_route: PerseusRouteKind::Locale, }, ); @@ -248,7 +246,7 @@ where Method::Get, "/", RocketHandlerWithTurbine { - turbine: arc_turbine.clone(), + turbine: &turbine, perseus_route: PerseusRouteKind::IntialLoadHandler, }, ); @@ -257,7 +255,7 @@ where Method::Get, "/page/", RocketHandlerWithTurbine { - turbine: arc_turbine.clone(), + turbine: &turbine, perseus_route: PerseusRouteKind::SubsequentLoadHandler, }, ); @@ -286,7 +284,7 @@ where Method::Get, url, RocketHandlerWithTurbine { - turbine: arc_turbine.clone(), + turbine: &turbine, perseus_route: PerseusRouteKind::StaticAlias(static_path), }, ); From a25dc41e2c19de314e42ecf23b5264c0f1b7e23f Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 20 Mar 2023 16:37:41 +0100 Subject: [PATCH 07/13] remove unnecessary clones --- packages/perseus-rocket/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 2373ca0261..a1067e5ead 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -65,17 +65,17 @@ impl<'r> Responder<'r, 'static> for ApiResponse { #[get("/bundle.js")] async fn get_js_bundle(opts: &State) -> std::io::Result { - NamedFile::open(opts.js_bundle.clone()).await + NamedFile::open(&opts.js_bundle).await } #[get("/bundle.wasm")] async fn get_wasm_bundle(opts: &State) -> std::io::Result { - NamedFile::open(opts.wasm_bundle.clone()).await + NamedFile::open(&opts.wasm_bundle).await } #[get("/bundle.wasm.js")] async fn get_wasm_js_bundle(opts: &State) -> std::io::Result { - NamedFile::open(opts.wasm_js_bundle.clone()).await + NamedFile::open(&opts.wasm_js_bundle).await } // ----- Turbine dependant route handlers ----- @@ -195,15 +195,15 @@ where { async fn handle<'r>(&self, req: &'r Request<'_>, _data: Data<'r>) -> Outcome<'r> { match self.perseus_route { - PerseusRouteKind::Locale => perseus_locale(req, self.turbine.clone()).await, + PerseusRouteKind::Locale => perseus_locale(req, self.turbine).await, PerseusRouteKind::StaticAlias(static_alias) => { perseus_static_alias(req, static_alias).await } PerseusRouteKind::IntialLoadHandler => { - perseus_initial_load_handler(req, self.turbine.clone()).await + perseus_initial_load_handler(req, self.turbine).await } PerseusRouteKind::SubsequentLoadHandler => { - perseus_subsequent_load_handler(req, self.turbine.clone()).await + perseus_subsequent_load_handler(req, self.turbine).await } } } From b1ff8900ca107e669109b063368ed00452f81181 Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 3 Apr 2023 16:08:58 +0200 Subject: [PATCH 08/13] Bump perseus beta version --- examples/core/custom_server_rocket/Cargo.toml | 2 +- packages/perseus-rocket/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/core/custom_server_rocket/Cargo.toml b/examples/core/custom_server_rocket/Cargo.toml index 49c8aed12e..ee53824274 100644 --- a/examples/core/custom_server_rocket/Cargo.toml +++ b/examples/core/custom_server_rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_server_rocket" -version = "0.4.0-beta.21" +version = "0.4.0-beta.22" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/perseus-rocket/Cargo.toml b/packages/perseus-rocket/Cargo.toml index 32ee25a606..cad02d3c77 100644 --- a/packages/perseus-rocket/Cargo.toml +++ b/packages/perseus-rocket/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "perseus-rocket" -version = "0.4.0-beta.21" +version = "0.4.0-beta.22" edition = "2021" description = "An integration that makes the Perseus framework easy to use with Rocket." authors = ["Miroito "] From a57ddaa4928d240e6d378c6cfd6f0dca3b2dea15 Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 3 Apr 2023 16:10:23 +0200 Subject: [PATCH 09/13] fix: some typos --- examples/core/custom_server_rocket/README.md | 2 +- packages/perseus-rocket/src/lib.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/core/custom_server_rocket/README.md b/examples/core/custom_server_rocket/README.md index 7f4f84015f..22c2f3429e 100644 --- a/examples/core/custom_server_rocket/README.md +++ b/examples/core/custom_server_rocket/README.md @@ -1,3 +1,3 @@ # Custom Server Rocket Example -This is an example of setting up a custom server using rocket with Perseus, with one example api route. +This is an example of setting up a custom server using rocket with Perseus, with one example API route. diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index a1067e5ead..0907738399 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -167,7 +167,7 @@ where } } -// ----- Rocket Hanlder trait implementation ----- +// ----- Rocket handler trait implementation ----- #[derive(Clone)] enum PerseusRouteKind<'a> { @@ -219,7 +219,7 @@ async fn perseus_static_alias<'r>(req: &'r Request<'_>, static_alias: &String) - // ----- Integration code ----- /// Configures an Rocket Web app for Perseus. -/// This returns a rocket at the build stage that can be built upon further with +/// This returns a rocket app at the build stage that can be built upon further with /// more routes, fairings etc... pub async fn perseus_base_app( turbine: &'static Turbine, From 956d51cbff31138a9989b8c2f9ada7c783202612 Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 3 Apr 2023 16:11:56 +0200 Subject: [PATCH 10/13] feat: use the host parameter --- examples/core/custom_server_rocket/src/main.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/core/custom_server_rocket/src/main.rs b/examples/core/custom_server_rocket/src/main.rs index 7a3e6a600e..a8d319057a 100644 --- a/examples/core/custom_server_rocket/src/main.rs +++ b/examples/core/custom_server_rocket/src/main.rs @@ -19,13 +19,19 @@ pub async fn dflt_server< >( turbine: &'static perseus::turbine::Turbine, opts: perseus::server::ServerOptions, - (_host, port): (String, u16), + (host, port): (String, u16), ) { use perseus_rocket::perseus_base_app; use rocket::routes; + + let addr = host.parse().expect("Invalid address provided to bind to."); + + let mut config = rocket::Config::default(); + let mut app = perseus_base_app(turbine, opts).await; app = app.mount("/api", routes![api::hello]); - let mut config = rocket::Config::default(); + + config.address = addr; config.port = port; app = app.configure(config); From 7dba0f5515111b73309302cb506179b819de29c5 Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 3 Apr 2023 16:12:45 +0200 Subject: [PATCH 11/13] use basic example templates --- .../core/custom_server_rocket/src/main.rs | 1 + .../src/templates/about.rs | 19 +++++++++++ .../src/templates/index.rs | 34 ++++++++++++++----- .../custom_server_rocket/src/templates/mod.rs | 1 + 4 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 examples/core/custom_server_rocket/src/templates/about.rs diff --git a/examples/core/custom_server_rocket/src/main.rs b/examples/core/custom_server_rocket/src/main.rs index a8d319057a..e9bc4ed662 100644 --- a/examples/core/custom_server_rocket/src/main.rs +++ b/examples/core/custom_server_rocket/src/main.rs @@ -45,5 +45,6 @@ pub async fn dflt_server< pub fn main() -> PerseusApp { PerseusApp::new() .template(crate::templates::index::get_template()) + .template(crate::templates::about::get_template()) .error_views(ErrorViews::unlocalized_development_default()) } diff --git a/examples/core/custom_server_rocket/src/templates/about.rs b/examples/core/custom_server_rocket/src/templates/about.rs new file mode 100644 index 0000000000..12989c60c7 --- /dev/null +++ b/examples/core/custom_server_rocket/src/templates/about.rs @@ -0,0 +1,19 @@ +use perseus::prelude::*; +use sycamore::prelude::*; + +fn about_page(cx: Scope) -> View { + view! { cx, + p { "About." } + } +} + +#[engine_only_fn] +fn head(cx: Scope) -> View { + view! { cx, + title { "About Page | Perseus Example – Basic" } + } +} + +pub fn get_template() -> Template { + Template::build("about").view(about_page).head(head).build() +} diff --git a/examples/core/custom_server_rocket/src/templates/index.rs b/examples/core/custom_server_rocket/src/templates/index.rs index 3f9f9706b6..09ef5e05f7 100644 --- a/examples/core/custom_server_rocket/src/templates/index.rs +++ b/examples/core/custom_server_rocket/src/templates/index.rs @@ -1,23 +1,39 @@ use perseus::prelude::*; +use serde::{Deserialize, Serialize}; use sycamore::prelude::*; -fn index_page(cx: Scope) -> View { - let counter = create_signal(cx, 0); +#[derive(Serialize, Deserialize, ReactiveState, Clone)] +#[rx(alias = "IndexPageStateRx")] +struct IndexPageState { + greeting: String, +} + +#[auto_scope] +fn index_page(cx: Scope, state: &IndexPageStateRx) -> View { view! { cx, - (counter.get()) - br () {} - button (on:click=move |_| {counter.set(*counter.get() - 1)}) { "remove 1"} - button (on:click=move |_| {counter.set(*counter.get() + 1)}) { "Add 1"} + p { (state.greeting.get()) } + a(href = "about", id = "about-link") { "About!" } } } #[engine_only_fn] -fn head(cx: Scope) -> View { +fn head(cx: Scope, _props: IndexPageState) -> View { view! { cx, - title { "Index Page" } + title { "Index Page | Perseus Example – Basic" } + } +} + +#[engine_only_fn] +async fn get_build_state(_info: StateGeneratorInfo<()>) -> IndexPageState { + IndexPageState { + greeting: "Hello World!".to_string(), } } pub fn get_template() -> Template { - Template::build("index").view(index_page).head(head).build() + Template::build("index") + .build_state_fn(get_build_state) + .view_with_state(index_page) + .head_with_state(head) + .build() } diff --git a/examples/core/custom_server_rocket/src/templates/mod.rs b/examples/core/custom_server_rocket/src/templates/mod.rs index 33edc959c9..9b9cf18fc5 100644 --- a/examples/core/custom_server_rocket/src/templates/mod.rs +++ b/examples/core/custom_server_rocket/src/templates/mod.rs @@ -1 +1,2 @@ +pub mod about; pub mod index; From affb8a43304a66975bcc39a218e88ac6b99e2e65 Mon Sep 17 00:00:00 2001 From: Miroito Date: Mon, 3 Apr 2023 16:19:17 +0200 Subject: [PATCH 12/13] Add warning for Rocket Beta --- packages/perseus-rocket/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/perseus-rocket/README.md b/packages/perseus-rocket/README.md index 23b6565587..ec363178d1 100644 --- a/packages/perseus-rocket/README.md +++ b/packages/perseus-rocket/README.md @@ -1,5 +1,7 @@ # Perseus Rocket Integration +> :warning: This integration uses Rocket 0.5.0-rc.2 which is still in beta + This is the official [Perseus](https://github.com/arctic-hen7/perseus) integration for making serving your apps on [Rocket](https://rocket.rs/) significantly easier! If you're new to Perseus, you should check out [the core package](https://github.com/arctic-hen7/perseus) first. From 8098e6fa5b1714b2392b942be81a849a7f8402d8 Mon Sep 17 00:00:00 2001 From: Miroito Date: Wed, 5 Apr 2023 19:04:19 +0200 Subject: [PATCH 13/13] fix: Copy paste blunders --- packages/perseus-rocket/README.md | 4 ++-- packages/perseus-rocket/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/perseus-rocket/README.md b/packages/perseus-rocket/README.md index ec363178d1..fb1fae06f9 100644 --- a/packages/perseus-rocket/README.md +++ b/packages/perseus-rocket/README.md @@ -2,6 +2,6 @@ > :warning: This integration uses Rocket 0.5.0-rc.2 which is still in beta -This is the official [Perseus](https://github.com/arctic-hen7/perseus) integration for making serving your apps on [Rocket](https://rocket.rs/) significantly easier! +This is the official [Perseus](https://github.com/framesurge/perseus) integration for making serving your apps on [Rocket](https://rocket.rs/) significantly easier! -If you're new to Perseus, you should check out [the core package](https://github.com/arctic-hen7/perseus) first. +If you're new to Perseus, you should check out [the core package](https://github.com/framesurge/perseus) first. diff --git a/packages/perseus-rocket/src/lib.rs b/packages/perseus-rocket/src/lib.rs index 0907738399..d69165b528 100644 --- a/packages/perseus-rocket/src/lib.rs +++ b/packages/perseus-rocket/src/lib.rs @@ -3,7 +3,7 @@ /*! ## Packages -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://framesurge.sh/perseus/en-US) for +This is the API documentation for the `perseus-rocket` package, which allows Perseus apps to run on Rocket. Note that Perseus mostly uses [the book](https://framesurge.sh/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/framesurge/perseus/tree/main/examples). */