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
27 changes: 26 additions & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,35 @@ after formatting. Line numbers below refer to that file.
Provide naming conventions and generic bounds for the
`FrameProcessor` trait, state extractors and middleware via
`async_trait` and associated types.
- [ ] Provide a minimal, runnable example.
- [x] Provide a minimal, runnable example.
Include imports and an async `main` so the snippet compiles out of
the box.

```rust
// No extra imports required
use wireframe::{
app::{Service, WireframeApp},
server::WireframeServer,
};

async fn handler() {}

#[tokio::main]
async fn main() -> std::io::Result<()> {
let factory = || {
WireframeApp::new()
.unwrap()
.route(1, Box::new(|| Box::pin(handler())))
.unwrap()
};

WireframeServer::new(factory)
.bind("127.0.0.1:7878".parse().unwrap())?
.run()
.await
}
```

## 2. Middleware and Extractors

- [ ] Develop a minimal middleware system and extractor traits for payloads,
Expand Down
33 changes: 18 additions & 15 deletions src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<T: Send + Sync> SharedState<T> {
///
/// # Examples
///
/// ```ignore
/// ```no_run
/// use std::sync::Arc;
/// use wireframe::extractor::SharedState;
///
Expand All @@ -69,6 +69,22 @@ impl<T: Send + Sync> SharedState<T> {
/// assert_eq!(*state, 5);
/// ```
#[must_use]
pub fn new(inner: Arc<T>) -> Self {
Self(inner)
}
}

impl<T: Send + Sync> From<Arc<T>> for SharedState<T> {
fn from(inner: Arc<T>) -> Self {
Self(inner)
}
}

impl<T: Send + Sync> From<T> for SharedState<T> {
fn from(inner: T) -> Self {
Self(Arc::new(inner))
}
}

#[cfg(test)]
mod tests {
Expand All @@ -91,20 +107,6 @@ mod tests {
assert_eq!(payload.remaining(), 2);
}
}
/// Creates a new `SharedState` instance wrapping the provided `Arc<T>`.
///
/// # Examples
///
/// ```
/// use std::sync::Arc;
/// let state = Arc::new(42);
/// let shared = SharedState::new(state.clone());
/// assert_eq!(*shared, 42);
/// ```
pub fn new(inner: Arc<T>) -> Self {
Self(inner)
}
}

impl<T: Send + Sync> std::ops::Deref for SharedState<T> {
type Target = T;
Expand All @@ -117,6 +119,7 @@ impl<T: Send + Sync> std::ops::Deref for SharedState<T> {
///
/// ```
/// use std::sync::Arc;
/// use wireframe::extractor::SharedState;
/// let state = Arc::new(42);
/// let shared = SharedState::new(state.clone());
/// assert_eq!(*shared, 42);
Expand Down
45 changes: 8 additions & 37 deletions src/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,48 +20,19 @@ impl<'a, S> Next<'a, S>
where
S: Service + ?Sized,
{
/// Creates a new `Next` instance wrapping a reference to the given service.
///
///
/// ```ignore
/// use wireframe::middleware::{ServiceRequest, ServiceResponse, Next, Service};
/// ```
/// Service produced by the middleware.
type Wrapped: Service;
async fn transform(&self, service: S) -> Self::Wrapped;
/// let service = MyService::default();
/// let next = Next::new(&service);
type Wrapped: Service;
async fn transform(&self, service: S) -> Self::Wrapped;
/// Create a new [`Next`] wrapping the given service.
#[inline]
#[must_use]
pub fn new(service: &'a S) -> Self {
Self { service }
}

/// Call the next service with the given request.
/// Call the next service with the provided request.
///
/// # Errors
///
/// Asynchronously invokes the next service in the middleware chain with the given request.
///
/// Returns the response from the wrapped service, or propagates any error produced.
///
/// # Examples
///
/// ```
/// # use your_crate::{ServiceRequest, ServiceResponse, Next, Service};
/// # struct DummyService;
/// # #[async_trait::async_trait]
/// # impl Service for DummyService {
/// # type Error = std::convert::Infallible;
/// # async fn call(&self, _req: ServiceRequest) -> Result<ServiceResponse, Self::Error> {
/// # Ok(ServiceResponse::default())
/// # }
/// # }
/// # let service = DummyService;
/// let next = Next::new(&service);
/// let req = ServiceRequest {};
/// let res = tokio_test::block_on(next.call(req));
/// assert!(res.is_ok());
/// ```
/// Propagates any error produced by the wrapped service.
#[must_use = "await the returned future"]
pub async fn call(&self, req: ServiceRequest) -> Result<ServiceResponse, S::Error> {
Comment thread
leynos marked this conversation as resolved.
self.service.call(req).await
}
Expand All @@ -83,7 +54,7 @@ pub trait Transform<S>: Send + Sync
where
S: Service,
{
/// Wrapped service produced by the middleware.
/// Middleware-wrapped service produced by `transform`.
type Output: Service;

/// Create a new middleware service wrapping `service`.
Expand Down
46 changes: 11 additions & 35 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,42 +28,18 @@ impl<F> WireframeServer<F>
where
F: Fn() -> WireframeApp + Send + Sync + Clone + 'static,
{
/// Constructs a new `WireframeServer` using the provided application factory closure.
///
/// The server is initialised with a default worker count equal to the number of CPU cores.
///
/// ```no_run
/// use wireframe::{app::WireframeApp, server::WireframeServer};
///
/// let factory = || WireframeApp::new().unwrap();
/// let server = WireframeServer::new(factory);
/// ```
workers: num_cpus::get().max(1),
/// Construct a new server using the supplied application factory.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Include default worker count behavior in documentation

Please note in the documentation that the default worker count is determined by the number of CPU cores.

Suggested change
/// Construct a new server using the supplied application factory.
/// Construct a new server using the supplied application factory.
///
/// By default, the number of worker threads is set to the number of CPU cores available.

#[must_use]
pub fn new(factory: F) -> Self {
Self {
factory,
listener: None,
workers: num_cpus::get(),
}
}

/// Set the number of worker tasks to spawn for the server.
///
/// #[tokio::main]
/// async fn main() -> std::io::Result<()> {
/// let factory = || WireframeApp::new().unwrap();
/// WireframeServer::new(factory)
/// .workers(4)
/// .bind("127.0.0.1:0".parse().unwrap())?
/// .run()
/// .await
/// }
/// A new `WireframeServer` instance with the updated worker count.
///
/// # Examples
///
/// ```ignore
/// let server = WireframeServer::new(factory).workers(4);
/// Sets the number of worker tasks for the server, ensuring at least one worker.
///
/// # Examples
///
/// ```ignore
/// let server = WireframeServer::new(factory).workers(4);
/// ```
/// Set the number of worker tasks to spawn.
#[must_use]
pub fn workers(mut self, count: usize) -> Self {
self.workers = count.max(1);
self
Expand Down
Loading