mirror of https://github.com/rwf2/Rocket.git
Enforce using 'MsgPack<T>' to deserialize.
This commit enforces using 'MsgPack<T>', and not 'MsgPack<T, Foo>' or 'Compact<T>', to deserialize MsgPack-encoded data. It also simplifies the round-trip msgpack test and removes the dev-dependency on `rmp`.
This commit is contained in:
parent
0998b37aeb
commit
39ed4a4909
|
@ -140,4 +140,3 @@ version_check = "0.9.1"
|
||||||
tokio = { version = "1", features = ["macros", "io-std"] }
|
tokio = { version = "1", features = ["macros", "io-std"] }
|
||||||
figment = { version = "0.10.17", features = ["test"] }
|
figment = { version = "0.10.17", features = ["test"] }
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
rmp = "0.8"
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ impl<T, const COMPACT: bool> MsgPack<T, COMPACT> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, T: Deserialize<'r>, const COMPACT: bool> MsgPack<T, COMPACT> {
|
impl<'r, T: Deserialize<'r>> MsgPack<T> {
|
||||||
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
|
fn from_bytes(buf: &'r [u8]) -> Result<Self, Error> {
|
||||||
rmp_serde::from_slice(buf).map(MsgPack)
|
rmp_serde::from_slice(buf).map(MsgPack)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@ impl<'r, T: Deserialize<'r>, const COMPACT: bool> MsgPack<T, COMPACT> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::async_trait]
|
#[crate::async_trait]
|
||||||
impl<'r, T: Deserialize<'r>, const COMPACT: bool> FromData<'r> for MsgPack<T, COMPACT> {
|
impl<'r, T: Deserialize<'r>> FromData<'r> for MsgPack<T> {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r, Self> {
|
||||||
|
@ -233,9 +233,7 @@ impl<'r, T: Serialize, const COMPACT: bool> Responder<'r, 'static> for MsgPack<T
|
||||||
}
|
}
|
||||||
|
|
||||||
#[crate::async_trait]
|
#[crate::async_trait]
|
||||||
impl<'v, T, const COMPACT: bool> form::FromFormField<'v> for MsgPack<T, COMPACT>
|
impl<'v, T: Deserialize<'v> + Send> form::FromFormField<'v> for MsgPack<T> {
|
||||||
where T: Deserialize<'v> + Send
|
|
||||||
{
|
|
||||||
// TODO: To implement `from_value`, we need to the raw string so we can
|
// TODO: To implement `from_value`, we need to the raw string so we can
|
||||||
// decode it into bytes as opposed to a string as it won't be UTF-8.
|
// decode it into bytes as opposed to a string as it won't be UTF-8.
|
||||||
|
|
||||||
|
|
|
@ -1,113 +1,71 @@
|
||||||
#![cfg(feature = "msgpack")]
|
#![cfg(feature = "msgpack")]
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use rocket::serde::msgpack::{MsgPack, Compact};
|
use rocket::serde::msgpack::{self, MsgPack, Compact};
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
struct Person {
|
struct Person<'r> {
|
||||||
name: String,
|
name: &'r str,
|
||||||
age: u8,
|
age: u8,
|
||||||
gender: Gender,
|
gender: Gender,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
#[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
#[serde(tag = "gender")]
|
|
||||||
enum Gender {
|
enum Gender {
|
||||||
Male,
|
Male,
|
||||||
Female,
|
Female,
|
||||||
NonBinary,
|
NonBinary,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::post("/age_named", data = "<person>")]
|
#[rocket::post("/named", data = "<person>")]
|
||||||
fn named(person: MsgPack<Person>) -> MsgPack<Person> {
|
fn named(person: MsgPack<Person<'_>>) -> MsgPack<Person<'_>> {
|
||||||
let person = Person { age: person.age + 1, ..person.into_inner() };
|
person
|
||||||
MsgPack(person)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::post("/age_compact", data = "<person>")]
|
#[rocket::post("/compact", data = "<person>")]
|
||||||
fn compact(person: MsgPack<Person>) -> Compact<Person> {
|
fn compact(person: MsgPack<Person<'_>>) -> Compact<Person<'_>> {
|
||||||
let person = Person { age: person.age + 1, ..person.into_inner() };
|
MsgPack(person.into_inner())
|
||||||
MsgPack(person)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rocket() -> Rocket<Build> {
|
fn rocket() -> Rocket<Build> {
|
||||||
rocket::build()
|
rocket::build().mount("/", rocket::routes![named, compact])
|
||||||
.mount("/", rocket::routes![named, compact])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_string(buf: &mut rmp::decode::Bytes) -> String {
|
// The object we're going to roundtrip through the API.
|
||||||
let mut string_buf = vec![0; 32]; // Awful but we're just testing.
|
const OBJECT: Person<'static> = Person {
|
||||||
rmp::decode::read_str(buf, &mut string_buf).unwrap().to_string()
|
name: "Cal",
|
||||||
}
|
age: 17,
|
||||||
|
gender: Gender::NonBinary,
|
||||||
|
};
|
||||||
|
|
||||||
|
// [ "Cal", 17, "NonBinary" ]
|
||||||
|
const COMPACT_BYTES: &[u8] = &[
|
||||||
|
147, 163, 67, 97, 108, 17, 169, 78, 111, 110, 66, 105, 110, 97, 114, 121
|
||||||
|
];
|
||||||
|
|
||||||
|
// { "name": "Cal", "age": 17, "gender": "NonBinary" }
|
||||||
|
const NAMED_BYTES: &[u8] = &[
|
||||||
|
131, 164, 110, 97, 109, 101, 163, 67, 97, 108, 163, 97, 103, 101, 17, 166,
|
||||||
|
103, 101, 110, 100, 101, 114, 169, 78, 111, 110, 66, 105, 110, 97, 114, 121
|
||||||
|
];
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_named_roundtrip() {
|
fn check_roundtrip() {
|
||||||
let client = Client::debug(rocket()).unwrap();
|
let client = Client::debug(rocket()).unwrap();
|
||||||
let person = Person {
|
let inputs: &[(&'static str, Cow<'static, [u8]>)] = &[
|
||||||
name: "Cal".to_string(),
|
("objpack", msgpack::to_vec(&OBJECT).unwrap().into()),
|
||||||
age: 17,
|
("named bytes", NAMED_BYTES.into()),
|
||||||
gender: Gender::NonBinary,
|
("compact bytes", COMPACT_BYTES.into()),
|
||||||
};
|
];
|
||||||
let response = client
|
|
||||||
.post("/age_named")
|
|
||||||
.body(rmp_serde::to_vec_named(&person).unwrap())
|
|
||||||
.dispatch()
|
|
||||||
.into_bytes()
|
|
||||||
.unwrap();
|
|
||||||
let mut bytes = rmp::decode::Bytes::new(&response);
|
|
||||||
assert_eq!(rmp::decode::read_map_len(&mut bytes).unwrap(), 3);
|
|
||||||
assert_eq!(&read_string(&mut bytes), "name");
|
|
||||||
assert_eq!(&read_string(&mut bytes), "Cal");
|
|
||||||
assert_eq!(&read_string(&mut bytes), "age");
|
|
||||||
assert_eq!(rmp::decode::read_int::<u8, _>(&mut bytes).unwrap(), 18);
|
|
||||||
assert_eq!(&read_string(&mut bytes), "gender");
|
|
||||||
// Enums are complicated in serde. In this test, they're encoded like this:
|
|
||||||
// (JSON equivalent) `{ "gender": "NonBinary" }`, where that object is itself
|
|
||||||
// the value of the `gender` key in the outer object. `#[serde(flatten)]`
|
|
||||||
// on the `gender` key in the outer object fixes this, but it prevents `rmp`
|
|
||||||
// from using compact mode, which would break the test.
|
|
||||||
assert_eq!(rmp::decode::read_map_len(&mut bytes).unwrap(), 1);
|
|
||||||
assert_eq!(&read_string(&mut bytes), "gender");
|
|
||||||
assert_eq!(&read_string(&mut bytes), "NonBinary");
|
|
||||||
|
|
||||||
let response_from_compact = client
|
for (name, input) in inputs {
|
||||||
.post("/age_named")
|
let compact = client.post("/compact").body(input).dispatch();
|
||||||
.body(rmp_serde::to_vec(&person).unwrap())
|
assert_eq!(compact.into_bytes().unwrap(), COMPACT_BYTES, "{name} mismatch");
|
||||||
.dispatch()
|
|
||||||
.into_bytes()
|
let named = client.post("/named").body(input).dispatch();
|
||||||
.unwrap();
|
assert_eq!(named.into_bytes().unwrap(), NAMED_BYTES, "{name} mismatch");
|
||||||
assert_eq!(response, response_from_compact);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_compact_roundtrip() {
|
|
||||||
let client = Client::debug(rocket()).unwrap();
|
|
||||||
let person = Person {
|
|
||||||
name: "Maeve".to_string(),
|
|
||||||
age: 15,
|
|
||||||
gender: Gender::Female,
|
|
||||||
};
|
|
||||||
let response = client
|
|
||||||
.post("/age_compact")
|
|
||||||
.body(rmp_serde::to_vec(&person).unwrap())
|
|
||||||
.dispatch()
|
|
||||||
.into_bytes()
|
|
||||||
.unwrap();
|
|
||||||
let mut bytes = rmp::decode::Bytes::new(&response);
|
|
||||||
assert_eq!(rmp::decode::read_array_len(&mut bytes).unwrap(), 3);
|
|
||||||
assert_eq!(&read_string(&mut bytes), "Maeve");
|
|
||||||
assert_eq!(rmp::decode::read_int::<u8, _>(&mut bytes).unwrap(), 16);
|
|
||||||
// Equivalent to the named representation, gender here is encoded like this:
|
|
||||||
// `[ "Female" ]`.
|
|
||||||
assert_eq!(rmp::decode::read_array_len(&mut bytes).unwrap(), 1);
|
|
||||||
assert_eq!(&read_string(&mut bytes), "Female");
|
|
||||||
|
|
||||||
let response_from_named = client
|
|
||||||
.post("/age_compact")
|
|
||||||
.body(rmp_serde::to_vec_named(&person).unwrap())
|
|
||||||
.dispatch()
|
|
||||||
.into_bytes()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(response, response_from_named);
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue