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:
1 parent
cda8bb24f1
commit
6a0c1c392b
12 files changed
+625
-218
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
Reference in new issue
Block a user