#error #deserialize #json-parser #serialization #default-value #serde-default

deserr

专注于错误处理的反序列化库

13次发布

0.6.2 2024年7月2日
0.6.1 2023年9月20日
0.6.0 2023年5月11日
0.5.0 2023年2月16日
0.1.0 2022年12月13日

#110 in 编码

Download history 1961/week @ 2024-04-25 2166/week @ 2024-05-02 1982/week @ 2024-05-09 2086/week @ 2024-05-16 1773/week @ 2024-05-23 2008/week @ 2024-05-30 1841/week @ 2024-06-06 1988/week @ 2024-06-13 2169/week @ 2024-06-20 2323/week @ 2024-06-27 1227/week @ 2024-07-04 1195/week @ 2024-07-11 958/week @ 2024-07-18 1107/week @ 2024-07-25 846/week @ 2024-08-01 802/week @ 2024-08-08

3,864 每月下载量

MIT/Apache

145KB
2.5K SLoC

Deserr

简介

Deserr是一个用于反序列化数据的crate,能够在失败时返回自定义、类型特定的错误。它还考虑了用户界面API,因此在此用例中提供了比serde更好的默认设置。

与serde不同,deserr本身不解析其序列化格式的数据,而是将工作委托给其他crate。相反,它将已解析的序列化数据反序列化到最终类型。例如

// bytes of the serialized value
let s: &str = ".." ;
// parse serialized data using another crate, such as `serde_json`
let json: serde_json::Value = serde_json::from_str(s).unwrap();
// finally deserialize with deserr
let data = T::deserialize_from_value(json.into_value()).unwrap();
// `T` must implement `Deserr`.

为什么我会使用它

你应该使用deserr的主要地方是在你的用户界面API上,特别是如果它打算由人类阅读。由于deserr让你完全控制你的错误类型,你可以提高错误消息的质量。以下是一些使用deserr的示例

假设我发送了这个有效载荷来更新我的 Meilisearch 设置

{
  "filterableAttributes": ["doggo.age", "catto.age"],
  "sortableAttributes": ["uploaded_at"],
  "typoTolerance": {
    "minWordSizeForTypos": {
      "oneTypo": 1000, "twoTypo": 80
    },
    "enabled": true
  },
  "displayedAttributes": ["*"],
  "searchableAttributes": ["doggo.name", "catto.name"]
}

使用serde

使用serde,我们几乎没有定制;这是我们会得到的一种典型消息

{
  "message": "Json deserialize error: invalid value: integer `1000`, expected u8 at line 6 column 21",
  "code": "bad_request",
  "type": "invalid_request",
  "link": "https://docs.meilisearch.com/errors#bad_request"
}
消息

Json反序列化错误:无效值:整数 1000,在行6列21期望u8

  • 消息使用了单词 u8,这对于不了解rust或对类型不熟悉的人来说肯定没有帮助。
  • 位置是以行和列的形式提供的。虽然这通常很好,但当大多数用户在终端中阅读此消息时,实际上并没有多少帮助。

有效载荷的其余部分

由于serde返回了此错误,我们无法知道发生了什么,或者是在哪个字段发生的。因此,我们能做的最好的事情是生成一个适用于我们整个API的通用代码 bad_request。然后,我们使用这个代码生成指向我们文档的链接,以帮助我们的用户。但是这样的通用链接并不能帮助我们的用户,因为它可以被Meilisearch的每个路由抛出。

使用deserr

{
  "message": "Invalid value at `.typoTolerance.minWordSizeForTypos.oneTypo`: value: `1000` is too large to be deserialized, maximum value authorized is `255`",
  "code": "invalid_settings_typo_tolerance",
  "type": "invalid_request",
  "link": "https://docs.meilisearch.com/errors#invalid-settings-typo-tolerance"
}
消息

.typoTolerance.minWordSizeForTypos.oneTypo 位置存在无效值:值 1000 过大,无法反序列化,授权的最大值是 255

  • 我们得到了一个更易读的位置;.typoTolerance.minWordSizeForTypos.oneTypo。它告诉我们有问题的字段。
  • 这次我们还得到了一个不那么令人困惑且有帮助的消息;它明确告诉我们授权的最大值是 255
有效载荷的其余部分

由于deserr在过程中调用了我们的一个函数,我们能够使用自定义的错误代码 + 链接将用户重定向到特定于此功能和字段的文档。

