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 编码
3,864 每月下载量
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的主要部分包括
Deserr<E>
是主要的反序列化特质,与 Serde 不同,它非常容易手动反序列化此特质,请参阅我们的示例目录中的implements_deserr_manually.rs
文件。IntoValue
和Value
描述了解析的序列化数据必须具有的形状DeserializeError
是所有反序列化错误都必须遵守的特质MergeWithError<E>
描述了如何将多个错误组合在一起。它允许 Deserr 一次返回多个反序列化错误。ValuePointerRef
和ValuePointer
指向值内部的特定位置。它们用于定位错误的起源。deserialize<Ret, Val, E>
是用于反序列化值的主体函数。Ret
是返回值或您想要反序列化的结构。Val
是您想要从其反序列化的值类型。目前,此 crate 中只提供了一个serde_json::Value
的实现,但您可以添加自己的!请随时查看我们的serde_json
模块。E
是在反序列化过程中发生错误时应使用的错误类型。
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
根据给定的案例约定重命名结构体中的所有字段。可能的值有 lowercase
、camelCase
。如果您需要更多的案例约定,请提出问题;添加更多是微不足道的。
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
反序列化类型。您需要提供以下信息;
- 函数的输入类型(这里
&String
) - 函数的路径(这里,我们简单地使用 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
反序列化类型。您需要提供以下信息;
- 函数的输入类型(这里
&String
) - 函数的路径(这里,我们简单地使用 std 的
FromStr
实现) - 此函数可能返回的错误类型(这里
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 | 但它可以通过 from 和 try_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
- 如果使用
serde-json
功能,则实现了一个 json actix-web 提取器。 - 如果使用
serde-json
功能,则实现了ResponseError
对JsonError
类型的实现。
常见问题解答
但是为什么?
在 Meilisearch,我们希望在失败反序列化特定字段时自定义返回的错误代码。一些错误信息也非常不明确,无法编辑。
维护方面如何?
在 Meilisearch,我们已经在生产中使用 deserr,因此它得到了良好的维护。
在哪里可以看到此crate的使用示例?
目前,您可以在本仓库的 examples
目录中阅读我们的示例。您还可以查看我们的集成测试;每个属性都有一个简单易读的测试。
当然,您还可以阅读 Meilisearch 的代码,其中在所有路由上使用了 deserr。
我的问题没有列出来
请,如果您认为这个库中存在错误或想添加新功能,请打开一个问题或讨论。如果您想更直接地与我们聊天,您可以在 discord(https://discord.com/invite/meilisearch)上加入我们。
许可证
根据您的选择,受 Apache License, Version 2.0 或 MIT 许可证 的许可。除非您明确表示否则,根据 Apache-2.0 许可证的定义,您有意提交的任何贡献,都将按照上述方式双重许可,不附加任何额外条款或条件。
依赖项
~1–12MB
~141K SLoC