From 1d58854cf3232d3e9a6baff498d6bd2c607a3168 Mon Sep 17 00:00:00 2001 From: Nate Wilcox Date: Mon, 8 May 2023 15:25:01 -0700 Subject: [PATCH] Add an example for arbitrary type validation upon deserialization. --- _src/examples.md | 4 ++ .../validate-containers-on-deserialization.md | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 _src/validate-containers-on-deserialization.md diff --git a/_src/examples.md b/_src/examples.md index efa66829..31bf3f50 100644 --- a/_src/examples.md +++ b/_src/examples.md @@ -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. diff --git a/_src/validate-containers-on-deserialization.md b/_src/validate-containers-on-deserialization.md new file mode 100644 index 00000000..7f8df3a1 --- /dev/null +++ b/_src/validate-containers-on-deserialization.md @@ -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, + option2: Option, +} + +// The shadow type only has to implement Deserialize +#[derive(Deserialize)] +struct MyTypeShadow { + option1: Option, + option2: Option, +} + +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 for MyType { + type Error = MyTypeValidationError; + fn try_from(shadow: MyTypeShadow) -> Result { + 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::(r##"{}"##)); + // This will work + println!( + "{:?}", + serde_json::from_str::(r##"{"option1": 20}"##) + ); +} +```