create server package

This commit is contained in:
Evan Almloff 2023-03-28 13:35:17 -05:00
parent c5085496db
commit 7f6f6fb8c8
8 changed files with 258 additions and 0 deletions

View file

@ -21,6 +21,7 @@ members = [
"packages/rsx-rosetta",
"packages/signals",
"packages/hot-reload",
"packages/server",
"docs/guide",
]

View file

@ -0,0 +1,21 @@
[package]
name = "server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
server_fn = { version = "0.2.4", features = ["stable"] }
# warp
warp = { version = "0.3.3", optional = true }
# axum
axum = { version = "0.6.1", optional = true, features = ["ws"] }
# salvo
salvo = { version = "0.37.7", optional = true, features = ["ws"] }
serde = "1.0.159"
dioxus = { path = "../dioxus", version = "^0.3.0" }

View file

@ -0,0 +1,8 @@
[package]
name = "server_macro"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -0,0 +1,67 @@
/// Declares that a function is a [server function](leptos_server). This means that
/// its body will only run on the server, i.e., when the `ssr` feature is enabled.
///
/// If you call a server function from the client (i.e., when the `csr` or `hydrate` features
/// are enabled), it will instead make a network request to the server.
///
/// You can specify one, two, or three arguments to the server function:
/// 1. **Required**: A type name that will be used to identify and register the server function
/// (e.g., `MyServerFn`).
/// 2. *Optional*: A URL prefix at which the function will be mounted when its registered
/// (e.g., `"/api"`). Defaults to `"/"`.
/// 3. *Optional*: either `"Cbor"` (specifying that it should use the binary `cbor` format for
/// serialization) or `"Url"` (specifying that it should be use a URL-encoded form-data string).
/// Defaults to `"Url"`. If you want to use this server function to power a `<form>` that will
/// work without WebAssembly, the encoding must be `"Url"`.
///
/// The server function itself can take any number of arguments, each of which should be serializable
/// and deserializable with `serde`. Optionally, its first argument can be a Leptos [Scope](leptos_reactive::Scope),
/// which will be injected *on the server side.* This can be used to inject the raw HTTP request or other
/// server-side context into the server function.
///
/// ```ignore
/// # use leptos::*; use serde::{Serialize, Deserialize};
/// # #[derive(Serialize, Deserialize)]
/// # pub struct Post { }
/// #[server(ReadPosts, "/api")]
/// pub async fn read_posts(how_many: u8, query: String) -> Result<Vec<Post>, ServerFnError> {
/// // do some work on the server to access the database
/// todo!()
/// }
/// ```
///
/// Note the following:
/// - You must **register** the server function by calling `T::register()` somewhere in your main function.
/// - **Server functions must be `async`.** Even if the work being done inside the function body
/// can run synchronously on the server, from the clients perspective it involves an asynchronous
/// function call.
/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
/// inside the function body cant fail, the processes of serialization/deserialization and the
/// network call are fallible.
/// - **Return types must be [Serializable](leptos_reactive::Serializable).**
/// This should be fairly obvious: we have to serialize arguments to send them to the server, and we
/// need to deserialize the result to return it to the client.
/// - **Arguments must be implement [`Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html)
/// and [`DeserializeOwned`](https://docs.rs/serde/latest/serde/de/trait.DeserializeOwned.html).**
/// They are serialized as an `application/x-www-form-urlencoded`
/// form data using [`serde_urlencoded`](https://docs.rs/serde_urlencoded/latest/serde_urlencoded/) or as `application/cbor`
/// using [`cbor`](https://docs.rs/cbor/latest/cbor/).
/// - **The [Scope](leptos_reactive::Scope) comes from the server.** Optionally, the first argument of a server function
/// can be a Leptos [Scope](leptos_reactive::Scope). This scope can be used to inject dependencies like the HTTP request
/// or response or other server-only dependencies, but it does *not* have access to reactive state that exists in the client.
#[proc_macro_attribute]
pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
let context = ServerContext {
ty: syn::parse_quote!(Scope),
path: syn::parse_quote!(::leptos::Scope),
};
match server_macro_impl(
args.into(),
s.into(),
Some(context),
Some(syn::parse_quote!(::leptos::server_fn)),
) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}

View file

