Skip to content

Commit

Permalink
add BorrowState trait for better diagnostic on compile error.
Browse files Browse the repository at this point in the history
  • Loading branch information
fakeshadow committed Jul 1, 2024
1 parent b56ebfc commit f4ff1b2
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 22 deletions.
8 changes: 6 additions & 2 deletions codegen/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# unreleased 0.2.0
# unreleased 0.3.0
## Change
- macro is refactored to targeting xitca-web 0.4.0.
- macro is refactored to target xitca-web `0.6.0`

# 0.2.0
## Change
- macro is refactored to target xitca-web `0.4.0`

# 0.1.1
## Fix
Expand Down
2 changes: 1 addition & 1 deletion codegen/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xitca-codegen"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
license = "Apache-2.0"
description = "proc macro for xitca"
Expand Down
2 changes: 1 addition & 1 deletion codegen/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub(crate) fn state(input: DeriveInput) -> Result<TokenStream, Error> {
let ty = &field.ty;

quote! {
impl ::core::borrow::Borrow<#ty> for #ty_ident {
impl ::xitca_web::handler::state::BorrowState<#ty> for #ty_ident {
fn borrow(&self) -> &#ty {
&self.#ident
}
Expand Down
15 changes: 15 additions & 0 deletions http/src/util/service/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,11 @@ where
impl<E> error::Error for RouterError<E> where E: error::Error {}

/// trait for specialized route generation when utilizing [Router::insert].
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl PathGen trait",
label = "route must impl PathGen trait for specialized route path configuration",
note = "consider add `impl PathGen for {Self}`"
)]
pub trait PathGen {
/// path generator.
///
Expand All @@ -147,6 +152,11 @@ pub trait PathGen {
}

/// trait for specialized route generation when utilizing [Router::insert].
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl RouteGen trait",
label = "route must impl RouteGen trait for specialized route path and service configuration",
note = "consider add `impl PathGen for {Self}` and `impl RouteGen for {Self}`"
)]
pub trait RouteGen: PathGen {
/// service builder type for generating the final route service.
type Route<R>;
Expand Down Expand Up @@ -286,6 +296,11 @@ where
/// A [Service] type, for example, may be type-erased into `Box<dyn Service<&'static str>>`,
/// `Box<dyn for<'a> Service<&'a str>>`, `Box<dyn Service<&'static str> + Service<u8>>`, etc.
/// Each would be a separate impl for [IntoObject].
#[diagnostic::on_unimplemented(
message = "`{Self}` does not impl IntoObject trait",
label = "route must impl IntoObject trait for specialized type erased service type",
note = "consider add `impl IntoObject<_> for {Self}`"
)]
pub trait IntoObject<I, Arg> {
/// The type-erased form of `I`.
type Object;
Expand Down
27 changes: 27 additions & 0 deletions web/CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# unreleased 0.6.0
## Add
- add `handler::state::BorrowState` trait for partial borrowing sub-typed state

## Change
- add support for scoped application state
```rust
Expand All @@ -16,6 +19,30 @@
assert_eq!(*state, 996isize)
}
```
- use `handler::state::BorrowState` trait instead of `std::borrow::Borrow` for borrowing sub-typed state
```rust
use xitca_web::handler::state::{BorrowState, StateRef};

// arbitrary state type
struct State {
field: String
}

// impl trait for borrowing String type from State
impl BorrowState<String> for State {
fn borrow(&self) -> &String {
&self.field
}
}

// handler function use &String as typed state.
async fn handler(StateRef(state): StateRef<'_, String>) -> String {
assert_eq!(state, "996");
state.into()
}

App::new().with_state(State { field: String::from("996") }).at("/foo", handler_service(handler));
```
- remove generic type param from `IntoCtx` trait
- bump MSRV to `1.79`
- update `xitca-http` to `0.6.0`
Expand Down
4 changes: 2 additions & 2 deletions web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,15 +124,15 @@ tracing = { version = "0.1", optional = true }
tracing-subscriber = { version = "0.3", optional = true }

# codegen
xitca-codegen = { version = "0.2", optional = true }
xitca-codegen = { version = "0.3", optional = true }

# tower-http-compat
tower-service = { version = "0.3", optional = true }
tower-layer = { version = "0.3", optional = true }
http-body = { version = "1", optional = true }

[dev-dependencies]
xitca-codegen = { version = "0.2" }
xitca-codegen = { version = "0.3" }

futures-util = { version = "0.3", features = ["alloc"] }
serde = { version = "1.0.137", features = ["derive"] }
Expand Down
7 changes: 3 additions & 4 deletions web/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,9 @@ impl<R, CF> App<R, CF> {
///
/// # Example
/// ```rust
/// # use std::borrow::Borrow;
/// # use xitca_web::{
/// # error::Error,
/// # handler::{handler_service, state::StateRef, FromRequest},
/// # handler::{handler_service, state::{BorrowState, StateRef}, FromRequest},
/// # service::Service,
/// # App, WebContext
/// # };
Expand All @@ -337,7 +336,7 @@ impl<R, CF> App<R, CF> {
/// }
///
/// // implement Borrow trait to enable borrowing &String type from State.
/// impl Borrow<String> for State {
/// impl BorrowState<String> for State {
/// fn borrow(&self) -> &String {
/// &self.string
/// }
Expand All @@ -360,7 +359,7 @@ impl<R, CF> App<R, CF> {
/// async fn middleware_fn<S, C, Res>(service: &S, ctx: WebContext<'_, C>) -> Result<Res, Error<C>>
/// where
/// S: for<'r> Service<WebContext<'r, C>, Response = Res, Error = Error<C>>,
/// C: Borrow<String> // annotate we want to borrow &String from generic C state type.
/// C: BorrowState<String> // annotate we want to borrow &String from generic C state type.
/// {
/// // WebContext::state would return &C then we can call Borrow::borrow on it to get &String
/// let _string = ctx.state().borrow();
Expand Down
70 changes: 58 additions & 12 deletions web/src/handler/types/state.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
//! type extractor or application state.
use core::{borrow::Borrow, fmt, ops::Deref};
use core::{fmt, ops::Deref};

use crate::{context::WebContext, error::Error, handler::FromRequest};

/// borrow trait for extracting typed field from application state
#[diagnostic::on_unimplemented(
message = "`{T}` can not be borrowed from {Self}",
label = "{Self} must impl BorrowState trait for borrowing {T} from app state",
note = "consider add `impl BorrowState<{T}> for {Self}`"
)]
pub trait BorrowState<T>
where
T: ?Sized,
{
fn borrow(&self) -> &T;
}

impl<T> BorrowState<T> for T
where
T: ?Sized,
{
#[inline]
fn borrow(&self) -> &T {
self
}
}

/// App state extractor.
/// S type must be the same with the type passed to App::with_xxx_state(S).
pub struct StateRef<'a, S>(pub &'a S)
where
S: ?Sized;

impl<S: fmt::Debug> fmt::Debug for StateRef<'_, S> {
impl<S> fmt::Debug for StateRef<'_, S>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StateRef({:?})", self.0)
}
}

