Skip to content

Commit c5cda0e

Browse files
authored
Nested Route Groups (#164)
* feat: nested groups * updated CHANGELOG.md
1 parent 9f52ccb commit c5cda0e

File tree

11 files changed

+221
-26
lines changed

11 files changed

+221
-26
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/).
77

8-
## Unreleased
8+
## 0.8.5
99

1010
### Added
1111
* Per-status-code OpenAPI response config: `produces_*` methods now accept a status code, `IntoStatusCode` trait (supports `u16`, `u32`, `i32`, `http::StatusCode`) (#162)
1212
* OpenAPI `produces_problem()` and `produces_problem_example()` for `application/problem+json` responses, gated on `problem-details` feature (#162)
1313

14+
### Changed
15+
* Nested Route Groups support with middleware/CORS/OpenAPI isolation (#164)
16+
* Updated Global Error Handler: improved performance at the request hot-path (#163)
17+
1418
## 0.8.4
1519

1620
### Added

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Fast, simple, and high-performance web framework for Rust, built on top of
55
Volga is designed to make building HTTP services straightforward and explicit,
66
while keeping performance predictable and overhead minimal.
77

8-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
8+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
99
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
1010
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
1111
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)
@@ -46,7 +46,7 @@ Volga is a good fit if you:
4646
### Dependencies
4747
```toml
4848
[dependencies]
49-
volga = "0.8.4"
49+
volga = "0.8.5"
5050
tokio = { version = "1", features = ["full"] }
5151
```
5252
### Simple request handler

examples/open_api/src/main.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ async fn main() -> std::io::Result<()> {
3636
api.map_get("/hello", async || "Hello, World!");
3737
api.map_get("/{name}", async |name: String| ok!(fmt: "Hello {name}"));
3838
api.map_get("/{name}/{age:integer}", async |Path((_name, _age)): Path<(String, u32)>| {});
39-
api.map_get("/named/{name}/{age}", async |path: NamedPath<Payload>| ok!(path.into_inner()))
40-
.open_api(|cfg| cfg
41-
.produces_json::<Payload>(200)
42-
.produces_no_schema(400));
39+
40+
api.group("/named", |api| {
41+
api.map_get("/{name}/{age}", async |path: NamedPath<Payload>| ok!(path.into_inner()))
42+
.open_api(|cfg| cfg
43+
.produces_json::<Payload>(200)
44+
.produces_no_schema(400));
45+
});
4346
});
4447

45-
app.group("/file", |api| {
48+
app.group("/files", |api| {
4649
api.open_api(|cfg| cfg.with_docs(["v1", "v2"]));
4750

4851
api.map_post("/file", async |file: File| file.into_byte_stream());

examples/route_groups/src/main.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@ use volga::{App, HttpResult, ok};
1010
async fn main() -> std::io::Result<()> {
1111
let mut app = App::new();
1212

13-
app.group("/user", |group| {
14-
group.map_get("/{id}", get_user);
15-
group.map_post("/create/{name}", create_user);
13+
app.group("/v1", |v1| {
14+
v1.group("/user", |api| {
15+
api.map_get("/{id}", get_user);
16+
api.map_post("/create/{name}", create_user);
17+
});
18+
});
19+
20+
app.group("/v2", |v2| {
21+
v2.group("/user", |api| {
22+
api.map_get("/{id}", get_user);
23+
api.map_post("/create/{name}", create_user);
24+
});
1625
});
1726

1827
app.run().await

volga-dev-cert/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Volga Development Certificates
22
A Rust library for generating self-signed TLS certificates for local development.
33

4-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
4+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
55
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
77
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)

volga-di/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Volga DI
22
A standalone, flexible, and easy-to-configure DI container.
33

4-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
4+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
55
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
77
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)

volga-macros/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Volga Macros
22
Macros library for Volga Web Framework
33

4-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
4+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
55
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
77
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)

volga-open-api/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ OpenAPI 3.0 integration for the **Volga** web framework.
66

77
It is fully optional and designed to stay out of your way.
88

9-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
9+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
1010
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
1111
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
1212
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)

volga-rate-limiter/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A lightweight and efficient rate-limiting library for Rust.
55
This crate provides in-memory rate limiting algorithms designed
66
for high-performance HTTP services and middleware.
77

8-
[![latest](https://img.shields.io/badge/latest-0.8.4-blue)](https://crates.io/crates/volga)
8+
[![latest](https://img.shields.io/badge/latest-0.8.5-blue)](https://crates.io/crates/volga)
99
[![latest](https://img.shields.io/badge/rustc-1.90+-964B00)](https://crates.io/crates/volga)
1010
[![License: MIT](https://img.shields.io/badge/License-MIT-violet.svg)](https://github.com/RomanEmreis/volga/blob/main/LICENSE)
1111
[![Build](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml/badge.svg)](https://github.com/RomanEmreis/volga/actions/workflows/rust.yml)

volga/src/app/router.rs

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ impl App {
4949
///# fn get_user() -> User { unimplemented!() }
5050
///# fn create_user(user: Json<User>) -> i32 { unimplemented!() }
5151
/// ```
52-
pub fn group<'a, F>(&'a mut self, prefix: &'a str, f: F)
53-
where
54-
F: FnOnce(&mut RouteGroup<'a>)
52+
pub fn group<F>(&mut self, prefix: &str, f: F)
53+
where
54+
F: FnOnce(&mut RouteGroup<'_>)
5555
{
5656
let mut group = RouteGroup::new(self, prefix);
5757

@@ -62,15 +62,15 @@ impl App {
6262
}
6363

6464
/// Adds a request handler that matches HTTP GET requests for the specified pattern.
65-
///
65+
///
6666
/// # Examples
6767
/// ```no_run
6868
/// use volga::{App, ok};
6969
///
7070
///# #[tokio::main]
7171
///# async fn main() -> std::io::Result<()> {
7272
/// let mut app = App::new();
73-
///
73+
///
7474
/// app.map_get("/hello", || async {
7575
/// ok!("Hello World!")
7676
/// });
@@ -380,7 +380,7 @@ pub struct Route<'a> {
380380
/// Represents a group of routes
381381
pub struct RouteGroup<'a> {
382382
pub(crate) app: &'a mut App,
383-
pub(crate) prefix: &'a str,
383+
pub(crate) prefix: String,
384384
pub(crate) route_count: usize,
385385
#[cfg(feature = "middleware")]
386386
pub(crate) middleware: Vec<MiddlewareFn>,
@@ -452,13 +452,66 @@ impl<'a> RouteGroup<'a> {
452452
}
453453
}
454454

455+
impl<'a> RouteGroup<'a> {
456+
/// Maps a sub-group of request handlers combined by `sub_prefix`.
457+
///
458+
/// Inherits the parent group's middleware, CORS policy, and OpenAPI
459+
/// configuration. Any middleware or settings added to the sub-group
460+
/// apply only to routes within it (and any further nested groups),
461+
/// running after the parent's middleware.
462+
///
463+
/// # Examples
464+
/// ```no_run
465+
/// use volga::{App, ok};
466+
///
467+
///# #[tokio::main]
468+
///# async fn main() -> std::io::Result<()> {
469+
/// let mut app = App::new();
470+
///
471+
/// app.group("/api", |api| {
472+
/// api.map_get("/info", || async { ok!() });
473+
///
474+
/// api.group("/users", |users| {
475+
/// users.map_get("/{id}", |id: i32| async move { ok!(id) });
476+
/// });
477+
/// });
478+
///# app.run().await
479+
///# }
480+
/// ```
481+
pub fn group<F>(&mut self, sub_prefix: &str, f: F)
482+
where
483+
F: FnOnce(&mut RouteGroup<'_>)
484+
{
485+
let full_prefix = [self.prefix.as_str(), sub_prefix].concat();
486+
let mut child = RouteGroup {
487+
app: self.app,
488+
prefix: full_prefix,
489+
route_count: 0,
490+
#[cfg(feature = "middleware")]
491+
middleware: self.middleware.clone(),
492+
#[cfg(feature = "middleware")]
493+
cors: self.cors.clone(),
494+
#[cfg(feature = "openapi")]
495+
openapi_config: self.openapi_config.clone(),
496+
};
497+
498+
#[cfg(feature = "openapi")]
499+
{
500+
let tag = child.prefix.clone();
501+
child.open_api(|cfg| cfg.with_tag(tag));
502+
}
503+
504+
f(&mut child);
505+
}
506+
}
507+
455508
macro_rules! define_route_group_methods {
456509
($(($fn_name:ident, $http_method:expr))*) => {
457510
impl<'a> RouteGroup<'a> {
458-
fn new(app: &'a mut App, prefix: &'a str) -> Self {
511+
fn new(app: &'a mut App, prefix: &str) -> Self {
459512
RouteGroup {
460513
app,
461-
prefix,
514+
prefix: prefix.to_string(),
462515
route_count: 0,
463516
#[cfg(feature = "middleware")]
464517
middleware: Vec::with_capacity(4),
@@ -478,7 +531,7 @@ macro_rules! define_route_group_methods {
478531
Args: FromRequest + Send + 'static,
479532
{
480533
self.route_count += 1;
481-
let pattern = [self.prefix, pattern].concat();
534+
let pattern = [self.prefix.as_str(), pattern].concat();
482535

483536
#[cfg(feature = "middleware")]
484537
{

0 commit comments

Comments
 (0)