deserr带来的更多可能性,这是serde所无法实现的

在多个字段上添加约束

在Meilisearch中,对 minWordSizeForTypos 这个字段还有另一个约束,即 twoTypo 字段 必须 大于 oneType 字段。

Serde没有提供任何功能来实现这一点。你可以为整个子对象 minWordSizeForTypos 编写自己的 Deserialize 实现,但这通常很困难,甚至无法让你自定义错误类型。因此,这就是你以后需要手动检查的事情。这是有风险的,可能会导致大多数反序列化错误消息与你的错误消息之间不一致。

使用deserr,我们提供了属性,允许你在结构反序列化后进行验证。

当字段缺失时

当字段缺失时,你可以提供自己的函数。

pub fn missing_field<E: DeserializeError>(field: &str, location: ValuePointerRef) -> E {
    todo!()
}

在Meilisearch中,我们使用此函数指定自定义错误代码,但保留默认的错误消息,该消息相当准确。

当遇到未知字段时

当字段缺失时,你可以提供自己的函数。

fn unknown_field<E: DeserializeError>(
    field: &str,
    accepted: &[&str],
    location: ValuePointerRef,
) -> E {
    todo!()
}

以下是我们有一些想法或希望在Meilisearch中实现的想法:

  • 对于可以 PUT 某些字段但无法 PATCH 所有字段的资源,我们可以抛出一个特殊的 不可变字段 x 错误,而不是 未知字段 x 错误。
  • 检测用户是否使用了替代字段的字段名;例如,我们使用 q 来进行 query,而一些Meilisearch替代方案使用 query。我们可以通过将字段更正为Meilisearch中的正确名称来帮助用户,提供一个 你是否想? 消息。
  • 通过计算用户输入和可接受输入之间的 levenshtein距离 来猜测用户试图说什么,并提供一个 你是否想? 消息来尝试纠正拼写错误。
当遇到多个错误时

在尝试将值反序列化到你的类型时,Deserr允许你使用其 MergeWithError 特性累积多个错误。这是一种减少用户修复无效负载所需交互次数的好方法,从而提高用户体验。


deserr的主要部分包括

  1. Deserr<E> 是主要的反序列化特质,与 Serde 不同,它非常容易手动反序列化此特质,请参阅我们的示例目录中的 implements_deserr_manually.rs 文件。
  2. IntoValueValue 描述了解析的序列化数据必须具有的形状
  3. DeserializeError 是所有反序列化错误都必须遵守的特质
  4. MergeWithError<E> 描述了如何将多个错误组合在一起。它允许 Deserr 一次返回多个反序列化错误。
  5. ValuePointerRefValuePointer 指向值内部的特定位置。它们用于定位错误的起源。
  6. deserialize<Ret, Val, E> 是用于反序列化值的主体函数。
    • Ret 是返回值或您想要反序列化的结构。
    • Val 是您想要从其反序列化的值类型。目前,此 crate 中只提供了一个 serde_json::Value 的实现,但您可以添加自己的!请随时查看我们的 serde_json 模块。
    • E 是在反序列化过程中发生错误时应使用的错误类型。
  7. Deserr derive 程序宏

示例

使用自定义错误实现自定义类型的反序列化

在下面的示例中,我们将反序列化包含许多字段的结构,并使用一个自定义错误类型,该类型会累积在反序列化结构时遇到的全部错误。

use deserr::{deserialize, DeserializeError, Deserr, ErrorKind, errors::JsonError, Value, ValueKind, IntoValue, take_cf_content, MergeWithError, ValuePointerRef, ValuePointer};
use serde_json::json;
use std::str::FromStr;
use std::ops::ControlFlow;
use std::fmt;
use std::convert::Infallible;

/// This is our custom error type. It'll accumulate multiple `JsonError`.
#[derive(Debug)]
struct MyError(Vec<JsonError>);

impl DeserializeError for MyError {
    /// Create a new error with the custom message.
    ///
    /// Return `ControlFlow::Continue` to continue deserializing even though an error was encountered.
    /// We could return `ControlFlow::Break` as well to stop right here.
    fn error<V: IntoValue>(self_: Option<Self>, error: ErrorKind<V>, location: ValuePointerRef) -> ControlFlow<Self, Self> {
        /// The `take_cf_content` return the inner error in a `ControlFlow<E, E>`.
        let error = take_cf_content(JsonError::error(None, error, location));

        let errors = if let Some(MyError(mut errors)) = self_ {
            errors.push(error);
            errors
        } else {
            vec![error]
        };
        ControlFlow::Continue(MyError(errors))
    }
}

