Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,12 @@ after formatting. Line numbers below refer to that file.

- [ ] Implement middleware using `Transform`/`Service` traits.

- [ ] Implement `ServiceRequest` and `ServiceResponse` wrappers (lines
- [x] Implement `ServiceRequest` and `ServiceResponse` wrappers (lines
866-899) and introduce a `Next` helper to build the asynchronous call
chain. Trait definitions live in
[`src/middleware.rs`](../src/middleware.rs#L71-L84).
- [ ] Provide a `from_fn` helper for functional middleware.
- [ ] Add tests verifying middleware can modify requests and observe
- [x] Add tests verifying middleware can modify requests and observe
responses.

- [ ] Register middleware with `WireframeApp::wrap` and build the chain around
Expand Down
53 changes: 47 additions & 6 deletions src/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,58 @@
//! Traits and helpers for request middleware.
//! Middleware traits and helpers.
//!
//! Middleware components implement [`Transform`] to wrap services and
//! process `ServiceRequest` instances before passing them along the chain.
//! This module defines the asynchronous [`Service`] and [`Transform`] traits,
//! along with [`ServiceRequest`] and [`ServiceResponse`] wrappers. Middleware
//! components use the [`Next`] helper to call the next service in the chain.

use async_trait::async_trait;

/// Incoming request wrapper passed through middleware.
#[derive(Debug)]
pub struct ServiceRequest;
pub struct ServiceRequest {
frame: Vec<u8>,
}

impl ServiceRequest {
/// Create a new [`ServiceRequest`] from raw frame bytes.
#[must_use]
pub fn new(frame: Vec<u8>) -> Self { Self { frame } }

/// Borrow the underlying frame bytes.
#[must_use]
pub fn frame(&self) -> &[u8] { &self.frame }

/// Mutable access to the inner frame bytes.
#[must_use]
pub fn frame_mut(&mut self) -> &mut Vec<u8> { &mut self.frame }

/// Consume the request, returning the inner frame bytes.
#[must_use]
pub fn into_inner(self) -> Vec<u8> { self.frame }
}

/// Response produced by a handler or middleware.
#[derive(Debug, Default)]
pub struct ServiceResponse;
pub struct ServiceResponse {
frame: Vec<u8>,
}

impl ServiceResponse {
/// Create a new [`ServiceResponse`] containing the given frame bytes.
#[must_use]
pub fn new(frame: Vec<u8>) -> Self { Self { frame } }

/// Borrow the inner frame bytes.
#[must_use]
pub fn frame(&self) -> &[u8] { &self.frame }

/// Mutable access to the response frame bytes.
#[must_use]
pub fn frame_mut(&mut self) -> &mut Vec<u8> { &mut self.frame }

/// Consume the response, yielding the raw frame bytes.
#[must_use]
pub fn into_inner(self) -> Vec<u8> { self.frame }
}

/// Continuation used by middleware to call the next service in the chain.
pub struct Next<'a, S>
Expand All @@ -25,7 +66,7 @@ impl<'a, S> Next<'a, S>
where
S: Service + ?Sized,
{
/// Creates a new [`Next`] instance wrapping a reference to the given service.
/// Creates a new `Next` instance wrapping a reference to `service`.
///
/// # Examples
///
Expand Down
56 changes: 56 additions & 0 deletions tests/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use async_trait::async_trait;
use wireframe::middleware::{Next, Service, ServiceRequest, ServiceResponse, Transform};

struct EchoService;

#[async_trait]
impl Service for EchoService {
type Error = std::convert::Infallible;

async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
Ok(ServiceResponse::new(req.into_inner()))
}
}

struct ModifyMiddleware;

struct ModifyService<S> {
inner: S,
}

#[async_trait]
impl<S> Transform<S> for ModifyMiddleware
where
S: Service + Send + Sync + 'static,
{
type Output = ModifyService<S>;

async fn transform(&self, service: S) -> Self::Output { ModifyService { inner: service } }
}

#[async_trait]
impl<S> Service for ModifyService<S>
where
S: Service + Send + Sync + 'static,
{
type Error = S::Error;

async fn call(&self, mut request: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
request.frame_mut().push(b'!');
let next = Next::new(&self.inner);
let mut response = next.call(request).await?;
response.frame_mut().push(b'?');
Ok(response)
}
}

#[tokio::test]
async fn middleware_modifies_request_and_response() {
let service = EchoService;
let mw = ModifyMiddleware;
let wrapped = mw.transform(service).await;

let request = ServiceRequest::new(vec![1, 2, 3]);
let response = wrapped.call(request).await.unwrap();
assert_eq!(response.frame(), &[1, 2, 3, b'!', b'?']);
}