Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example for arbitrary type validation upon deserialization. #148

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions _src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ into a Serde error for some other format using `Error::custom`.
**[Date in a custom format](custom-date-format.md)**: Handle a
[`chrono`](https://github.com/chronotope/chrono) `DateTime` formatted with a
custom string representation.

**[Validate containers on
deserialization](validate-containers-on-deserialization.md)**: Validate
a type after deserializing the underlying encoding.
62 changes: 62 additions & 0 deletions _src/validate-containers-on-deserialization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Validating containers on deserialization

When a struct must meet certain conditions across its fields, it is
useful to ensure deserialization enforces those conditions.

We can implement this pattern by introducing an intermediate type whose
deserialization is derived by `serde`, then use the `try_from` directive
on the user-facing type.

For example, suppose we have a struct `MyType` with two `Option` fields,
and we want to ensure every `MyType` value has at least one `Some` field:

```
use serde::Deserialize;
use serde_json;

// The target is to not allow deserialization if option1 & option2 are none
#[derive(Deserialize, Debug)]
#[serde(try_from = "MyTypeShadow")]
pub struct MyType {
option1: Option<usize>,
option2: Option<usize>,
}

// The shadow type only has to implement Deserialize
#[derive(Deserialize)]
struct MyTypeShadow {
option1: Option<usize>,
option2: Option<usize>,
}

pub struct MyTypeValidationError;

// The error type has to implement Display
impl std::fmt::Display for MyTypeValidationError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "option1 and option2 cannot be null")
}
}

impl std::convert::TryFrom<MyTypeShadow> for MyType {
type Error = MyTypeValidationError;
fn try_from(shadow: MyTypeShadow) -> Result<Self, Self::Error> {
let MyTypeShadow { option1, option2 } = shadow;
if option1.is_none() && option2.is_none() {
return Err(MyTypeValidationError);
}
// Any other validations
Ok(MyType { option1, option2 })
}
}

fn main() {
// This will return an Err
println!("{:?}", serde_json::from_str::<MyType>(r##"{}"##));
// This will work
println!(
"{:?}",
serde_json::from_str::<MyType>(r##"{"option1": 20}"##)
);
}
```