/// We have to implements `MergeWithError` between our error type _aaand_ our error type.
impl MergeWithError<MyError> for MyError {
    fn merge(self_: Option<Self>, mut other: MyError, _merge_location: ValuePointerRef) -> ControlFlow<Self, Self> {
        if let Some(MyError(mut errors)) = self_ {
                other.0.append(&mut errors);
        }
        ControlFlow::Continue(other)
    }
}

#[derive(Debug, Deserr, PartialEq, Eq)]
#[deserr(deny_unknown_fields)]
struct Search {
    #[deserr(default = String::new())]
    query: String,
    #[deserr(try_from(&String) = FromStr::from_str -> IndexUidError)]
    index: IndexUid,
    #[deserr(from(String) = From::from)]
    field: Wildcard,
    #[deserr(default)]
    filter: Option<serde_json::Value>,
    // Even though this field is an `Option` it IS mandatory.
    limit: Option<usize>,
    #[deserr(default)]
    offset: usize,
}

/// An `IndexUid` can only be composed of ascii characters.
#[derive(Debug, PartialEq, Eq)]
struct IndexUid(String);
/// If we encounter a non-ascii character this is the error type we're going to throw.
struct IndexUidError(char);

impl FromStr for IndexUid {
    type Err = IndexUidError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(c) = s.chars().find(|c| !c.is_ascii()) {
            Err(IndexUidError(c))
        } else {
            Ok(Self(s.to_string()))
        }
    }
}

impl fmt::Display for IndexUidError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Encountered invalid character: `{}`, only ascii characters are accepted in the index",
            self.0
        )
    }
}

/// We need to define how the `IndexUidError` error is going to be merged with our
/// custom error type.
impl MergeWithError<IndexUidError> for MyError {
    fn merge(self_: Option<Self>, other: IndexUidError, merge_location: ValuePointerRef) -> ControlFlow<Self, Self> {
            // To be consistent with the other error and automatically get the position of the error we re-use the `JsonError`
            // type and simply define ourself as an `Unexpected` error.
        let error = take_cf_content(JsonError::error::<Infallible>(None, ErrorKind::Unexpected { msg: other.to_string() }, merge_location));
        let errors = if let Some(MyError(mut errors)) = self_ {
            errors.push(error);
            errors
        } else {
            vec![error]
        };
        ControlFlow::Continue(MyError(errors))
    }
}

/// A `Wildcard` can either contains a normal value or be a unit wildcard.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(from(String) = From::from)]
enum Wildcard {
    Wildcard,
    Value(String),
}

impl From<String> for Wildcard {
    fn from(s: String) -> Self {
        if s == "*" {
            Wildcard::Wildcard
        } else {
            Wildcard::Value(s)
        }
    }
}

// Here is an example of a typical payload we could deserialize:
let data = deserialize::<Search, _, MyError>(
    json!({ "index": "mieli", "field": "doggo", "filter": ["id = 1", ["catto = jorts"]], "limit": null }),
).unwrap();
assert_eq!(data, Search {
    query: String::new(),
    index: IndexUid(String::from("mieli")),
    field: Wildcard::Value(String::from("doggo")),
    filter: Some(json!(["id = 1", ["catto = jorts"]])),
    limit: None,
    offset: 0,
});

// And here is what happens when everything goes wrong at the same time:
let error = deserialize::<Search, _, MyError>(
    json!({ "query": 12, "index": "mieli 🍯", "field": true, "offset": "🔢"  }),
).unwrap_err();
// We're going to stringify all the error so it's easier to read
assert_eq!(error.0.into_iter().map(|error| error.to_string()).collect::<Vec<String>>().join("\n"),
"\
Invalid value type at `.query`: expected a string, but found a positive integer: `12`
Invalid value type at `.offset`: expected a positive integer, but found a string: `\"🔢\"`
Invalid value at `.index`: Encountered invalid character: `🍯`, only ascii characters are accepted in the index
Invalid value type at `.field`: expected a string, but found a boolean: `true`
Missing field `limit`\
");

支持的功能

rename_all

根据给定的案例约定重命名结构体中的所有字段。可能的值有 lowercasecamelCase。如果您需要更多的案例约定,请提出问题;添加更多是微不足道的。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(rename_all = camelCase)]
struct Search {
    query: String,
    attributes_to_retrieve: Vec<String>,
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "attributesToRetrieve": ["age", "name"] }),
)
.unwrap();
assert_eq!(data, Search {
    query: String::from("doggo"),
    attributes_to_retrieve: vec![String::from("age"), String::from("name")],
});