impl<S: fmt::Display> fmt::Display for StateRef<'_, S> {
impl<S> fmt::Display for StateRef<'_, S>
where
S: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StateRef({})", self.0)
}
Expand All @@ -25,14 +54,15 @@ impl<S: fmt::Display> fmt::Display for StateRef<'_, S> {
impl<S> Deref for StateRef<'_, S> {
type Target = S;

#[inline]
fn deref(&self) -> &Self::Target {
self.0
}
}

impl<'a, 'r, C, B, T> FromRequest<'a, WebContext<'r, C, B>> for StateRef<'a, T>
where
C: Borrow<T>,
C: BorrowState<T>,
T: ?Sized + 'static,
{
type Type<'b> = StateRef<'b, T>;
Expand All @@ -48,13 +78,19 @@ where
/// S type must be the same with the type passed to App::with_xxx_state(S).
pub struct StateOwn<S>(pub S);

impl<S: fmt::Debug> fmt::Debug for StateOwn<S> {
impl<S> fmt::Debug for StateOwn<S>
where
S: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StateOwn({:?})", self.0)
}
}

impl<S: fmt::Display> fmt::Display for StateOwn<S> {
impl<S> fmt::Display for StateOwn<S>
where
S: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "StateOwn({})", self.0)
}
Expand All @@ -63,14 +99,15 @@ impl<S: fmt::Display> fmt::Display for StateOwn<S> {
impl<S> Deref for StateOwn<S> {
type Target = S;

#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<'a, 'r, C, B, T> FromRequest<'a, WebContext<'r, C, B>> for StateOwn<T>
where
C: Borrow<T>,
C: BorrowState<T>,
T: Clone,
{
type Type<'b> = StateOwn<T>;
Expand All @@ -86,23 +123,32 @@ where
mod test {
use std::sync::Arc;

use xitca_codegen::State;
use xitca_unsafe_collection::futures::NowOrPanic;

use crate::{handler::handler_service, http::WebRequest, route::get, service::Service, App};

use super::*;

#[derive(State, Clone, Debug)]
#[derive(Clone, Debug)]
struct State {
#[borrow]
field1: String,
#[borrow]
field2: u32,
field3: Arc<dyn std::any::Any + Send + Sync>,
}

impl Borrow<dyn std::any::Any + Send + Sync> for State {
impl BorrowState<String> for State {
fn borrow(&self) -> &String {
&self.field1
}
}

impl BorrowState<u32> for State {
fn borrow(&self) -> &u32 {
&self.field2
}
}

impl BorrowState<dyn std::any::Any + Send + Sync> for State {
fn borrow(&self) -> &(dyn std::any::Any + Send + Sync) {
&*self.field3
}
Expand Down

0 comments on commit f4ff1b2

Please sign in to comment.