反序列化具有通用类型的可选结构字段会导致语义怪异
Deserializing an optional struct field with a generic type leads to semantic weirdness
我正在尝试将 JSON 反序列化为包含可选字段 authorization
的结构。 JSON 可能包含也可能不包含此字段。如果它确实包含该字段,我正在对 hyper::header::Authorization<hyper::header::Scheme>
进行自定义反序列化。因为 Authorization
需要 Scheme
的通用类型,所以我需要(正如我写的那样)在我的结构中包含通用类型。
所有测试都通过了,但是最后一个(de_json_none
,JSON没有授权字段的测试在语义上很奇怪,因为我必须以明确的 Scheme
类型(如图所示 Bearer
或 Basic
)为目标变量,尽管从 Rust 的角度来看是完全有效的,但两者都对该数据没有任何意义。
很清楚为什么会这样,但这是我不想要的,也是我不确定如何解决的问题。
我想编写一个 Rocket 处理程序,通过将数据类型设置为 Headers<Bearer>
,仅匹配包含类型 Authorization<Bearer>
的授权字段的数据。目前,它还会匹配根本没有该字段的数据。我也没有明确的方法来专门按类型调出缺少字段的数据。
我正在寻找有关如何重构此代码以反映 Headers
确实具有三个不同的、互斥的化身(Basic
、Bearer
和 None
).也许我应该在这里用枚举做点什么?
extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use hyper::header::{Authorization, Header, Raw, Scheme};
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize, PartialEq)]
struct Headers<S>
where
S: Scheme + 'static,
{
#[serde(deserialize_with = "auth_header", default = "no_auth")]
authorization: Option<Authorization<S>>,
#[serde(rename = ":path")]
path: String,
}
fn auth_header<'de, D, S>(deserializer: D) -> Result<Option<Authorization<S>>, D::Error>
where
D: Deserializer<'de>,
S: Scheme + 'static,
{
let s = String::deserialize(deserializer)?;
let auth = Authorization::parse_header(&Raw::from(s.into_bytes()));
auth.map(|a| Some(a)).map_err(serde::de::Error::custom)
}
fn no_auth<S>() -> Option<Authorization<S>>
where
S: Scheme + 'static,
{
None
}
#[cfg(test)]
mod test {
use hyper::header::{Basic, Bearer};
use serde_json;
use super::*;
#[test]
fn de_json_basic() {
let data = r#"{
"authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(Authorization(Basic {
username: "Aladdin".to_owned(),
password: Some("open sesame".to_owned()),
})),
path: "/service/".to_owned(),
};
let h: Headers<Basic> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_bearer() {
let data = r#"{
"authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(Authorization(
Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
)),
path: "/service/".to_owned(),
};
let h: Headers<Bearer> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_none() {
let data = r#"{
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: None,
path: "/service/".to_owned(),
};
let h: Headers<Bearer> = serde_json::from_str(data).unwrap();
// this also works, though neither should ideally
// let h: Headers<Basic> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
}
。编译器需要知道为 either case:
的值分配多少 space
struct ReallyBig([u8; 1024]);
struct ReallySmall(u8);
fn main() {
let mut choice = None; // How much space to allocate?
}
在您的代码中,Authorization
的大小取决于为 S
选择的值。由于 Headers
包含一个 Option<Authorization<S>>
,因此 Headers
和 的大小可以取决于 S
.
的选择
即使你没有得到任何值,你也必须选择解析成某种特定的类型。也许您稍后会通过构建适当的值将其从 None
手动更改为 Some
— 如果没有分配足够的 space,那就麻烦了!
因此,我看不到您的解决方案如何运作。类型是静态的——如果解码 JSON 会导致 Authorization
或 Bearer
,您需要在编译时 知道 ,而这根本不是可能。
通常情况下,我建议您使用 Box<Scheme>
的动态调度。这在这里不起作用,因为 Scheme
不是对象安全的。
然后,我建议您实现自己的枚举包装 Basic
或 Box
并为此实现 Scheme
。这并不容易工作,因为 Scheme::scheme
必须 return 一个 单个 关键字,但您实际上支持两个关键字!
下一步是实现我们自己的 Header
:
extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use hyper::header::{Authorization, Header, Raw, Basic, Bearer};
use serde::{Deserialize, Deserializer};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
enum MyAuthorization {
Basic(Authorization<Basic>),
Bearer(Authorization<Bearer>),
}
impl Header for MyAuthorization {
fn header_name() -> &'static str {
// Should always be the same header name, right?
Authorization::<Basic>::header_name()
}
fn parse_header(raw: &Raw) -> hyper::error::Result<Self> {
Authorization::<Basic>::parse_header(raw)
.map(MyAuthorization::Basic)
.or_else(|_| {
Authorization::<Bearer>::parse_header(raw).map(MyAuthorization::Bearer)
})
}
fn fmt_header(&self, f: &mut hyper::header::Formatter) -> fmt::Result {
match *self {
MyAuthorization::Basic(ref a) => a.fmt_header(f),
MyAuthorization::Bearer(ref a) => a.fmt_header(f),
}
}
}
#[derive(Debug, Deserialize, PartialEq)]
struct Headers {
#[serde(deserialize_with = "auth_header", default)]
authorization: Option<MyAuthorization>,
#[serde(rename = ":path")]
path: String,
}
fn auth_header<'de, D>(deserializer: D) -> Result<Option<MyAuthorization>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let auth = MyAuthorization::parse_header(&Raw::from(s.into_bytes()));
auth.map(Some).map_err(serde::de::Error::custom)
}
#[cfg(test)]
mod test {
use hyper::header::{Basic, Bearer};
use serde_json;
use super::*;
#[test]
fn de_json_basic() {
let data = r#"{
"authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(MyAuthorization::Basic(Authorization(Basic {
username: "Aladdin".to_owned(),
password: Some("open sesame".to_owned()),
}))),
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_bearer() {
let data = r#"{
"authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(MyAuthorization::Bearer(Authorization(
Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
))),
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_none() {
let data = r#"{
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: None,
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
}
您可能希望与 Hyper 维护者核实,看看这是否是执行此类操作的预期方式。
我正在尝试将 JSON 反序列化为包含可选字段 authorization
的结构。 JSON 可能包含也可能不包含此字段。如果它确实包含该字段,我正在对 hyper::header::Authorization<hyper::header::Scheme>
进行自定义反序列化。因为 Authorization
需要 Scheme
的通用类型,所以我需要(正如我写的那样)在我的结构中包含通用类型。
所有测试都通过了,但是最后一个(de_json_none
,JSON没有授权字段的测试在语义上很奇怪,因为我必须以明确的 Scheme
类型(如图所示 Bearer
或 Basic
)为目标变量,尽管从 Rust 的角度来看是完全有效的,但两者都对该数据没有任何意义。
很清楚为什么会这样,但这是我不想要的,也是我不确定如何解决的问题。
我想编写一个 Rocket 处理程序,通过将数据类型设置为 Headers<Bearer>
,仅匹配包含类型 Authorization<Bearer>
的授权字段的数据。目前,它还会匹配根本没有该字段的数据。我也没有明确的方法来专门按类型调出缺少字段的数据。
我正在寻找有关如何重构此代码以反映 Headers
确实具有三个不同的、互斥的化身(Basic
、Bearer
和 None
).也许我应该在这里用枚举做点什么?
extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use hyper::header::{Authorization, Header, Raw, Scheme};
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize, PartialEq)]
struct Headers<S>
where
S: Scheme + 'static,
{
#[serde(deserialize_with = "auth_header", default = "no_auth")]
authorization: Option<Authorization<S>>,
#[serde(rename = ":path")]
path: String,
}
fn auth_header<'de, D, S>(deserializer: D) -> Result<Option<Authorization<S>>, D::Error>
where
D: Deserializer<'de>,
S: Scheme + 'static,
{
let s = String::deserialize(deserializer)?;
let auth = Authorization::parse_header(&Raw::from(s.into_bytes()));
auth.map(|a| Some(a)).map_err(serde::de::Error::custom)
}
fn no_auth<S>() -> Option<Authorization<S>>
where
S: Scheme + 'static,
{
None
}
#[cfg(test)]
mod test {
use hyper::header::{Basic, Bearer};
use serde_json;
use super::*;
#[test]
fn de_json_basic() {
let data = r#"{
"authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(Authorization(Basic {
username: "Aladdin".to_owned(),
password: Some("open sesame".to_owned()),
})),
path: "/service/".to_owned(),
};
let h: Headers<Basic> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_bearer() {
let data = r#"{
"authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(Authorization(
Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
)),
path: "/service/".to_owned(),
};
let h: Headers<Bearer> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_none() {
let data = r#"{
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: None,
path: "/service/".to_owned(),
};
let h: Headers<Bearer> = serde_json::from_str(data).unwrap();
// this also works, though neither should ideally
// let h: Headers<Basic> = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
}
struct ReallyBig([u8; 1024]);
struct ReallySmall(u8);
fn main() {
let mut choice = None; // How much space to allocate?
}
在您的代码中,Authorization
的大小取决于为 S
选择的值。由于 Headers
包含一个 Option<Authorization<S>>
,因此 Headers
和 的大小可以取决于 S
.
即使你没有得到任何值,你也必须选择解析成某种特定的类型。也许您稍后会通过构建适当的值将其从 None
手动更改为 Some
— 如果没有分配足够的 space,那就麻烦了!
因此,我看不到您的解决方案如何运作。类型是静态的——如果解码 JSON 会导致 Authorization
或 Bearer
,您需要在编译时 知道 ,而这根本不是可能。
通常情况下,我建议您使用 Box<Scheme>
的动态调度。这在这里不起作用,因为 Scheme
不是对象安全的。
然后,我建议您实现自己的枚举包装 Basic
或 Box
并为此实现 Scheme
。这并不容易工作,因为 Scheme::scheme
必须 return 一个 单个 关键字,但您实际上支持两个关键字!
下一步是实现我们自己的 Header
:
extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
use hyper::header::{Authorization, Header, Raw, Basic, Bearer};
use serde::{Deserialize, Deserializer};
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
enum MyAuthorization {
Basic(Authorization<Basic>),
Bearer(Authorization<Bearer>),
}
impl Header for MyAuthorization {
fn header_name() -> &'static str {
// Should always be the same header name, right?
Authorization::<Basic>::header_name()
}
fn parse_header(raw: &Raw) -> hyper::error::Result<Self> {
Authorization::<Basic>::parse_header(raw)
.map(MyAuthorization::Basic)
.or_else(|_| {
Authorization::<Bearer>::parse_header(raw).map(MyAuthorization::Bearer)
})
}
fn fmt_header(&self, f: &mut hyper::header::Formatter) -> fmt::Result {
match *self {
MyAuthorization::Basic(ref a) => a.fmt_header(f),
MyAuthorization::Bearer(ref a) => a.fmt_header(f),
}
}
}
#[derive(Debug, Deserialize, PartialEq)]
struct Headers {
#[serde(deserialize_with = "auth_header", default)]
authorization: Option<MyAuthorization>,
#[serde(rename = ":path")]
path: String,
}
fn auth_header<'de, D>(deserializer: D) -> Result<Option<MyAuthorization>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let auth = MyAuthorization::parse_header(&Raw::from(s.into_bytes()));
auth.map(Some).map_err(serde::de::Error::custom)
}
#[cfg(test)]
mod test {
use hyper::header::{Basic, Bearer};
use serde_json;
use super::*;
#[test]
fn de_json_basic() {
let data = r#"{
"authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(MyAuthorization::Basic(Authorization(Basic {
username: "Aladdin".to_owned(),
password: Some("open sesame".to_owned()),
}))),
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_bearer() {
let data = r#"{
"authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: Some(MyAuthorization::Bearer(Authorization(
Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
))),
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
#[test]
fn de_json_none() {
let data = r#"{
":path": "/service/",
":method": "GET"
}"#;
let message = Headers {
authorization: None,
path: "/service/".to_owned(),
};
let h: Headers = serde_json::from_str(data).unwrap();
assert_eq!(message, h);
}
}
您可能希望与 Hyper 维护者核实,看看这是否是执行此类操作的预期方式。