@ -0,0 +1,23 @@
use crate::{LiveViewError, LiveViewSocket};
use axum::extract::ws::{Message, WebSocket};
use futures_util::{SinkExt, StreamExt};
/// Convert a warp websocket into a LiveViewSocket
///
/// This is required to launch a LiveView app using the warp web framework
pub fn axum_socket(ws: WebSocket) -> impl LiveViewSocket {
ws.map(transform_rx)
.with(transform_tx)
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, axum::Error>) -> Result<String, LiveViewError> {
message
.map_err(|_| LiveViewError::SendingFailed)?
.into_text()
.map_err(|_| LiveViewError::SendingFailed)
}
async fn transform_tx(message: String) -> Result<Message, axum::Error> {
Ok(Message::Text(message))
}

View file

@ -0,0 +1,25 @@
use futures_util::{SinkExt, StreamExt};
use salvo::ws::{Message, WebSocket};
use crate::{LiveViewError, LiveViewSocket};
/// Convert a salvo websocket into a LiveViewSocket
///
/// This is required to launch a LiveView app using the warp web framework
pub fn salvo_socket(ws: WebSocket) -> impl LiveViewSocket {
ws.map(transform_rx)
.with(transform_tx)
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, salvo::Error>) -> Result<String, LiveViewError> {
let as_bytes = message.map_err(|_| LiveViewError::SendingFailed)?;
let msg = String::from_utf8(as_bytes.into_bytes()).map_err(|_| LiveViewError::SendingFailed)?;
Ok(msg)
}
async fn transform_tx(message: String) -> Result<Message, salvo::Error> {
Ok(Message::text(message))
}

View file

@ -0,0 +1,28 @@
use crate::{LiveViewError, LiveViewSocket};
use futures_util::{SinkExt, StreamExt};
use warp::ws::{Message, WebSocket};
/// Convert a warp websocket into a LiveViewSocket
///
/// This is required to launch a LiveView app using the warp web framework
pub fn warp_socket(ws: WebSocket) -> impl LiveViewSocket {
ws.map(transform_rx)
.with(transform_tx)
.sink_map_err(|_| LiveViewError::SendingFailed)
}
fn transform_rx(message: Result<Message, warp::Error>) -> Result<String, LiveViewError> {
// destructure the message into the buffer we got from warp
let msg = message
.map_err(|_| LiveViewError::SendingFailed)?
.into_bytes();
// transform it back into a string, saving us the allocation
let msg = String::from_utf8(msg).map_err(|_| LiveViewError::SendingFailed)?;
Ok(msg)
}
async fn transform_tx(message: String) -> Result<Message, warp::Error> {
Ok(Message::text(message))
}

View file

@ -0,0 +1,85 @@
use dioxus::prelude::*;
use serde::{de::DeserializeOwned, Deserializer, Serialize, Serializer};
// We use deref specialization to make it possible to pass either a value that implements
pub trait SerializeToRemoteWrapper {
fn serialize_to_remote<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
impl<T: Serialize> SerializeToRemoteWrapper for &T {
fn serialize_to_remote<S: Serializer>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error> {
self.serialize(serializer)
}
}
impl<S: SerializeToRemote> SerializeToRemoteWrapper for &mut &S {
fn serialize_to_remote<S2: Serializer>(
&self,
serializer: S2,
) -> Result<<S2 as Serializer>::Ok, <S2 as Serializer>::Error> {
(**self).serialize_to_remote(serializer)
}
}
pub trait SerializeToRemote {
fn serialize_to_remote<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
}
impl<S: Serialize> SerializeToRemote for UseState<S> {
fn serialize_to_remote<S2: Serializer>(
&self,
serializer: S2,
) -> Result<<S2 as Serializer>::Ok, <S2 as Serializer>::Error> {
self.current().serialize(serializer)
}
}
// We use deref specialization to make it possible to pass either a value that implements
pub trait DeserializeOnRemoteWrapper {
type Output;
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Self::Output, D::Error>;
}
impl<T: DeserializeOwned> DeserializeOnRemoteWrapper for &T {
type Output = T;
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Self::Output, D::Error> {
T::deserialize(deserializer)
}
}
impl<D: DeserializeOnRemote> DeserializeOnRemoteWrapper for &mut &D {
type Output = D::Output;
fn deserialize_on_remote<'a, D2: Deserializer<'a>>(
deserializer: D2,
) -> Result<Self::Output, D2::Error> {
D::deserialize_on_remote(deserializer)
}
}
pub trait DeserializeOnRemote {
type Output;
fn deserialize_on_remote<'a, D: Deserializer<'a>>(
deserializer: D,
) -> Result<Self::Output, D::Error>;
}
impl<D: DeserializeOwned> DeserializeOnRemote for UseState<D> {
type Output = D;
fn deserialize_on_remote<'a, D2: Deserializer<'a>>(
deserializer: D2,
) -> Result<Self::Output, D2::Error> {
D::deserialize(deserializer)
}
}