feat: add CommonMiddleware - #639
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new CommonMiddleware layer to bundle the default middleware stack used by generated Cot projects, while keeping the same middleware ordering and making each child middleware individually configurable via [middlewares]. This reduces boilerplate in templates/docs and introduces regression coverage for config-driven behavior (notably trailing-slash handling).
Changes:
- Introduce
CommonMiddlewareincotto compose trailing-slash, live-reload, session, auth, and static-files middleware with per-middleware enable flags. - Extend configuration to support
auth,static_files, andtrailing_slashenable toggles (and addenabledto session config), with defaults preserving existing generated-project behavior. - Update the project template and documentation to use
CommonMiddleware, and add tests validating config-driven behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| docs/introduction.md | Updates the guide example to use CommonMiddleware and explains per-child runtime configurability. |
| cot/src/middleware.rs | Implements CommonMiddleware and adds a regression test ensuring it respects trailing-slash enablement. |
| cot/src/config.rs | Adds EnabledMiddlewareConfig, extends MiddlewareConfig with new enable toggles, and updates config parsing tests. |
| cot-cli/src/project_template/src/main.rs | Simplifies generated project boilerplate by replacing individual middleware wiring with CommonMiddleware. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
ElijahAhianyo
left a comment
There was a problem hiding this comment.
Thanks for your contribution! Please address the comments and then we can merge this. Also, please feel free to ask questions on anything you need clarity on.
| type ConfiguredLiveReloadMiddleware = LiveReloadMiddleware; | ||
| #[cfg(not(feature = "live-reload"))] | ||
| type ConfiguredLiveReloadMiddleware = tower::layer::util::Identity; | ||
| type CommonMiddlewareLayer = ( |
There was a problem hiding this comment.
I'm not exactly sure if this is the right approach. I would expect the CommonMiddleware to be a stack of default middlewares that are enabled as a group by default without users manually registering them at startup time, not particularly one where some can be enabled and some disabled. That defeats the purpose of grouping them. If users want all default middlewares enabled, they should only have to register this middleware; however, they should be able to opt out and manually register ones they want.
A rough idea I had in mind was along the lines of:
use tower::Layer;
use crate::middleware::TrailingSlashMiddleware;
use crate::project::MiddlewareContext;
#[derive(Debug, Clone)]
pub struct CommonMiddleware {
trailing_slash: TrailingSlashMiddleware,
}
impl CommonMiddleware {
#[must_use]
pub fn from_context(context: &MiddlewareContext) -> Self {
Self {
trailing_slash: TrailingSlashMiddleware::from_context(context),
}
}
}
impl<S> Layer<S> for CommonMiddleware {
type Service = <TrailingSlashMiddleware as Layer<S>>::Service;
fn layer(&self, inner: S) -> Self::Service {
self.trailing_slash.layer(inner)
}
}If we decide to add the session and auth middlewares(as an example) to the common middlewares, then it will look something like this:
#[derive(Debug, Clone)]
pub struct CommonMiddleware {
trailing_slash: TrailingSlashMiddleware,
session: SessionMiddleware,
auth: AuthMiddleware,
}
impl CommonMiddleware {
#[must_use]
pub fn from_context(context: &MiddlewareContext) -> Self {
Self {
trailing_slash: TrailingSlashMiddleware::from_context(context),
session: SessionMiddleware::from_context(context),
auth: AuthMiddleware::new(),
}
}
}
impl<S> Layer<S> for CommonMiddleware
where
AuthMiddleware: Layer<S>,
SessionMiddleware: Layer<<AuthMiddleware as Layer<S>>::Service>,
TrailingSlashMiddleware:
Layer<<SessionMiddleware as Layer<<AuthMiddleware as Layer<S>>::Service>>::Service>,
{
type Service = <TrailingSlashMiddleware as Layer<
<SessionMiddleware as Layer<<AuthMiddleware as Layer<S>>::Service>>::Service,
>>::Service;
fn layer(&self, inner: S) -> Self::Service {
let service = self.auth.layer(inner);
let service = self.session.layer(service);
self.trailing_slash.layer(service)
}
}What I am not sure of is if we want to have the auth, session, and live middlewares as part of the common middlewares since they may not necessarily be the common denominator of every app. @m4tx Do you want to weigh in on which middlewares should be labelled as common?
One other thing to note, which I believe should be a separate issue(and not within the scope of this PR), is that registering the common middleware along with middlewares already present in the common middleware (or duplicating middlewares in general) could be wasteful.
handler
.middleware(CommonMiddleware::from_context(context))
.middleware(TrailingSlashMiddleware::from_context(context))I believe this should be permitted; however, we should probably warn the user of this at runtime
Related issue or discussion
Fixes #637.
Description
Adds
CommonMiddlewareto consolidate the five middlewares enabled by the generated project template while preserving their existing layer order. Each child middleware can be enabled or disabled under[middlewares]; defaults preserve the current generated-project behavior.The project template and introduction now use
CommonMiddleware, and configuration plus trailing-slash behavior have regression coverage.Type of change
Checklist
just test-all)just clippy)cargo fmt)Additional validation: MSRV 1.94,
--no-default-features, and 154 external-dependency tests with the repository Docker Compose services.