From a474fde85bd0cc3ca144c429dfb494696b40b055 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sun, 26 Mar 2023 19:18:04 -0700 Subject: [PATCH] Implement 'De(Serialize)' for 'Status'. Resolves #2366. --- core/http/src/status.rs | 67 ++++++++++++++++++++++++++++++++++++ core/lib/tests/http_serde.rs | 58 ++++++++++++++++++++++++++++++- 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/core/http/src/status.rs b/core/http/src/status.rs index 5104f04c..5518621d 100644 --- a/core/http/src/status.rs +++ b/core/http/src/status.rs @@ -92,6 +92,26 @@ impl StatusClass { /// ``` /// /// [`response::status`]: ../response/status/index.html +/// +/// # (De)serialization +/// +/// `Status` is both `Serialize` and `Deserialize`, represented as a `u16`. For +/// example, [`Status::Ok`] (de)serializes from/to `200`. Any integer in the +/// range `[100, 600)` is allowed to deserialize into a `Status`.` +/// +/// ```rust +/// # #[cfg(feature = "serde")] mod serde { +/// # use serde_ as serde; +/// use serde::{Serialize, Deserialize}; +/// use rocket::http::Status; +/// +/// #[derive(Deserialize, Serialize)] +/// # #[serde(crate = "serde_")] +/// struct Foo { +/// status: Status, +/// } +/// # } +/// ``` #[derive(Debug, Clone, Copy)] pub struct Status { /// The HTTP status code associated with this status. @@ -359,3 +379,50 @@ impl Ord for Status { self.code.cmp(&other.code) } } + +#[cfg(feature = "serde")] +mod serde { + use std::fmt; + use super::*; + + use serde_::ser::{Serialize, Serializer}; + use serde_::de::{Deserialize, Deserializer, Error, Visitor, Unexpected}; + + impl<'a> Serialize for Status { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_u16(self.code) + } + } + + struct DeVisitor; + + impl<'de> Visitor<'de> for DeVisitor { + type Value = Status; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(formatter, "HTTP status code integer in range [100, 600)") + } + + fn visit_i64(self, v: i64) -> Result { + if v < 100 || v >= 600 { + return Err(E::invalid_value(Unexpected::Signed(v), &self)); + } + + Ok(Status::new(v as u16)) + } + + fn visit_u64(self, v: u64) -> Result { + if v < 100 || v >= 600 { + return Err(E::invalid_value(Unexpected::Unsigned(v), &self)); + } + + Ok(Status::new(v as u16)) + } + } + + impl<'de> Deserialize<'de> for Status { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_u16(DeVisitor) + } + } +} diff --git a/core/lib/tests/http_serde.rs b/core/lib/tests/http_serde.rs index 7215ec83..3c5388d5 100644 --- a/core/lib/tests/http_serde.rs +++ b/core/lib/tests/http_serde.rs @@ -4,7 +4,7 @@ use pretty_assertions::assert_eq; use rocket::{Config, uri}; use rocket::http::uri::{Absolute, Asterisk, Authority, Origin, Reference}; -use rocket::http::Method; +use rocket::http::{Method, Status}; #[derive(PartialEq, Debug, Serialize, Deserialize)] struct UriContainer<'a> { @@ -31,6 +31,13 @@ struct MethodContainer { mpost: Method, } +#[derive(PartialEq, Debug, Serialize, Deserialize)] +struct StatusContainer { + a: Status, + b: Status, + c: Status, +} + #[test] fn uri_serde() { figment::Jail::expect_with(|jail| { @@ -148,3 +155,52 @@ fn method_serde() { Ok(()) }); } + +#[test] +fn status_serde() { + figment::Jail::expect_with(|jail| { + jail.create_file("Rocket.toml", r#" + [default] + a = 500 + b = 100 + c = 404 + "#)?; + + let statuses: StatusContainer = Config::figment().extract()?; + assert_eq!(statuses, StatusContainer { + a: Status::InternalServerError, + b: Status::Continue, + c: Status::NotFound + }); + + let tmp = Figment::from(Serialized::defaults(statuses)); + let statuses: StatusContainer = tmp.extract()?; + assert_eq!(statuses, StatusContainer { + a: Status::InternalServerError, + b: Status::Continue, + c: Status::NotFound + }); + + jail.create_file("Rocket.toml", r#" + [default] + a = 99 + b = 100 + c = 404 + "#)?; + + let statuses: Result = Config::figment().extract(); + assert!(statuses.is_err()); + + jail.create_file("Rocket.toml", r#" + [default] + a = 500 + b = 100 + c = 600 + "#)?; + + let statuses: Result = Config::figment().extract(); + assert!(statuses.is_err()); + + Ok(()) + }); +}