| Crates.io | lenient |
| lib.rs | lenient |
| version | 0.1.2 |
| created_at | 2025-06-04 20:26:19.682499+00 |
| updated_at | 2025-06-05 18:28:55.736392+00 |
| description | Serde-compatible wrapper for fault-tolerant (lenient) deserialization |
| homepage | |
| repository | https://github.com/masumeebhami/lenient-codegen |
| max_upload_size | |
| id | 1700850 |
| size | 10,654 |
A Rust crate for fault-tolerant deserialization using Serde. Automatically wraps fields to ensure invalid values fall back to sensible defaults โ instead of breaking the entire deserialization process.
This is useful when handling user inputs, external APIs, or anything where input reliability is uncertain.
Lenient<T>: wraps any type to gracefully fallback to T::default() on deserialization failure.Optional<T>: alias for Lenient<Option<T>>.#[derive(LenientDeserialize)]: a procedural macro to generate fault-tolerant wrappers for entire structs.#[lenient] and #[optional] attributes.tracing.Deref and DerefMut on Lenient<T>.This workspace contains two crates:
lenient/
โโโ lenient/ # Main library (Lenient wrapper, re-exports macro)
โโโ lenient_derive/ # Procedural macro crate (LenientDeserialize)
In your Cargo.toml:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = "0.1"
lenient_derive = "0.1"
use lenient_derive::LenientDeserialize;
use serde::Deserialize;
#[derive(Debug, Default)]
struct Age(pub u32);
impl<'de> Deserialize<'de> for Age {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let val = u32::deserialize(de)?;
Ok(Age(val))
}
}
#[derive(Debug, Default)]
struct Score(pub u8);
impl<'de> Deserialize<'de> for Score {
fn deserialize<D>(de: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de> {
let val = u8::deserialize(de)?;
Ok(Score(val))
}
}
#[derive(Debug, Default, LenientDeserialize)]
struct UserProfile {
#[lenient]
age: Age,
#[optional]
score: Score,
#[lenient]
nickname: String,
}
Derefuse serde_json::json;
let input = json!({ "age": "oops", "score": 90, "nickname": "Nina" });
let profile: UserProfile = serde_json::from_value(input).unwrap();
let age = profile.age.0;
let score = profile.score.0;
let nickname = &profile.nickname;
println!("User: {nickname}, Age: {age}, Score: {score}");
lenientcargo test -p lenient
#[test]
fn test_lenient_invalid() {
let json = r#"{ "age": "invalid", "score": 55, "nickname": "test" }"#;
let result: UserProfile = serde_json::from_str(json).unwrap();
assert_eq!(result.age.0, 0); // fallback to default
assert_eq!(result.nickname, "test");
}
MIT