Compare commits

..

4 Commits

Author SHA1 Message Date
Greg Johnston
f1bc734dcf 0.6.5 2024-01-31 19:40:41 -05:00
Greg Johnston
f71b4aae69 feat: easily create custom server fn clients (#2247) 2024-01-31 09:15:30 -05:00
Greg Johnston
a834c03974 fix: bug with Actix redirects (#2246) 2024-01-31 09:14:40 -05:00
Greg Johnston
595013579c 0.6.4 2024-01-30 09:17:52 -05:00
13 changed files with 114 additions and 40 deletions

View File

@@ -25,22 +25,22 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.6.3"
version = "0.6.5"
[workspace.dependencies]
leptos = { path = "./leptos", version = "0.6.3" }
leptos_dom = { path = "./leptos_dom", version = "0.6.3" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.3" }
leptos_macro = { path = "./leptos_macro", version = "0.6.3" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.3" }
leptos_server = { path = "./leptos_server", version = "0.6.3" }
server_fn = { path = "./server_fn", version = "0.6.3" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.3" }
leptos = { path = "./leptos", version = "0.6.5" }
leptos_dom = { path = "./leptos_dom", version = "0.6.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.5" }
leptos_macro = { path = "./leptos_macro", version = "0.6.5" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.5" }
leptos_server = { path = "./leptos_server", version = "0.6.5" }
server_fn = { path = "./server_fn", version = "0.6.5" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.5" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
leptos_config = { path = "./leptos_config", version = "0.6.3" }
leptos_router = { path = "./router", version = "0.6.3" }
leptos_meta = { path = "./meta", version = "0.6.3" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.3" }
leptos_config = { path = "./leptos_config", version = "0.6.5" }
leptos_router = { path = "./router", version = "0.6.5" }
leptos_meta = { path = "./meta", version = "0.6.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.5" }
[profile.release]
codegen-units = 1

View File

@@ -5,13 +5,15 @@ use leptos_meta::{provide_meta_context, Link, Meta, Stylesheet};
use leptos_router::{ActionForm, Route, Router, Routes};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use server_fn::{
client::{browser::BrowserClient, Client},
codec::{
Encoding, FromReq, FromRes, GetUrl, IntoReq, IntoRes, MultipartData,
MultipartFormData, Rkyv, SerdeLite, StreamingText, TextStream,
},
request::{ClientReq, Req},
response::{ClientRes, Res},
request::{browser::BrowserRequest, ClientReq, Req},
response::{browser::BrowserResponse, ClientRes, Res},
};
use std::future::Future;
#[cfg(feature = "ssr")]
use std::sync::{
atomic::{AtomicU8, Ordering},
@@ -58,6 +60,7 @@ pub fn HomePage() -> impl IntoView {
<FileUploadWithProgress/>
<FileWatcher/>
<CustomEncoding/>
<CustomClientExample/>
}
}
@@ -795,3 +798,55 @@ pub fn CustomEncoding() -> impl IntoView {
<p>{result}</p>
}
}
/// Middleware lets you modify the request/response on the server.
///
/// On the client, you might also want to modify the request. For example, you may need to add a
/// custom header for authentication on every request. You can do this by creating a "custom
/// client."
#[component]
pub fn CustomClientExample() -> impl IntoView {
// Define a type for our client.
pub struct CustomClient;
// Implement the `Client` trait for it.
impl<CustErr> Client<CustErr> for CustomClient {
// BrowserRequest and BrowserResponse are the defaults used by other server functions.
// They are wrappers for the underlying Web Fetch API types.
type Request = BrowserRequest;
type Response = BrowserResponse;
// Our custom `send()` implementation does all the work.
fn send(
req: Self::Request,
) -> impl Future<Output = Result<Self::Response, ServerFnError<CustErr>>>
+ Send {
// BrowserRequest derefs to the underlying Request type from gloo-net,
// so we can get access to the headers here
let headers = req.headers();
// modify the headers by appending one
headers.append("X-Custom-Header", "foobar");
// delegate back out to BrowserClient to send the modified request
BrowserClient::send(req)
}
}
// Specify our custom client with `client = `
#[server(client = CustomClient)]
pub async fn fn_with_custom_client() -> Result<(), ServerFnError> {
use http::header::HeaderMap;
use leptos_axum::extract;
let headers: HeaderMap = extract().await?;
let custom_header = headers.get("X-Custom-Header");
println!("X-Custom-Header = {custom_header:?}");
Ok(())
}
view! {
<h3>Custom clients</h3>
<p>You can define a custom server function client to do something like adding a header to every request.</p>
<p>Check the network request in your browser devtools to see how this client adds a custom header.</p>
<button on:click=|_| spawn_local(async { fn_with_custom_client().await.unwrap() })>Click me</button>
}
}

View File

@@ -150,14 +150,6 @@ pub fn redirect(path: &str) {
to redirect()."
);
}
if let Some(response_options) = use_context::<ResponseOptions>() {
response_options.set_status(StatusCode::FOUND);
response_options.insert_header(
header::LOCATION,
header::HeaderValue::from_str(path)
.expect("Failed to create HeaderValue"),
);
}
}
/// An Actix [struct@Route](actix_web::Route) that listens for a `POST` request with

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_meta"
version = "0.6.3"
version = "0.6.5"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -1,6 +1,6 @@
[package]
name = "leptos_router"
version = "0.6.3"
version = "0.6.5"
edition = "2021"
authors = ["Greg Johnston"]
license = "MIT"

View File

@@ -28,7 +28,7 @@
//! ## Example
//!
//! ```rust
//!
//!
//! use leptos::*;
//! use leptos_router::*;
//!

View File

@@ -40,7 +40,7 @@ where
{
async fn from_req(req: Request) -> Result<Self, ServerFnError<CustErr>> {
let string_data = req.as_query().unwrap_or_default();
let args = serde_qs::from_str::<Self>(&string_data)
let args = serde_qs::from_str::<Self>(string_data)
.map_err(|e| ServerFnError::Args(e.to_string()))?;
Ok(args)
}

View File

@@ -441,7 +441,6 @@ impl<Req, Res> Clone for ServerFnTraitObj<Req, Res> {
}
#[allow(unused)] // used by server integrations
/// A neat little type that stores our trait representations of your server functions
type LazyServerFnMap<Req, Res> =
Lazy<DashMap<&'static str, ServerFnTraitObj<Req, Res>>>;

View File

@@ -37,8 +37,8 @@ impl<CustErr> Req<CustErr> for ActixRequest
where
CustErr: 'static,
{
fn as_query(&self) -> Option<Cow<'_, str>> {
self.0 .0.uri().query().map(|q| q.into())
fn as_query(&self) -> Option<&str> {
self.0 .0.uri().query()
}
fn to_content_type(&self) -> Option<Cow<'_, str>> {

View File

@@ -12,8 +12,8 @@ impl<CustErr> Req<CustErr> for Request<Body>
where
CustErr: 'static,
{
fn as_query(&self) -> Option<Cow<'_, str>> {
self.uri().query().map(|q| q.into())
fn as_query(&self) -> Option<&str> {
self.uri().query()
}
fn to_content_type(&self) -> Option<Cow<'_, str>> {

View File

@@ -5,6 +5,7 @@ use futures::{Stream, StreamExt};
pub use gloo_net::http::Request;
use js_sys::{Reflect, Uint8Array};
use send_wrapper::SendWrapper;
use std::ops::{Deref, DerefMut};
use wasm_bindgen::JsValue;
use wasm_streams::ReadableStream;
use web_sys::{FormData, Headers, RequestInit, UrlSearchParams};
@@ -19,6 +20,32 @@ impl From<Request> for BrowserRequest {
}
}
impl From<BrowserRequest> for Request {
fn from(value: BrowserRequest) -> Self {
value.0.take()
}
}
impl From<BrowserRequest> for web_sys::Request {
fn from(value: BrowserRequest) -> Self {
value.0.take().into()
}
}
impl Deref for BrowserRequest {
type Target = Request;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
impl DerefMut for BrowserRequest {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.deref_mut()
}
}
/// The `FormData` type available in the browser.
#[derive(Debug)]
pub struct BrowserFormData(pub(crate) SendWrapper<FormData>);

View File

@@ -78,7 +78,7 @@ where
Self: Sized,
{
/// Returns the query string of the requests URL, starting after the `?`.
fn as_query(&self) -> Option<Cow<'_, str>>;
fn as_query(&self) -> Option<&str>;
/// Returns the `Content-Type` header, if any.
fn to_content_type(&self) -> Option<Cow<'_, str>>;
@@ -116,7 +116,7 @@ impl<CustErr> Req<CustErr> for BrowserMockReq
where
CustErr: 'static,
{
fn as_query(&self) -> Option<Cow<'_, str>> {
fn as_query(&self) -> Option<&str> {
unreachable!()
}

View File

@@ -59,14 +59,15 @@ pub fn server_macro_impl(
.inputs
.iter_mut()
.map(|f| {
let typed_arg =
match f {
FnArg::Receiver(_) => return Err(syn::Error::new(
let typed_arg = match f {
FnArg::Receiver(_) => {
return Err(syn::Error::new(
f.span(),
"cannot use receiver types in server function macro",
)),
FnArg::Typed(t) => t,
};
))
}
FnArg::Typed(t) => t,
};
// strip `mut`, which is allowed in fn args but not in struct fields
if let Pat::Ident(ident) = &mut *typed_arg.pat {