deny_unknown_fields

在遇到未知字段时抛出错误。当此属性不存在时,默认情况下会忽略未知字段。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug)]
#[deserr(deny_unknown_fields)]
struct Search {
    query: String,
}

let err = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "doggo": "bork" }),
)
.unwrap_err();

assert_eq!(err.to_string(), "Unknown field `doggo`: expected one of `query`");

还可以提供自定义函数来处理错误。

use deserr::{Deserr, deserialize, ErrorKind, DeserializeError, ValuePointerRef, take_cf_content, errors::JsonError};
use std::convert::Infallible;
use serde_json::json;

#[derive(Deserr, Debug)]
#[deserr(deny_unknown_fields = unknown_fields_search)]
struct Search {
    query: String,
}

fn unknown_fields_search<E: DeserializeError>(
    field: &str,
    accepted: &[&str],
    location: ValuePointerRef,
) -> E {
    // `E::error` returns a `ControlFlow<E, E>`, which returns the error and indicates
    // whether we should keep accumulating errors or not. However, here we simply
    // want to retrieve the error's value. This is what `take_cf_content` does.
    match field {
        "doggo" => take_cf_content(E::error::<Infallible>(
                None,
                ErrorKind::Unexpected {
                    msg: String::from("can I pet the doggo? uwu")
                },
                location,
            )),
        _ => take_cf_content(E::error::<Infallible>(
            None,
            deserr::ErrorKind::UnknownKey { key: field, accepted },
            location,
        )),
    }
}

let err = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "doggo": "bork" }),
)
.unwrap_err();

assert_eq!(err.to_string(), "Invalid value: can I pet the doggo? uwu");

let err = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "catto": "jorts" }),
)
.unwrap_err();

assert_eq!(err.to_string(), "Unknown field `catto`: expected one of `query`");

tag

对枚举进行外部标记。Deserr 目前不支持对枚举进行内部标记,这意味着如果您正在反序列化枚举,则始终需要使用此属性。对于完整的单元枚举,deserr 可以从字符串反序列化它们的值。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    query: Query,
}

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(tag = "type")]
enum Query {
    Single {
        search: String,
    },
    Multi {
        searches: Vec<String>,
    }
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": { "type": "Single", "search": "bork" } }),
)
.unwrap();
assert_eq!(data, Search {
    query: Query::Single {
        search: String::from("bork"),
    },
});

from

从函数而不是从 Value 反序列化类型。您需要提供以下信息;

  1. 函数的输入类型(这里 &String
  2. 函数的路径(这里,我们简单地使用 std 的 FromStr 实现)

Deserr 将首先尝试使用其 Deserr<E> 实现来反序列化给定的类型。这意味着 from 的输入类型可能很复杂。然后 Deserr 将调用您的函数。

如果您的函数可能失败,请参阅 try_from

它可以用作容器属性
use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(from(String) = From::from)]
enum Wildcard {
    Wildcard,
    Value(String),
}

impl From<String> for Wildcard {
    fn from(s: String) -> Self {
        if s == "*" {
            Wildcard::Wildcard
        } else {
            Wildcard::Value(s)
        }
    }
}

let data = deserialize::<Wildcard, _, JsonError>(
    json!("doggo"),
)
.unwrap();
assert_eq!(data, Wildcard::Value(String::from("doggo")));

let data = deserialize::<Wildcard, _, JsonError>(
    json!("*"),
)
.unwrap();
assert_eq!(data, Wildcard::Wildcard);
或字段属性
use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(from(String) = From::from)]
enum Wildcard {
    Wildcard,
    Value(String),
}

impl From<String> for Wildcard {
    fn from(s: String) -> Self {
        if s == "*" {
            Wildcard::Wildcard
        } else {
            Wildcard::Value(s)
        }
    }
}

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    query: String,
    #[deserr(from(String) = From::from)]
    field: Wildcard,
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "field": "catto" }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), field: Wildcard::Value(String::from("catto")) });

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "field": "*" }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), field: Wildcard::Wildcard });

