Improve documentation for the fullstack crate (#2952)

* improve documentation for the fullstack server context

* Add a section about axum integration to the crate root docs

* make serve_dioxus_application accept the cfg builder directly

* remove unused server_fn module

* improve fullstack config docs

* improve documentation for the server function macro

* fix axum router extension link

* Fix doc tests

* Fix launch builder

* Remove redundant server_only inside server function

* Fix isrg config path
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-10-10 13:40:11 -07:00
1 parent cda8bb24f1
commit 6a0c1c392b
12 files changed
+625 -218

No files matched your search

+74
View File
@@ -63,6 +63,80 @@ async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
}
```
## Axum Integration
If you have an existing Axum router or you need more control over the server, you can use the [`DioxusRouterExt`](https://docs.rs/dioxus-fullstack/0.6.0-alpha.2/dioxus_fullstack/prelude/trait.DioxusRouterExt.html) trait to integrate with your existing Axum router.
First, make sure your `axum` dependency is optional and enabled by the server feature flag. Axum cannot be compiled to wasm, so if it is enabled by default, it will cause a compile error:
```toml
[dependencies]
dioxus = { version = "*", features = ["fullstack"] }
axum = { version = "0.7.0", optional = true }
[features]
server = ["dep:axum", "dioxus/server"]
web = ["dioxus/web"]
```
Then we can set up dioxus with the axum server:
```rust, no_run
#![allow(non_snake_case)]
use dioxus::prelude::*;
// The entry point for the server
#[cfg(feature = "server")]
fn main() {
// Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address
// and we use the generated address the CLI gives us
let cli_args = dioxus_cli_config::RuntimeCLIArguments::from_cli();
let address = cli_args
.as_ref()
.map(|args| args.fullstack_address().address())
.unwrap_or_else(SocketAddr::from(([127, 0, 0, 1], 8080)));
// Set up the axum router
let router = axum::Router::new()
// You can add a dioxus application to the router with the `serve_dioxus_application` method
// This will add a fallback route to the router that will serve your component and server functions
.serve_dioxus_application(ServeConfigBuilder::default(), App);
// Finally, we can launch the server
let router = router.into_make_service();
let listener = tokio::net::TcpListener::bind(address).await.unwrap();
axum::serve(listener, router).await.unwrap();
}
// For any other platform, we just launch the app
#[cfg(not(feature = "server"))]
fn main() {
launch(App);
}
#[component]
fn App() -> Element {
let mut meaning = use_signal(|| None);
rsx! {
h1 { "Meaning of life: {meaning:?}" }
button {
onclick: move |_| async move {
if let Ok(data) = get_meaning("life the universe and everything".into()).await {
meaning.set(data);
}
},
"Run a server function"
}
}
}
#[server]
async fn get_meaning(of: String) -> Result<Option<u32>, ServerFnError> {
Ok(of.contains("life").then(|| 42))
}
```
## Getting Started
To get started with full stack Dioxus, check out our [getting started guide](https://dioxuslabs.com/learn/0.5/getting_started), or the [full stack examples](https://github.com/DioxusLabs/dioxus/tree/master/examples).
+40
View File
@@ -0,0 +1,40 @@
This method interacts with information from the current request. The request may come from:
1. The initial SSR render if this method called from a [`Component`](dioxus_lib::prelude::component) or a [`server`](crate::prelude::server) function that is called during the initial render
```rust
#[component]
fn PrintHtmlRequestInfo() -> Element {
// The server context only exists on the server, so we need to put it behind a server_only! config
server_only! {
// Since we are calling this from a component, the server context that is returned will be from
// the html request for ssr rendering
println!("headers are {:?}", server_context().request_parts().headers);
}
rsx! {}
}
```
2. A request to a [`server`](crate::prelude::server) function called directly from the client (either on desktop/mobile or on the web frontend after the initial render)
```rust
#[server]
async fn read_headers() -> Result<(), ServerFnError> {
// Since we are calling this from a server function, the server context that is may be from the
// initial request or a request from the client
println!("headers are {:?}", server_context().request_parts().headers);
Ok(())
}
#[component]
fn CallServerFunction() -> Element {
rsx! {
button {
// If you click the button, the server function will be called and the server context will be
// from the client request
onclick: move |_| read_headers().await,
"Call server function"
}
}
}
```
+23 -12
View File
@@ -21,7 +21,7 @@
//! listener,
//! axum::Router::new()
//! // Server side render the application, serve static assets, and register server functions
//! .serve_dioxus_application(ServeConfig::new().unwrap(), app)
//! .serve_dioxus_application(ServeConfigBuilder::default(), app)
//! .into_make_service(),
//! )
//! .await
@@ -160,8 +160,10 @@ pub trait DioxusRouterExt<S> {
/// rsx! { "Hello World" }
/// }
/// ```
fn serve_dioxus_application(self, cfg: impl Into<ServeConfig>, app: fn() -> Element) -> Self
fn serve_dioxus_application<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
where
Cfg: TryInto<ServeConfig, Error = Error>,
Error: std::error::Error,
Self: Sized;
}
@@ -243,18 +245,27 @@ where
self
}
fn serve_dioxus_application(self, cfg: impl Into<ServeConfig>, app: fn() -> Element) -> Self {
let cfg = cfg.into();
let ssr_state = SSRState::new(&cfg);
fn serve_dioxus_application<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
where
Cfg: TryInto<ServeConfig, Error = Error>,
Error: std::error::Error,
{
// Add server functions and render index.html
let server = self.serve_static_assets().register_server_functions();
server.fallback(
get(render_handler)
.with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)),
)
match cfg.try_into() {
Ok(cfg) => {
let ssr_state = SSRState::new(&cfg);
server.fallback(
get(render_handler)
.with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)),
)
}
Err(err) => {
tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err);
server
}
}
}
}
@@ -343,7 +354,7 @@ impl RenderHandleState {
/// // to inject the context into server functions running outside
/// // of an SSR render context.
/// .fallback(get(render_handler)
/// .with_state(RenderHandleState::new(app))
/// .with_state(RenderHandleState::new(ServeConfig::new().unwrap(), app))
/// )
/// .into_make_service();
/// let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
+192 -8
View File
@@ -5,6 +5,34 @@ use dioxus_lib::prelude::*;
use std::sync::Arc;
/// Settings for a fullstack app.
///
/// Depending on what features are enabled, you can pass in configurations for each client platform as well as the server:
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg = cfg.with_server_config(ServeConfigBuilder::default());
/// }
///
/// // Only set the web config if the web feature is enabled
/// web! {
/// cfg = cfg.with_web_config(dioxus::web::Config::default());
/// }
///
/// // Only set the desktop config if the desktop feature is enabled
/// desktop! {
/// cfg = cfg.with_desktop_config(dioxus::desktop::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
pub struct Config {
#[cfg(feature = "server")]
pub(crate) server_cfg: ServeConfigBuilder,
@@ -41,7 +69,25 @@ impl Config {
Self::default()
}
/// Set the incremental renderer config.
/// Set the incremental renderer config. The incremental config can be used to improve
/// performance of heavy routes by caching the rendered html in memory and/or the file system.
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg = cfg.incremental(IncrementalRendererConfig::default().with_memory_cache_limit(10000));
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
@@ -51,28 +97,94 @@ impl Config {
}
}
/// Set the server config.
/// Set the server config
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg = cfg.with_server_config(ServeConfigBuilder::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
pub fn with_server_cfg(self, server_cfg: ServeConfigBuilder) -> Self {
pub fn with_server_config(self, server_cfg: ServeConfigBuilder) -> Self {
Self { server_cfg, ..self }
}
/// Set the server config.
/// Set the server config by modifying the config in place
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg.set_server_config(ServeConfigBuilder::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
pub fn set_server_cfg(&mut self, server_cfg: ServeConfigBuilder) {
pub fn set_server_config(&mut self, server_cfg: ServeConfigBuilder) {
self.server_cfg = server_cfg;
}
/// Set the web config.
/// Set the web config
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the web config if the server feature is enabled
/// web! {
/// cfg = cfg.with_web_config(dioxus::web::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "web")]
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
pub fn with_web_config(self, web_cfg: dioxus_web::Config) -> Self {
Self { web_cfg, ..self }
}
/// Set the web config.
/// Set the server config by modifying the config in place
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the web config if the server feature is enabled
/// web! {
/// cfg.set_web_config(dioxus::web::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "web")]
#[cfg_attr(docsrs, doc(cfg(feature = "web")))]
pub fn set_web_config(&mut self, web_cfg: dioxus_web::Config) {
@@ -80,6 +192,22 @@ impl Config {
}
/// Set the desktop config
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the desktop config if the server feature is enabled
/// desktop! {
/// cfg = cfg.with_desktop_config(dioxus::desktop::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "desktop")]
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
pub fn with_desktop_config(self, desktop_cfg: dioxus_desktop::Config) -> Self {
@@ -89,17 +217,73 @@ impl Config {
}
}
/// Set the desktop config.
/// Set the desktop config by modifying the config in place
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the desktop config if the server feature is enabled
/// desktop! {
/// cfg.set_desktop_config(dioxus::desktop::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
#[cfg(feature = "desktop")]
#[cfg_attr(docsrs, doc(cfg(feature = "desktop")))]
pub fn set_desktop_config(&mut self, desktop_cfg: dioxus_desktop::Config) {
self.desktop_cfg = desktop_cfg;
}
/// Set the mobile config
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the mobile config if the server feature is enabled
/// mobile! {
/// cfg = cfg.with_mobile_cfg(dioxus::mobile::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// Set the mobile config.
#[cfg(feature = "mobile")]
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
pub fn with_mobile_cfg(self, mobile_cfg: dioxus_mobile::Config) -> Self {
Self { mobile_cfg, ..self }
}
/// Set the mobile config by modifying the config in place
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the mobile config if the server feature is enabled
/// mobile! {
/// cfg.set_mobile_cfg(dioxus::mobile::Config::default());
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// Set the mobile config.
#[cfg(feature = "mobile")]
#[cfg_attr(docsrs, doc(cfg(feature = "mobile")))]
pub fn set_mobile_cfg(&mut self, mobile_cfg: dioxus_mobile::Config) {
self.mobile_cfg = mobile_cfg;
}
}
+73 -3
View File
@@ -15,7 +15,7 @@ pub struct ServeConfigBuilder {
}
impl ServeConfigBuilder {
/// Create a new ServeConfigBuilder with the root component and props to render on the server.
/// Create a new ServeConfigBuilder with incremental static generation disabled and the default index.html settings
pub fn new() -> Self {
Self {
root_id: None,
@@ -25,7 +25,25 @@ impl ServeConfigBuilder {
}
}
/// Enable incremental static generation
/// Enable incremental static generation. Incremental static generation caches the
/// rendered html in memory and/or the file system. It can be used to improve performance of heavy routes.
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg = cfg.with_server_cfg(ServeConfigBuilder::default().incremental(IncrementalRendererConfig::default()));
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
pub fn incremental(mut self, cfg: dioxus_isrg::IncrementalRendererConfig) -> Self {
self.incremental = Some(cfg);
self
@@ -44,12 +62,56 @@ impl ServeConfigBuilder {
}
/// Set the id of the root element in the index.html file to place the prerendered content into. (defaults to main)
///
/// # Example
///
/// If your index.html file looks like this:
/// ```html
/// <!DOCTYPE html>
/// <html>
/// <head>
/// <title>My App</title>
/// </head>
/// <body>
/// <div id="my-custom-root"></div>
/// </body>
/// </html>
/// ```
///
/// You can set the root id to `"my-custom-root"` to render the app into that element:
///
/// ```rust, no_run
/// # fn app() -> Element { todo!() }
/// use dioxus::prelude::*;
///
/// let mut cfg = dioxus::fullstack::Config::new();
///
/// // Only set the server config if the server feature is enabled
/// server_only! {
/// cfg = cfg.with_server_cfg(ServeConfigBuilder::default().root_id("app"));
/// }
///
/// // You also need to set the root id in your web config
/// web! {
/// cfg = cfg.with_web_config(dioxus::web::Config::default().rootname("app"));
/// }
///
/// // And desktop config
/// desktop! {
/// cfg = cfg.with_desktop_config(dioxus::desktop::Config::default().with_root_name("app"));
/// }
///
/// // Finally, launch the app with the config
/// LaunchBuilder::new()
/// .with_cfg(cfg)
/// .launch(app);
/// ```
pub fn root_id(mut self, root_id: &'static str) -> Self {
self.root_id = Some(root_id);
self
}
/// Build the ServeConfig
/// Build the ServeConfig. This may fail if the index.html file is not found.
pub fn build(self) -> Result<ServeConfig, UnableToLoadIndex> {
// The CLI always bundles static assets into the exe/public directory
let public_path = public_path();
@@ -75,6 +137,14 @@ impl ServeConfigBuilder {
}
}
impl TryInto<ServeConfig> for ServeConfigBuilder {
type Error = UnableToLoadIndex;
fn try_into(self) -> Result<ServeConfig, Self::Error> {
self.build()
}
}
/// Get the path to the public assets directory to serve static files from
pub(crate) fn public_path() -> PathBuf {
// The CLI always bundles static assets into the exe/public directory
+135 -17
View File
@@ -7,9 +7,21 @@ type SendSyncAnyMap =
std::collections::HashMap<std::any::TypeId, Box<dyn Any + Send + Sync + 'static>>;
/// A shared context for server functions that contains information about the request and middleware state.
/// This allows you to pass data between your server framework and the server functions. This can be used to pass request information or information about the state of the server. For example, you could pass authentication data though this context to your server functions.
///
/// You should not construct this directly inside components. Instead use the `HasServerContext` trait to get the server context from the scope.
/// You should not construct this directly inside components or server functions. Instead use [`server_context()`] to get the server context from the current request.
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn read_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// let headers: http::HeaderMap = server_context.extract().await?;
/// println!("{:?}", headers);
/// Ok(())
/// }
/// ```
#[derive(Clone)]
pub struct DioxusServerContext {
shared_context: std::sync::Arc<RwLock<SendSyncAnyMap>>,
@@ -59,7 +71,34 @@ mod server_fn_impl {
}
}
/// Clone a value from the shared server context
/// Clone a value from the shared server context. If you are using [`DioxusRouterExt`](crate::prelude::DioxusRouterExt), any values you insert into
/// the launch context will also be available in the server context.
///
/// Example:
/// ```rust, no_run
/// use dioxus::prelude::*;
///
/// LaunchBuilder::new()
/// // You can provide context to your whole app (including server functions) with the `with_context` method on the launch builder
/// .with_context(server_only! {
/// 1234567890u32
/// })
/// .launch(app);
///
/// #[server]
/// async fn read_context() -> Result<u32, ServerFnError> {
/// // You can extract values from the server context with the `extract` function
/// let FromContext(value) = extract().await?;
/// Ok(value)
/// }
///
/// fn app() -> Element {
/// let future = use_resource(read_context);
/// rsx! {
/// h1 { "{future:?}" }
/// }
/// }
/// ```
pub fn get<T: Any + Send + Sync + Clone + 'static>(&self) -> Option<T> {
self.shared_context
.read()
@@ -68,10 +107,10 @@ mod server_fn_impl {
}
/// Insert a value into the shared server context
///
///
pub fn insert<T: Any + Send + Sync + 'static>(&self, value: T) {
self.shared_context
.write()
.insert(TypeId::of::<T>(), Box::new(value));
self.insert_any(Box::new(value));
}
/// Insert a Boxed `Any` value into the shared server context
@@ -82,33 +121,112 @@ mod server_fn_impl {
}
/// Get the response parts from the server context
///
#[doc = include_str!("../docs/request_origin.md")]
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn set_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// let cookies = server_context.response_parts()
/// .headers()
/// .get("Cookie")
/// .ok_or_else(|| ServerFnError::msg("failed to find Cookie header in the response"))?;
/// println!("{:?}", cookies);
/// Ok(())
/// }
/// ```
pub fn response_parts(&self) -> RwLockReadGuard<'_, http::response::Parts> {
self.response_parts.read()
}
/// Get the response parts from the server context
///
#[doc = include_str!("../docs/request_origin.md")]
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn set_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// server_context.response_parts_mut()
/// .headers_mut()
/// .insert("Cookie", "dioxus=fullstack");
/// Ok(())
/// }
/// ```
pub fn response_parts_mut(&self) -> RwLockWriteGuard<'_, http::response::Parts> {
self.response_parts.write()
}
/// Get the request that triggered:
/// - The initial SSR render if called from a ScopeState or ServerFn
/// - The server function to be called if called from a server function after the initial render
/// Get the request parts
///
#[doc = include_str!("../docs/request_origin.md")]
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn read_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// let id: &i32 = server_context.request_parts()
/// .extensions
/// .get()
/// .ok_or_else(|| ServerFnError::msg("failed to find i32 extension in the request"))?;
/// println!("{:?}", id);
/// Ok(())
/// }
/// ```
pub fn request_parts(&self) -> parking_lot::RwLockReadGuard<'_, http::request::Parts> {
self.parts.read()
}
/// Get the request that triggered:
/// - The initial SSR render if called from a ScopeState or ServerFn
/// - The server function to be called if called from a server function after the initial render
/// Get the request parts mutably
///
#[doc = include_str!("../docs/request_origin.md")]
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn read_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// let id: i32 = server_context.request_parts_mut()
/// .extensions
/// .remove()
/// .ok_or_else(|| ServerFnError::msg("failed to find i32 extension in the request"))?;
/// println!("{:?}", id);
/// Ok(())
/// }
/// ```
pub fn request_parts_mut(&self) -> parking_lot::RwLockWriteGuard<'_, http::request::Parts> {
self.parts.write()
}
/// Extract some part from the request
pub async fn extract<R: std::error::Error, T: FromServerContext<Rejection = R>>(
&self,
) -> Result<T, R> {
/// Extract part of the request.
///
#[doc = include_str!("../docs/request_origin.md")]
///
/// # Example
///
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// #[server]
/// async fn read_headers() -> Result<(), ServerFnError> {
/// let server_context = server_context();
/// let headers: http::HeaderMap = server_context.extract().await?;
/// println!("{:?}", headers);
/// Ok(())
/// }
/// ```
pub async fn extract<M, T: FromServerContext<M>>(&self) -> Result<T, T::Rejection> {
T::from_request(self).await
}
}
@@ -175,7 +293,7 @@ impl<F: std::future::Future> std::future::Future for ProvideServerContext<F> {
#[async_trait::async_trait]
pub trait FromServerContext<I = ()>: Sized {
/// The error type returned when extraction fails. This type must implement `std::error::Error`.
type Rejection: std::error::Error;
type Rejection;
/// Extract this type from the server context.
async fn from_request(req: &DioxusServerContext) -> Result<Self, Self::Rejection>;
@@ -1,174 +0,0 @@
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
#[derive(Clone)]
/// A trait object for a function that be called on serializable arguments and returns a serializable result.
pub struct ServerFnTraitObj(server_fn::ServerFnTraitObj<()>);
#[cfg(feature = "server")]
impl std::ops::Deref for ServerFnTraitObj {
type Target = server_fn::ServerFnTraitObj<()>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(feature = "server")]
impl std::ops::DerefMut for ServerFnTraitObj {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(feature = "server")]
impl ServerFnTraitObj {
fn new(
prefix: &'static str,
url: &'static str,
encoding: server_fn::Encoding,
run: ServerFunction,
) -> Self {
Self(server_fn::ServerFnTraitObj::new(prefix, url, encoding, run))
}
/// Create a new `ServerFnTraitObj` from a `server_fn::ServerFnTraitObj`.
pub const fn from_generic_server_fn(server_fn: server_fn::ServerFnTraitObj<()>) -> Self {
Self(server_fn)
}
}
#[cfg(feature = "server")]
server_fn::inventory::collect!(ServerFnTraitObj);
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
/// Middleware for a server function
pub struct ServerFnMiddleware {
/// The prefix of the server function.
pub prefix: &'static str,
/// The url of the server function.
pub url: &'static str,
/// The middleware layers.
pub middleware: fn() -> Vec<std::sync::Arc<dyn crate::layer::Layer>>,
}
#[cfg(feature = "server")]
pub(crate) static MIDDLEWARE: once_cell::sync::Lazy<
std::collections::HashMap<
(&'static str, &'static str),
Vec<std::sync::Arc<dyn crate::layer::Layer>>,
>,
> = once_cell::sync::Lazy::new(|| {
let mut map: std::collections::HashMap<
(&'static str, &'static str),
Vec<std::sync::Arc<dyn crate::layer::Layer>>,
> = std::collections::HashMap::new();
for middleware in server_fn::inventory::iter::<ServerFnMiddleware> {
map.entry((middleware.prefix, middleware.url))
.or_default()
.extend((middleware.middleware)().iter().cloned());
}
map
});
#[cfg(feature = "server")]
server_fn::inventory::collect!(ServerFnMiddleware);
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
/// A server function that can be called on serializable arguments and returns a serializable result.
pub type ServerFunction = server_fn::SerializedFnTraitObj<()>;
#[cfg(feature = "server")]
#[allow(clippy::type_complexity)]
static REGISTERED_SERVER_FUNCTIONS: once_cell::sync::Lazy<
std::sync::Arc<std::sync::RwLock<std::collections::HashMap<&'static str, ServerFnTraitObj>>>,
> = once_cell::sync::Lazy::new(|| {
let mut map = std::collections::HashMap::new();
for server_fn in server_fn::inventory::iter::<ServerFnTraitObj> {
map.insert(server_fn.0.url(), server_fn.clone());
}
std::sync::Arc::new(std::sync::RwLock::new(map))
});
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
/// The registry of all Dioxus server functions.
pub struct DioxusServerFnRegistry;
#[cfg(feature = "server")]
impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry {
type Error = ServerRegistrationFnError;
fn register_explicit(
prefix: &'static str,
url: &'static str,
server_function: ServerFunction,
encoding: server_fn::Encoding,
) -> Result<(), Self::Error> {
// store it in the hashmap
let mut write = REGISTERED_SERVER_FUNCTIONS
.write()
.map_err(|e| ServerRegistrationFnError::Poisoned(e.to_string()))?;
let prev = write.insert(
url,
ServerFnTraitObj::new(prefix, url, encoding, server_function),
);
// if there was already a server function with this key,
// return Err
match prev {
Some(_) => Err(ServerRegistrationFnError::AlreadyRegistered(format!(
"There was already a server function registered at {:?}. \
This can happen if you use the same server function name \
in two different modules
on `stable` or in `release` mode.",
url
))),
None => Ok(()),
}
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).map(|inner| inner.0.clone()))
}
/// Returns the server function registered at the given URL, or `None` if no function is registered at that URL.
fn get_trait_obj(url: &str) -> Option<server_fn::ServerFnTraitObj<()>> {
Self::get(url)
}
fn get_encoding(url: &str) -> Option<server_fn::Encoding> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.and_then(|fns| fns.get(url).map(|f| f.encoding()))
}
/// Returns a list of all registered server functions.
fn paths_registered() -> Vec<&'static str> {
REGISTERED_SERVER_FUNCTIONS
.read()
.ok()
.map(|fns| fns.keys().cloned().collect())
.unwrap_or_default()
}
}
#[cfg(feature = "server")]
#[cfg_attr(docsrs, doc(cfg(feature = "server")))]
/// Errors that can occur when registering a server function.
#[derive(thiserror::Error, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum ServerRegistrationFnError {
/// The server function is already registered.
#[error("The server function {0} is already registered")]
AlreadyRegistered(String),
/// The server function registry is poisoned.
#[error("The server function registry is poisoned: {0}")]
Poisoned(String),
}