Skip to content

Commit de53d0a

Browse files
committed
Merge branch 'release/v0.1.0'
2 parents 9e6853b + d7cc3cd commit de53d0a

File tree

7 files changed

+851
-0
lines changed

7 files changed

+851
-0
lines changed

.github/workflows/ci.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
test:
14+
name: Test
15+
runs-on: ubuntu-latest
16+
strategy:
17+
matrix:
18+
rust: [stable, beta, nightly]
19+
include:
20+
- rust: stable
21+
toolchain: stable
22+
- rust: beta
23+
toolchain: beta
24+
- rust: nightly
25+
toolchain: nightly
26+
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Install Rust toolchain
31+
uses: dtolnay/rust-toolchain@master
32+
with:
33+
toolchain: ${{ matrix.toolchain }}
34+
components: rustfmt, clippy
35+
36+
- name: Cache dependencies
37+
uses: Swatinem/rust-cache@v2
38+
39+
- name: Check formatting
40+
run: cargo fmt --all -- --check
41+
42+
- name: Clippy
43+
run: cargo clippy -- -D warnings
44+
45+
- name: Run tests
46+
run: cargo test --all-features
47+
48+
- name: Build documentation
49+
run: cargo doc --no-deps --document-private-items
50+
51+
publish:
52+
name: Publish
53+
needs: test
54+
runs-on: ubuntu-latest
55+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
56+
steps:
57+
- uses: actions/checkout@v4
58+
59+
- name: Install Rust toolchain
60+
uses: dtolnay/rust-toolchain@master
61+
with:
62+
toolchain: stable
63+
64+
- name: Cache dependencies
65+
uses: Swatinem/rust-cache@v2
66+
67+
- name: Publish to crates.io
68+
run: cargo publish --token ${CRATES_TOKEN}
69+
env:
70+
CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }}

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/target
2+
**/*.rs.bk
3+
Cargo.lock
4+
.idea/
5+
.vscode/
6+
*.iml
7+
*.swp
8+
*.swo
9+
.DS_Store

Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "axum-validated-extractors"
3+
version = "0.1.0"
4+
edition = "2024"
5+
description = "A collection of validated extractors for Axum that automatically validate the extracted data using the validator crate"
6+
license = "MIT"
7+
repository = "https://github.com/truehazker/axum-validated-extractors"
8+
documentation = "https://docs.rs/axum-validated-extractors"
9+
readme = "README.md"
10+
keywords = ["axum", "validation", "extractors", "web", "http"]
11+
categories = ["web-programming::http-server", "web-programming::http-client"]
12+
authors = ["Egor Orlov <40111175+truehazker@users.noreply.github.com>"]
13+
rust-version = "1.87.0"
14+
15+
[package.metadata.docs.rs]
16+
all-features = true
17+
rustdoc-args = ["--cfg", "docsrs"]
18+
19+
[dependencies]
20+
axum = { version = "0.8.4", features = ["macros"] }
21+
serde = { version = "1.0.219", features = ["derive"] }
22+
thiserror = "2.0.12"
23+
validator = { version = "0.20.0", features = ["derive"] }
24+
25+
[dev-dependencies]
26+
tokio = { version = "1.45.1", features = ["full"] }
27+
tower = { version = "0.5.2", features = ["util"] }
28+
tracing = "0.1.41"
29+
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Egor Orlov
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# axum-validated-extractors
2+
3+
[![Crates.io](https://img.shields.io/crates/v/axum-validated-extractors.svg)](https://crates.io/crates/axum-validated-extractors)
4+
[![Documentation](https://docs.rs/axum-validated-extractors/badge.svg)](https://docs.rs/axum-validated-extractors)
5+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6+
[![CI](https://github.com/truehazker/axum-validated-extractors/actions/workflows/ci.yml/badge.svg)](https://github.com/truehazker/axum-validated-extractors/actions/workflows/ci.yml)
7+
[![Rust Version](https://img.shields.io/badge/rust-1.87.0+-blue.svg)](https://www.rust-lang.org)
8+
[![Dependency Status](https://deps.rs/repo/github/truehazker/axum-validated-extractors/status.svg)](https://deps.rs/repo/github/truehazker/axum-validated-extractors)
9+
10+
A collection of validated extractors for Axum that automatically validate the extracted data using the `validator` crate. This library provides type-safe, automatic validation for your Axum web applications, making it easier to handle and validate incoming requests.
11+
12+
## Features
13+
14+
- `ValidatedForm`: Validates form data (URL-encoded or multipart)
15+
- `ValidatedJson`: Validates JSON data
16+
- `ValidatedQuery`: Validates query parameters
17+
- Automatic validation using the `validator` crate
18+
- Type-safe error handling
19+
20+
## Installation
21+
22+
Add this to your `Cargo.toml`:
23+
24+
```toml
25+
[dependencies]
26+
axum-validated-extractors = "0.1.0"
27+
validator = { version = "0.16", features = ["derive"] }
28+
```
29+
30+
## Usage
31+
32+
### Basic Example
33+
34+
```rust
35+
use axum::{
36+
routing::post,
37+
Router,
38+
};
39+
use axum_validated_extractors::{ValidatedJson, ValidatedForm, ValidatedQuery};
40+
use serde::Deserialize;
41+
use validator::Validate;
42+
43+
#[derive(Debug, Deserialize, Validate)]
44+
struct CreateUser {
45+
#[validate(length(min = 3))]
46+
username: String,
47+
#[validate(email)]
48+
email: String,
49+
}
50+
51+
async fn create_user(
52+
ValidatedJson(user): ValidatedJson<CreateUser>,
53+
) {
54+
// user is guaranteed to be valid
55+
println!("Creating user: {:?}", user);
56+
}
57+
58+
let app: Router<()> = Router::new()
59+
.route("/users", post(create_user));
60+
```
61+
62+
### Form Data
63+
64+
```rust
65+
use axum::{
66+
routing::post,
67+
Router,
68+
};
69+
use axum_validated_extractors::ValidatedForm;
70+
use serde::Deserialize;
71+
use validator::Validate;
72+
73+
#[derive(Debug, Deserialize, Validate)]
74+
struct LoginForm {
75+
#[validate(length(min = 3))]
76+
username: String,
77+
#[validate(length(min = 8))]
78+
password: String,
79+
}
80+
81+
async fn login(
82+
ValidatedForm(form): ValidatedForm<LoginForm>,
83+
) {
84+
// form is guaranteed to be valid
85+
println!("Logging in user: {:?}", form);
86+
}
87+
88+
let app: Router<()> = Router::new()
89+
.route("/login", post(login));
90+
```
91+
92+
### Query Parameters
93+
94+
```rust
95+
use axum::{
96+
routing::get,
97+
Router,
98+
};
99+
use axum_validated_extractors::ValidatedQuery;
100+
use serde::Deserialize;
101+
use validator::Validate;
102+
103+
#[derive(Debug, Deserialize, Validate)]
104+
struct SearchParams {
105+
#[validate(length(min = 2))]
106+
query: String,
107+
#[validate(range(min = 1, max = 100))]
108+
page: Option<u32>,
109+
}
110+
111+
async fn search(
112+
ValidatedQuery(params): ValidatedQuery<SearchParams>,
113+
) {
114+
// params is guaranteed to be valid
115+
println!("Searching with params: {:?}", params);
116+
}
117+
118+
let app: Router<()> = Router::new()
119+
.route("/search", get(search));
120+
```
121+
122+
## Validation Rules
123+
124+
The validation rules are provided by the `validator` crate. Here are some common validation rules:
125+
126+
```rust
127+
#[derive(Debug, Deserialize, Validate)]
128+
struct User {
129+
#[validate(length(min = 3, max = 20))]
130+
username: String,
131+
132+
#[validate(email)]
133+
email: String,
134+
135+
#[validate(range(min = 18))]
136+
age: u32,
137+
138+
#[validate(url)]
139+
website: Option<String>,
140+
141+
#[validate(regex = "^[a-zA-Z0-9_]+$")]
142+
nickname: String,
143+
}
144+
```
145+
146+
See the [validator documentation](https://docs.rs/validator) for more validation rules.
147+
148+
## Error Handling
149+
150+
The extractors return a `ValidationError` when validation fails. This error can be converted into a response:
151+
152+
```rust
153+
use axum::{
154+
routing::post,
155+
Router,
156+
response::IntoResponse,
157+
};
158+
use axum_validated_extractors::{ValidatedJson, ValidationError};
159+
use serde::Deserialize;
160+
use validator::Validate;
161+
162+
#[derive(Debug, Deserialize, Validate)]
163+
struct CreateUser {
164+
#[validate(length(min = 3))]
165+
username: String,
166+
#[validate(email)]
167+
email: String,
168+
}
169+
170+
async fn create_user(
171+
ValidatedJson(user): ValidatedJson<CreateUser>,
172+
) -> impl IntoResponse {
173+
// If validation fails, a 400 Bad Request response is returned
174+
// with a detailed error message
175+
user
176+
}
177+
178+
let app: Router<()> = Router::new()
179+
.route("/users", post(create_user));
180+
```
181+
182+
## Contributing
183+
184+
Contributions are welcome! Please feel free to submit a Pull Request.
185+
186+
## License
187+
188+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

0 commit comments

Comments
 (0)