Skip to content

Commit

Permalink
Optionally accept string boolean fields (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
Justice4Joffrey committed Sep 6, 2022
1 parent d676faa commit 2f79ac2
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ ureq = ["oauth2/ureq"]
native-tls = ["oauth2/native-tls"]
rustls-tls = ["oauth2/rustls-tls"]
accept-rfc3339-timestamps = []
accept-string-booleans = []
nightly = []

[dependencies]
Expand Down
6 changes: 3 additions & 3 deletions src/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{Serialize, Serializer};

use crate::helpers::FlattenFilter;
use crate::types::helpers::{split_language_tag_key, timestamp_to_utc, utc_to_seconds};
use crate::types::{LocalizedClaim, Timestamp};
use crate::types::{Boolean, LocalizedClaim, Timestamp};
use crate::{
AddressCountry, AddressLocality, AddressPostalCode, AddressRegion, EndUserBirthday,
EndUserEmail, EndUserFamilyName, EndUserGivenName, EndUserMiddleName, EndUserName,
Expand Down Expand Up @@ -258,13 +258,13 @@ where
[LanguageTag(picture)]
[LanguageTag(website)]
[Option(email)]
[Option(email_verified)]
[Option(Boolean(email_verified))]
[Option(gender)]
[Option(birthday)]
[Option(zoneinfo)]
[Option(locale)]
[Option(phone_number)]
[Option(phone_number_verified)]
[Option(Boolean(phone_number_verified))]
[Option(address)]
[Option(DateTime(Seconds(updated_at)))]
}
Expand Down
28 changes: 28 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,11 +400,13 @@ macro_rules! new_url_type {
macro_rules! deserialize_fields {
(@field_str Option(Seconds($field:ident))) => { stringify![$field] };
(@field_str Option(DateTime(Seconds($field:ident)))) => { stringify![$field] };
(@field_str Option(Boolean($field:ident))) => { stringify![$field] };
(@field_str Option($field:ident)) => { stringify![$field] };
(@field_str LanguageTag($field:ident)) => { stringify![$field] };
(@field_str $field:ident) => { stringify![$field] };
(@let_none Option(Seconds($field:ident))) => { let mut $field = None; };
(@let_none Option(DateTime(Seconds($field:ident)))) => { let mut $field = None; };
(@let_none Option(Boolean($field:ident))) => { let mut $field = None; };
(@let_none Option($field:ident)) => { let mut $field = None; };
(@let_none LanguageTag($field:ident)) => { let mut $field = None; };
(@let_none $field:ident) => { let mut $field = None; };
Expand Down Expand Up @@ -451,6 +453,23 @@ macro_rules! deserialize_fields {
)
))).transpose()?;
};
(@case $map:ident $key:ident $language_tag_opt:ident
Option(Boolean($field:ident))) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
} else if let Some(language_tag) = $language_tag_opt {
return Err(
serde::de::Error::custom(
format!(
concat!("unexpected language tag `{}` for key `", stringify!($field), "`"),
language_tag.as_ref()
)
)
);
}
let boolean = $map.next_value::<Option<Boolean>>()?;
$field = boolean.map(|b| b.0);
};
(@case $map:ident $key:ident $language_tag_opt:ident Option($field:ident)) => {
if $field.is_some() {
return Err(serde::de::Error::duplicate_field(stringify!($field)));
Expand Down Expand Up @@ -514,6 +533,15 @@ macro_rules! deserialize_fields {
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [Option(Boolean($field_new:ident))] $([$($entry:tt)+])*
}) => {
deserialize_fields![
@struct_recurs [$($struct_type)+] {
$($name: $e,)* $field_new: $field_new => $([$($entry)+])*
}
]
};
(@struct_recurs [$($struct_type:tt)+] {
$($name:ident: $e:expr),* => [Option($field_new:ident)] $([$($entry:tt)+])*
}) => {
Expand Down
73 changes: 73 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,25 @@ impl Display for Timestamp {
}
}

///
/// Newtype around a bool, optionally supporting string values.
///
#[derive(Debug, Deserialize, Serialize)]
#[serde(transparent)]
pub(crate) struct Boolean(
#[cfg_attr(
feature = "accept-string-booleans",
serde(deserialize_with = "helpers::serde_string_bool::deserialize")
)]
pub bool,
);

impl Display for Boolean {
fn fmt(&self, f: &mut Formatter) -> Result<(), FormatterError> {
Display::fmt(&self.0, f)
}
}

new_url_type![
///
/// URL for retrieving redirect URIs that should receive identical pairwise subject identifiers.
Expand Down Expand Up @@ -1189,6 +1208,45 @@ pub(crate) mod helpers {
Timestamp::Seconds(utc.timestamp().into())
}

// Some providers return boolean values as strings. Provide support for
// parsing using stdlib.
#[cfg(feature = "accept-string-booleans")]
pub mod serde_string_bool {
use serde::{de, Deserializer};

use std::fmt;

pub fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
where
D: Deserializer<'de>,
{
struct BooleanLikeVisitor;

impl<'de> de::Visitor<'de> for BooleanLikeVisitor {
type Value = bool;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("A boolean-like value")
}

fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(v)
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
v.parse().map_err(E::custom)
}
}
deserializer.deserialize_any(BooleanLikeVisitor)
}
}

pub mod serde_utc_seconds {
use crate::types::Timestamp;
use chrono::{DateTime, Utc};
Expand Down Expand Up @@ -1281,6 +1339,7 @@ mod serde_base64url_byte_array {
#[cfg(test)]
mod tests {
use super::IssuerUrl;
use crate::types::Boolean;

#[test]
fn test_issuer_url_append() {
Expand Down Expand Up @@ -1339,4 +1398,18 @@ mod tests {
"\"http://example.com\"",
);
}

#[cfg(feature = "accept-string-booleans")]
#[test]
fn test_string_bool_parse() {
fn test_case(input: &str, expect: bool) {
let value: Boolean = serde_json::from_str(input).unwrap();
assert_eq!(value.0, expect);
}
test_case("true", true);
test_case("false", false);
test_case("\"true\"", true);
test_case("\"false\"", false);
assert!(serde_json::from_str::<Boolean>("\"maybe\"").is_err());
}
}

0 comments on commit 2f79ac2

Please sign in to comment.