try_from

尝试从函数而不是从 Value 反序列化类型。您需要提供以下信息;

  1. 函数的输入类型(这里 &String
  2. 函数的路径(这里,我们简单地使用 std 的 FromStr 实现)
  3. 此函数可能返回的错误类型(这里 Infallible

deserr 首先会尝试使用其 Deserr<E> 实现来反序列化给定的类型。这意味着 try_from 的输入类型可能很复杂。然后 deserr 会调用您的函数,并将指定的错误累积到调用者的错误类型中。

它可以用作容器属性
use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;
use std::str::FromStr;
use std::fmt;

// Notice how the `try_from` allows us to leverage the deserr limitation on tuple struct.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(try_from(&String) = FromStr::from_str -> AsciiStringError)]
struct AsciiString(String);

#[derive(Debug)]
struct AsciiStringError(char);

impl fmt::Display for AsciiStringError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "Encountered invalid character: `{}`, only ascii characters are accepted",
            self.0
        )
    }
}
impl std::error::Error for AsciiStringError {}

impl FromStr for AsciiString {
    type Err = AsciiStringError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if let Some(c) = s.chars().find(|c| !c.is_ascii()) {
            Err(AsciiStringError(c))
        } else {
            Ok(Self(s.to_string()))
        }
    }
}

let data = deserialize::<AsciiString, _, JsonError>(
    json!("doggo"),
)
.unwrap();
assert_eq!(data, AsciiString(String::from("doggo")));

let error = deserialize::<AsciiString, _, JsonError>(
    json!("👉👈"),
)
.unwrap_err();
assert_eq!(error.to_string(), "Invalid value: Encountered invalid character: `👉`, only ascii characters are accepted");
或字段属性
use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;
use std::convert::Infallible;
use std::str::FromStr;
use std::num::ParseIntError;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    query: String,
    #[deserr(try_from(&String) = FromStr::from_str -> ParseIntError)]
    limit: usize,

}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "limit": "12" }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), limit: 12 });

let error = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "limit": 12 }),
)
.unwrap_err();
assert_eq!(error.to_string(), "Invalid value type at `.limit`: expected a string, but found a positive integer: `12`");

validate

在结构体反序列化后对其进行验证。这在您的验证逻辑需要考虑多个字段时非常有用。

use deserr::{Deserr, DeserializeError, ErrorKind, ValuePointerRef, deserialize, errors::JsonError};
use serde_json::json;
use std::convert::Infallible;

// `__Deserr_E` represents the Error returned by the generated `Deserr` implementation.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(validate = validate_range -> __Deserr_E)]
struct Range {
    min: u8,
    max: u8,
}

fn validate_range<E: DeserializeError>(
    range: Range,
    location: ValuePointerRef,
) -> Result<Range, E> {
    if range.min > range.max {
        Err(deserr::take_cf_content(E::error::<Infallible>(
            None,
            ErrorKind::Unexpected {
                msg: format!(
                    "`max` (`{}`) should be greater than `min` (`{}`)",
                    range.max, range.min
                ),
            },
            location,
        )))
    } else {
        Ok(range)
    }
}

let data = deserialize::<Range, _, JsonError>(
    json!({ "min": 2, "max": 4 }),
)
.unwrap();
assert_eq!(data, Range { min: 2, max: 4 });

let error = deserialize::<Range, _, JsonError>(
    json!({ "min": 4, "max": 2 }),
)
.unwrap_err();
assert_eq!(error.to_string(), "Invalid value: `max` (`2`) should be greater than `min` (`4`)");

default

允许您为字段指定默认值。

请注意,与 serde 不同,默认情况下,Option 不自动使用此属性。在这里,您需要显式定义您的类型是否可以获取默认值。这使得它更不容易出错,并更容易将可选字段强制为必填。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    #[deserr(default)]
    query: Option<String>,
    #[deserr(default = 20)]
    limit: usize,
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "limit": 4 }),
)
.unwrap();
assert_eq!(data, Search { query: Some(String::from("doggo")), limit: 4 });

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo" }),
)
.unwrap();
assert_eq!(data, Search { query: Some(String::from("doggo")), limit: 20 });

skip

