trydeを使用したserdeのフィールドと型の検証


serde Rustで最も人気のあるシリアル化/シリアル化のフレームワークかもしれませんが、BOXの検証をサポートしていません.
しかし、TryFrom 特性 #[serde(try_from = "FromType")] , 逆シリアル化するときに、簡単に型とフィールドを検証できます.

スカラ値の検証
私たちは、ユーザーのメールを構築する前に検証するユーザーシステムを開発している想像してください.Rustでは、1つの要素タプル構造を定義できますString メールを表す.
pub struct Email(String);
Email 検証後にのみ構築する必要があります.try_new を構築する唯一の方法としてEmail .
impl Email {
    // Here we use a String to represent error just for simplicity
    // You can define a custom enum type like EmailParseError in your application
    pub fn try_new(email: String) -> Result<Self, String> {
        if validate_email(&email) {
            Ok(Self(email))
        } else {
            Err(format!("Invalid email {}", email))
        }
    }
}
そして、内部を消費するか、参照する若干の方法String
impl Email {
    pub fn into_inner(self) -> String { self.0 }
    pub fn inner(&self) -> &String { &self.0 }
}
上のコードでEmail , 内部の文字列が既に検証されていることを知っています我々があるならばString , 電話しなければならないEmail::try_new を検証します.
今、我々はEmail structと私たちはserde付きの文字列からそれを逆シリアル化したいです.以下のようにします:
use serde::Deserialize;

#[derive(Deserialize)]
pub struct Email(String);

let email: Email = serde_json::from_str("\"some_json_string\"").unwrap();
// Email("some_json_string".to_string())
現在Email 文字列から逆シリアル化できますが、検証されません!
ありがとうtry_from Serdeの属性は、文字列を逆シリアル化するためにSerdeに伝えることができますString 第一にString to Email::try_from 得るEmail .
use serde::Deserialize;
use std::convert::TryFrom;

#[derive(Deserialize)]
// Here we tell serde to call `Email::try_from` with a `String`
#[serde(try_from = "String")]
pub struct Email(String);

impl TryFrom<String> for Email {
    type Error = String;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Email::try_new(value)
    }
}
let email: Email = serde_json::from_str("\"[email protected]\"").unwrap();
// Email("[email protected]".to_string())
逆シリアル化のための上記のコードは次のようになります.
let string_value: String = serde_json::from_str("\"[email protected]\"").unwrap();
let email: Email::try_from(string_value).unwrap();

検証フィールド
簡単に使えますEmail structを逆シリアル化するときに検証するフィールドの型です.
#[derive(Deserialize)]
pub struct User {
    name: String,
    email: Email,
}

let user: User = serde_json::from_str(
    r#"{"name": "Alice", "email": "[email protected]"}"#
).unwrap();
// User {
//     name: "Alice".to_string(),
//     email: Email("[email protected]".to_string()),
// }

構造の検証
構造体自体を構築する前に検証する必要があります.例えば、入力構造体ValueRange 2つのフィールドmin and max . minmax .
同様Email 我々は定義することができますValueRange 次のようにします.
pub struct ValueRange {
    min: i32,
    max: i32,
}

impl ValueRange {
    pub fn try_new(min: i32, max: i32) -> Result<Self, String> {
        if min <= max {
            Ok(ValueRange { min, max })
        } else {
            Err("Invalid ValueRange".to_string())
        }
    }

    pub fn min(&self) -> i32 {
        self.min
    }
    pub fn max(&self) -> i32 {
        self.max
    }
}
呼び出しに注意ValueRange::try_new を構築する唯一の方法はValueRange . しかし、我々がちょうど得るならば#[derive(Deserialize)] for ValueRange , 検証せずに逆シリアル化されます.
したがって、新しいタイプを導入することができますValueRangeUnchecked と同じデータ構造を共有するValueRange .
#[derive(Deserialize)]
struct ValueRangeUnchecked {
    min: i32,
    max: i32,
}
それから、データを逆シリアル化するためにSerdeに話してくださいValueRangeUnchecked 最初に変換してValueRange 呼び出しによってValueRange::try_from .
#[derive(Deserialize)]
#[serde(try_from = "ValueRangeUnchecked")]
pub struct ValueRange {
    min: i32,
    max: i32,
}

impl TryFrom<ValueRangeUnchecked> for ValueRange {
    type Error = String;

    fn try_from(value: ValueRangeUnchecked) -> Result<Self, Self::Error> {
        let ValueRangeUnchecked { min, max } = value;
        Self::try_new(min, max)
    }
}
我々は保つことができることに注意してくださいValueRangeUnchecked それは逆シリアル化で個人的にのみ使用されるように、このmodに見える.
let range: ValueRange = serde_json::from_str(r#"{"min": 1, "max": 10}"#).unwrap();
この逆シリアル化のコードは以下のようになります.
let range_unchecked: ValueRangeUnchecked = serde_json::from_str(r#"{"min": 1, "max": 10}"#).unwrap();
let range: ValueRange = ValueRange::try_from(range_unchecked).unwrap();

フルコード
完全な作業コードでは、このrepoをチェックアウトできます.

EqualMa / serde-validation-with-try-from
tryfrom特性を持つserdeのフィールドを検証する
読書ありがとう!このポストがあなたを助けるならば、あなたはそうすることができますbuy me a coffee ♥.