Forráskód Böngészése

create server package

Evan Almloff 2 éve
szülő
commit
7f6f6fb8c8

+ 1 - 0
Cargo.toml

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

+ 21 - 0
packages/server/Cargo.toml

@@ -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" }

+ 8 - 0
packages/server/server_macro/Cargo.toml

@@ -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]

+ 67 - 0
packages/server/server_macro/src/lib.rs

@@ -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 it’s 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 client’s perspective it involves an asynchronous
+///   function call.
+/// - **Server functions must return `Result<T, ServerFnError>`.** Even if the work being done
+///   inside the function body can’t 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(),
+    }
+}

+ 23 - 0
packages/server/src/adapters/axum_adapter.rs

@@ -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))
+}

+ 25 - 0
packages/server/src/adapters/salvo_adapter.rs

@@ -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))
+}

+ 28 - 0
packages/server/src/adapters/warp_adapter.rs

@@ -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))
+}

+ 85 - 0
packages/server/src/lib.rs

@@ -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)
+    }
+}