允许您跳过字段的反序列化。它不会出现在由 deny_unknown_fields 生成的字段列表中,也不会出现在 ErrorKind 类型的 UnknownKey 变体中。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    query: String,
    // A field can be skipped if it implements `Default` or if the `default` attribute is specified.
    #[deserr(skip)]
    hidden: usize,
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo" }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), hidden: 0 });

// if you try to specify the field, it is ignored
let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "hidden": 2 }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), hidden: 0 });

// Here, we're going to see how skip interacts with `deny_unknown_fields`

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(deny_unknown_fields)]
struct Search2 {
    query: String,
    // A field can be skipped if it implements `Default`.
    #[deserr(skip)]
    hidden: usize,
}

let error = deserialize::<Search2, _, JsonError>(
    json!({ "query": "doggo", "hidden": 1 }),
)
.unwrap_err();
// NOTE: `hidden` isn't in the list of expected fields + `hidden` is effectively considered as a non-existing field.
assert_eq!(error.to_string(), "Unknown field `hidden`: expected one of `query`");

map

在反序列化后映射字段。

use deserr::{Deserr, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    query: String,
    #[deserr(map = add_one)]
    limit: usize,
}

fn add_one(n: usize) -> usize {
    n.saturating_add(1)
}

let data = deserialize::<Search, _, JsonError>(
    json!({ "query": "doggo", "limit": 0 }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), limit: 1 });

// Let's see how `map` interacts with the `default` attributes.
#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search2 {
    query: String,
    #[deserr(default, map = add_one)]
    limit: usize,
}

let data = deserialize::<Search2, _, JsonError>(
    json!({ "query": "doggo" }),
)
.unwrap();
// As we can see, the `map` attribute is applied AFTER the `default`.
assert_eq!(data, Search2 { query: String::from("doggo"), limit: 1 });

missing_field_error

如果您想为缺失的特定字段自定义错误信息,这将提供机会。

use deserr::{Deserr, DeserializeError, ValuePointerRef, ErrorKind, deserialize, errors::JsonError};
use serde_json::json;
use std::convert::Infallible;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search {
    #[deserr(missing_field_error = missing_query_field)]
    query: String,
    limit: usize,
}

fn missing_query_field<E: DeserializeError>(_field_name: &str, location: ValuePointerRef) -> E {
    deserr::take_cf_content(E::error::<Infallible>(
        None,
        ErrorKind::Unexpected {
            msg: String::from("I really need the query field, please give it to me uwu"),
        },
        location,
    ))
}

let error = deserialize::<Search, _, JsonError>(
    json!({ "limit": 0 }),
)
.unwrap_err();
assert_eq!(error.to_string(), "Invalid value: I really need the query field, please give it to me uwu");

error

自定义反序列化此结构时可以返回的错误类型,而不是保持其通用性。

use deserr::{Deserr, DeserializeError, ValuePointerRef, ErrorKind, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(error = JsonError)]
struct Search {
    query: String,
    limit: usize,
}

// As we can see, rust is able to infer the error type.
let data = deserialize::<Search, _, _>(
    json!({ "query": "doggo", "limit": 1 }),
)
.unwrap();
assert_eq!(data, Search { query: String::from("doggo"), limit: 1 });

它也可以用作字段属性;

use deserr::{Deserr, DeserializeError, ValuePointerRef, ErrorKind, deserialize, errors::JsonError};
use serde_json::json;

// Since the error returned by the `Search` structure needs to implements `MergeWithError<JsonError>`
// we also need to specify the `error` attribute as a `JsonError`. But as you will see later there are
// other solutions.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(error = JsonError)]
struct Search<A> {
    #[deserr(error = JsonError)]
    query: A,
    limit: usize,
}

where_predicate

允许您将 where 子句添加到 deserr 将生成的 Deserr 实现中。

use deserr::{Deserr, DeserializeError, MergeWithError, deserialize, errors::JsonError};
use serde_json::json;

// Here we can constraint the generic `__Deserr_E` type used by deserr to implements `MergeWithError`.
// Now instead of constraining the final error type it stays generic if it's able to accumulate with
// with a `JsonError`.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(where_predicate = __Deserr_E: MergeWithError<JsonError>, where_predicate = A: Deserr<JsonError>)]
struct Search<A> {
    #[deserr(error = JsonError)]
    query: A,
    limit: usize,
}

needs_predicate

自动为具有此属性的每个字段添加 where_predicate = FieldType: Deserr<ErrType>

use deserr::{Deserr, DeserializeError, MergeWithError, deserialize, errors::JsonError};
use serde_json::json;

#[derive(Deserr, Debug, PartialEq, Eq)]
struct Search<A> {
    #[deserr(needs_predicate)]
    query: A,
    limit: usize,
}

它与以下内容严格等价

use deserr::{Deserr, DeserializeError, MergeWithError, deserialize, errors::JsonError};
use serde_json::json;

// `__Deserr_E` represents the Error returned by the generated `Deserr` implementation.
#[derive(Deserr, Debug, PartialEq, Eq)]
#[deserr(where_predicate = A: Deserr<__Deserr_E>)]
struct Search<A> {
    query: A,
    limit: usize,
}

与 serde 的比较

由于 deserr 需要先反序列化负载到一个通用的 Value,这会在创建您的结构之前分配大量内存,所以它比 serde 慢得多。

例如,在 Meilisearch 的搜索路由中,在有效负载的情况下,我们观察到 400% 的减速(慢 4 倍)。这使得我们的搜索请求的反序列化时间从 500ns 降低到 2µs。这对于大多数用例来说已经足够快,但如果大部分时间都花在反序列化上,可能会成为问题。

数据结构支持

datastructure serde deserr note
Struct yes yes
Tuple struct yes no
Untagged Enum yes no
Untagged unit Enum yes yes
Tagged Enum yes yes

容器属性

features serde deserr note
rename yes no
rename_all yes yes
deny_unknown_fields yes yes 使用 deserr,当遇到未知字段时,您可以调用自定义函数
tag yes yes
tag+content yes no
untagged yes no 仅支持单元枚举
bound yes no 可以用 where_predicate 模拟
default yes no
remote yes no
transparent yes no
from yes yes
try_from yes yes
into yes no
crate yes no
validate no yes 允许您在结构体反序列化后验证其内容
error no yes 指定在反序列化此结构时应使用的错误类型
where_predicate no yes 允许您向生成的 Deserr 实现中添加 where 子句

字段属性

features serde deserr note
rename yes no
alias yes no
default yes yes
flatten yes no serde 不支持扁平化 + 拒绝未知字段
skip yes yes
deserialize_with yes no 但它可以通过 fromtry_from 模拟
with yes no
borrow yes no deserr 不支持具有引用的类型
bound yes no
map no yes 允许您在反序列化后映射值
from no yes 从这个不可靠的函数反序列化此字段
try_from no yes 从这个可能出错的函数反序列化此字段
missing_field_error no yes 允许您在字段缺失时返回自定义错误
error no yes 指定在反序列化此字段时应使用的错误类型

功能标志

serde-json

导入 serde_json 并提供;

  • deserr::IntoValue 实现了 serde_json::Value,使得这两个crate可以方便地一起使用。
  • JsonError 类型提供了一个默认实现,提供了尽可能好的泛型错误信息。

serde-cs

导入 serde-cs 并提供;

  • Deserr 实现了 serde_cs::CS<R>

actix-web

导入 actix-webfutures 并提供;

  • 如果使用 serde-json 功能,则实现了一个 json actix-web 提取器。
  • 如果使用 serde-json 功能,则实现了 ResponseErrorJsonError 类型的实现。

常见问题解答

但是为什么?

在 Meilisearch,我们希望在失败反序列化特定字段时自定义返回的错误代码。一些错误信息也非常不明确,无法编辑。

维护方面如何?

在 Meilisearch,我们已经在生产中使用 deserr,因此它得到了良好的维护。

在哪里可以看到此crate的使用示例?

目前,您可以在本仓库的 examples 目录中阅读我们的示例。您还可以查看我们的集成测试;每个属性都有一个简单易读的测试。

当然,您还可以阅读 Meilisearch 的代码,其中在所有路由上使用了 deserr。

我的问题没有列出来

请,如果您认为这个库中存在错误或想添加新功能,请打开一个问题或讨论。如果您想更直接地与我们聊天,您可以在 discord(https://discord.com/invite/meilisearch)上加入我们。

许可证

根据您的选择,受 Apache License, Version 2.0MIT 许可证 的许可。
除非您明确表示否则,根据 Apache-2.0 许可证的定义,您有意提交的任何贡献,都将按照上述方式双重许可,不附加任何额外条款或条件。

依赖项

~1–12MB
~141K SLoC