use std::collections::HashMap;
use std::fmt::{Debug, Error as FormatterError, Formatter};
use std::hash::Hash;
use std::iter::FromIterator;
use std::marker::PhantomData;
use std::ops::Deref;
use base64;
use failure::Fail;
use futures::Future;
use http_::header::{HeaderValue, ACCEPT};
use http_::method::Method;
use http_::status::StatusCode;
use oauth2;
use oauth2::helpers::deserialize_space_delimited_vec;
use rand::{thread_rng, Rng};
use ring::constant_time;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
use url;
use url::Url;
use super::http::{check_content_type, MIME_TYPE_JSON};
use super::{
AccessToken, AuthorizationCode, DiscoveryError, HttpRequest, HttpResponse,
SignatureVerificationError,
};
#[derive(Clone, Debug, PartialEq)]
pub struct LocalizedClaim<T>(HashMap<LanguageTag, T>, Option<T>);
impl<T> LocalizedClaim<T> {
pub fn new() -> Self {
Self::default()
}
pub fn contains_key(&self, locale: Option<&LanguageTag>) -> bool {
if let Some(l) = locale {
self.0.contains_key(l)
} else {
self.1.is_some()
}
}
pub fn get(&self, locale: Option<&LanguageTag>) -> Option<&T> {
if let Some(l) = locale {
self.0.get(l)
} else {
self.1.as_ref()
}
}
pub fn iter(&self) -> impl Iterator<Item = (Option<&LanguageTag>, &T)> {
self.1
.iter()
.map(|value| (None, value))
.chain(self.0.iter().map(|(locale, value)| (Some(locale), value)))
}
pub fn insert(&mut self, locale: Option<LanguageTag>, value: T) -> Option<T> {
if let Some(l) = locale {
self.0.insert(l, value)
} else {
self.1.replace(value)
}
}
pub fn remove(&mut self, locale: Option<&LanguageTag>) -> Option<T> {
if let Some(l) = locale {
self.0.remove(l)
} else {
self.1.take()
}
}
}
impl<T> Default for LocalizedClaim<T> {
fn default() -> Self {
Self(HashMap::new(), None)
}
}
impl<T> From<T> for LocalizedClaim<T> {
fn from(default: T) -> Self {
Self(HashMap::new(), Some(default))
}
}
impl<T> FromIterator<(Option<LanguageTag>, T)> for LocalizedClaim<T> {
fn from_iter<I: IntoIterator<Item = (Option<LanguageTag>, T)>>(iter: I) -> Self {
let mut temp: HashMap<Option<LanguageTag>, T> = iter.into_iter().collect();
let default = temp.remove(&None);
Self(
temp.into_iter()
.filter_map(|(locale, value)| locale.map(|l| (l, value)))
.collect(),
default,
)
}
}
impl<T> IntoIterator for LocalizedClaim<T>
where
T: 'static,
{
type Item = <LocalizedClaimIterator<T> as Iterator>::Item;
type IntoIter = LocalizedClaimIterator<T>;
fn into_iter(self) -> Self::IntoIter {
LocalizedClaimIterator {
inner: Box::new(
self.1.into_iter().map(|value| (None, value)).chain(
self.0
.into_iter()
.map(|(locale, value)| (Some(locale), value)),
),
),
}
}
}
pub struct LocalizedClaimIterator<T> {
inner: Box<dyn Iterator<Item = (Option<LanguageTag>, T)>>,
}
impl<T> Iterator for LocalizedClaimIterator<T> {
type Item = (Option<LanguageTag>, T);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
pub trait ApplicationType: Debug + DeserializeOwned + Serialize + 'static {}
pub trait AuthDisplay: AsRef<str> + Debug + DeserializeOwned + Serialize + 'static {}
pub trait AuthPrompt: AsRef<str> + 'static {}
pub trait ClaimName: Debug + DeserializeOwned + Serialize + 'static {}
pub trait ClaimType: Debug + DeserializeOwned + Serialize + 'static {}
pub trait ClientAuthMethod: Debug + DeserializeOwned + Serialize + 'static {}
pub trait GrantType: Debug + DeserializeOwned + Serialize + 'static {}
#[derive(Clone, Debug, Fail, PartialEq)]
pub enum SigningError {
#[fail(display = "Crypto error")]
CryptoError,
#[fail(display = "Unsupported signature algorithm: {}", _0)]
UnsupportedAlg(String),
#[fail(display = "Other error: {}", _0)]
Other(String),
}
pub trait JsonWebKey<JS, JT, JU>: Clone + Debug + DeserializeOwned + Serialize + 'static
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
{
fn key_id(&self) -> Option<&JsonWebKeyId>;
fn key_type(&self) -> &JT;
fn key_use(&self) -> Option<&JU>;
fn new_symmetric(key: Vec<u8>) -> Self;
fn verify_signature(
&self,
signature_alg: &JS,
message: &[u8],
signature: &[u8],
) -> Result<(), SignatureVerificationError>;
}
pub trait PrivateSigningKey<JS, JT, JU, K>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
fn sign(&self, signature_alg: &JS, message: &[u8]) -> Result<Vec<u8>, SigningError>;
fn as_verification_key(&self) -> K;
}
pub trait JsonWebKeyType:
Clone + Debug + DeserializeOwned + PartialEq + Serialize + 'static
{
}
pub trait JsonWebKeyUse: Debug + DeserializeOwned + Serialize + 'static {
fn allows_signature(&self) -> bool;
fn allows_encryption(&self) -> bool;
}
pub trait JweContentEncryptionAlgorithm<JT>:
Clone + Debug + DeserializeOwned + Serialize + 'static
where
JT: JsonWebKeyType,
{
fn key_type(&self) -> Result<JT, String>;
}
pub trait JweKeyManagementAlgorithm: Debug + DeserializeOwned + Serialize + 'static {
}
pub trait JwsSigningAlgorithm<JT>:
Clone + Debug + DeserializeOwned + Eq + Hash + PartialEq + Serialize + 'static
where
JT: JsonWebKeyType,
{
fn key_type(&self) -> Option<JT>;
fn uses_shared_secret(&self) -> bool;
fn hash_bytes(&self, bytes: &[u8]) -> Result<Vec<u8>, String>;
fn rsa_sha_256() -> Self;
}
pub trait ResponseMode: Debug + DeserializeOwned + Serialize + 'static {}
pub trait ResponseType: AsRef<str> + Debug + DeserializeOwned + Serialize + 'static {
fn to_oauth2(&self) -> oauth2::ResponseType;
}
pub trait SubjectIdentifierType: Debug + DeserializeOwned + Serialize + 'static {}
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AuthenticationContextClass(String)
];
impl AsRef<str> for AuthenticationContextClass {
fn as_ref(&self) -> &str {
self
}
}
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AuthenticationMethodReference(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AccessTokenHash(String)
impl {
pub fn from_token<JS, JT>(
access_token: &AccessToken,
alg: &JS
) -> Result<Self, SigningError>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
alg.hash_bytes(access_token.secret().as_bytes())
.map(|hash| {
Self::new(
base64::encode_config(&hash[0..hash.len() / 2], base64::URL_SAFE_NO_PAD)
)
})
.map_err(SigningError::UnsupportedAlg)
}
}
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AddressCountry(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AddressLocality(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AddressPostalCode(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AddressRegion(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
Audience(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
AuthorizationCodeHash(String)
impl {
pub fn from_code<JS, JT>(
code: &AuthorizationCode,
alg: &JS
) -> Result<Self, SigningError>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
{
alg.hash_bytes(code.secret().as_bytes())
.map(|hash| {
Self::new(
base64::encode_config(&hash[0..hash.len() / 2], base64::URL_SAFE_NO_PAD)
)
})
.map_err(SigningError::UnsupportedAlg)
}
}
];
new_type![
#[derive(Deserialize, Eq, Hash, Serialize)]
pub(crate) Base64UrlEncodedBytes(
#[serde(with = "serde_base64url_byte_array")]
Vec<u8>
)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
ClientName(String)
];
new_url_type![
ClientConfigUrl
];
new_url_type![
ClientUrl
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
ClientContactEmail(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserBirthday(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserEmail(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserFamilyName(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserGivenName(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserMiddleName(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserName(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserNickname(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserPhoneNumber(String)
];
new_url_type![
EndUserPictureUrl
];
new_url_type![
EndUserProfileUrl
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserTimezone(String)
];
new_url_type![
EndUserWebsiteUrl
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
EndUserUsername(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
FormattedAddress(String)
];
new_url_type![
InitiateLoginUrl
];
new_url_type![
IssuerUrl
impl {
pub fn join(&self, suffix: &str) -> Result<Url, url::ParseError> {
if let Some('/') = self.1.chars().next_back() {
Url::parse(&(self.1.clone() + suffix))
} else {
Url::parse(&(self.1.clone() + "/" + suffix))
}
}
}
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
JsonWebKeyId(String)
];
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct JsonWebKeySet<JS, JT, JU, K>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
#[serde(bound = "K: JsonWebKey<JS, JT, JU>")]
keys: Vec<K>,
#[serde(skip)]
_phantom: PhantomData<(JS, JT, JU)>,
}
impl<JS, JT, JU, K> JsonWebKeySet<JS, JT, JU, K>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
pub fn new(keys: Vec<K>) -> Self {
Self {
keys,
_phantom: PhantomData,
}
}
pub fn fetch<HC, RE>(
url: &JsonWebKeySetUrl,
http_client: HC,
) -> Result<Self, DiscoveryError<RE>>
where
HC: FnOnce(HttpRequest) -> Result<HttpResponse, RE>,
RE: Fail,
{
http_client(Self::fetch_request(url))
.map_err(DiscoveryError::Request)
.and_then(Self::fetch_response)
}
pub fn fetch_async<F, HC, RE>(
url: &JsonWebKeySetUrl,
http_client: HC,
) -> impl Future<Item = Self, Error = DiscoveryError<RE>>
where
F: Future<Item = HttpResponse, Error = RE>,
HC: FnOnce(HttpRequest) -> F,
RE: Fail,
{
http_client(Self::fetch_request(url))
.map_err(DiscoveryError::Request)
.and_then(Self::fetch_response)
}
fn fetch_request(url: &JsonWebKeySetUrl) -> HttpRequest {
HttpRequest {
url: url.url().clone(),
method: Method::GET,
headers: vec![(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON))]
.into_iter()
.collect(),
body: Vec::new(),
}
}
fn fetch_response<RE>(http_response: HttpResponse) -> Result<Self, DiscoveryError<RE>>
where
RE: Fail,
{
if http_response.status_code != StatusCode::OK {
return Err(DiscoveryError::Response(
http_response.status_code,
http_response.body,
format!("HTTP status code {}", http_response.status_code),
));
}
check_content_type(&http_response.headers, MIME_TYPE_JSON).map_err(|err_msg| {
DiscoveryError::Response(
http_response.status_code,
http_response.body.clone(),
err_msg,
)
})?;
serde_json::from_slice(&http_response.body).map_err(DiscoveryError::Parse)
}
pub fn keys(&self) -> &Vec<K> {
&self.keys
}
}
impl<JS, JT, JU, K> Clone for JsonWebKeySet<JS, JT, JU, K>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
fn clone(&self) -> Self {
Self::new(self.keys.clone())
}
}
impl<JS, JT, JU, K> Default for JsonWebKeySet<JS, JT, JU, K>
where
JS: JwsSigningAlgorithm<JT>,
JT: JsonWebKeyType,
JU: JsonWebKeyUse,
K: JsonWebKey<JS, JT, JU>,
{
fn default() -> Self {
Self::new(Vec::new())
}
}
new_url_type![
JsonWebKeySetUrl
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
LanguageTag(String)
];
impl AsRef<str> for LanguageTag {
fn as_ref(&self) -> &str {
self
}
}
new_secret_type![
#[derive(Clone, Deserialize, Serialize)]
LoginHint(String)
];
new_url_type![
LogoUrl
];
new_secret_type![
#[derive(Clone, Deserialize, Serialize)]
Nonce(String)
impl {
pub fn new_random() -> Self {
Nonce::new_random_len(16)
}
pub fn new_random_len(num_bytes: u32) -> Self {
let random_bytes: Vec<u8> = (0..num_bytes).map(|_| thread_rng().gen::<u8>()).collect();
Nonce::new(base64::encode_config(&random_bytes, base64::URL_SAFE_NO_PAD))
}
}
];
impl PartialEq for Nonce {
fn eq(&self, other: &Self) -> bool {
constant_time::verify_slices_are_equal(self.secret().as_bytes(), other.secret().as_bytes())
.is_ok()
}
}
new_url_type![
OpPolicyUrl
];
new_url_type![
OpTosUrl
];
new_url_type![
PolicyUrl
];
new_secret_type![
#[derive(Clone, Deserialize, Serialize)]
RegistrationAccessToken(String)
];
new_url_type![
RegistrationUrl
];
new_url_type![
RequestUrl
];
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct ResponseTypes<RT: ResponseType>(
#[serde(
deserialize_with = "deserialize_space_delimited_vec",
serialize_with = "helpers::serialize_space_delimited_vec"
)]
Vec<RT>,
);
impl<RT: ResponseType> ResponseTypes<RT> {
pub fn new(s: Vec<RT>) -> Self {
ResponseTypes::<RT>(s)
}
}
impl<RT: ResponseType> Deref for ResponseTypes<RT> {
type Target = Vec<RT>;
fn deref(&self) -> &Vec<RT> {
&self.0
}
}
new_type![
#[derive(Deserialize, Serialize)]
pub(crate) Seconds(serde_json::Number)
];
new_url_type![
SectorIdentifierUrl
];
new_url_type![
ServiceDocUrl
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
StreetAddress(String)
];
new_type![
#[derive(Deserialize, Eq, Hash, Ord, PartialOrd, Serialize)]
SubjectIdentifier(String)
];
new_url_type![
ToSUrl
];
pub(crate) mod helpers {
use chrono::{DateTime, TimeZone, Utc};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer, Serializer};
use serde_json::{from_value, Value};
use super::{LanguageTag, Seconds};
pub fn deserialize_string_or_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
use serde::de::Error;
let value: Value = Deserialize::deserialize(deserializer)?;
match from_value::<Vec<T>>(value.clone()) {
Ok(val) => Ok(val),
Err(_) => {
let single_val: T = from_value(value).map_err(Error::custom)?;
Ok(vec![single_val])
}
}
}
pub fn deserialize_string_or_vec_opt<'de, T, D>(
deserializer: D,
) -> Result<Option<Vec<T>>, D::Error>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
use serde::de::Error;
let value: Value = Deserialize::deserialize(deserializer)?;
match from_value::<Option<Vec<T>>>(value.clone()) {
Ok(val) => Ok(val),
Err(_) => {
let single_val: T = from_value(value).map_err(Error::custom)?;
Ok(Some(vec![single_val]))
}
}
}
pub fn deserialize_option_or_none<'de, T, D>(deserializer: D) -> Result<Option<T>, D::Error>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
match from_value::<Option<T>>(value) {
Ok(val) => Ok(val),
Err(_) => Ok(None),
}
}
pub fn serialize_space_delimited_vec<T, S>(vec: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
T: AsRef<str>,
S: Serializer,
{
let space_delimited = vec
.iter()
.map(AsRef::<str>::as_ref)
.collect::<Vec<_>>()
.join(" ");
serializer.serialize_str(&space_delimited)
}
pub fn split_language_tag_key(key: &str) -> (&str, Option<LanguageTag>) {
let mut lang_tag_sep = key.splitn(2, '#');
let field_name = lang_tag_sep.next().unwrap();
let language_tag = lang_tag_sep
.next()
.filter(|language_tag| !language_tag.is_empty())
.map(|language_tag| LanguageTag::new(language_tag.to_string()));
(field_name, language_tag)
}
pub(crate) fn seconds_to_utc(seconds: &Seconds) -> Result<DateTime<Utc>, ()> {
let (secs, nsecs) = if seconds.is_i64() {
(seconds.as_i64().ok_or(())?, 0u32)
} else {
let secs_f64 = seconds.as_f64().ok_or(())?;
let secs = secs_f64.floor();
(
secs as i64,
((secs_f64 - secs) * 1_000_000_000.).floor() as u32,
)
};
Utc.timestamp_opt(secs, nsecs).single().ok_or(())
}
pub(crate) fn utc_to_seconds(utc: &DateTime<Utc>) -> Seconds {
let (secs, nsecs) = (utc.timestamp(), utc.timestamp_subsec_nanos());
if nsecs == 0 {
Seconds::new(secs.into())
} else {
Seconds::new(
serde_json::Number::from_f64(secs as f64 + (f64::from(nsecs)) / 1_000_000_000.)
.expect("Failed to convert timestamp to f64"),
)
}
}
pub mod serde_utc_seconds {
use super::super::Seconds;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>,
{
let seconds: Seconds = Deserialize::deserialize(deserializer)?;
super::seconds_to_utc(&seconds).map_err(|_| {
serde::de::Error::custom(format!(
"failed to parse `{}` as UTC datetime (in seconds)",
*seconds
))
})
}
pub fn serialize<S>(v: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
super::utc_to_seconds(v).serialize(serializer)
}
}
pub mod serde_utc_seconds_opt {
use super::super::Seconds;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
where
D: Deserializer<'de>,
{
let seconds: Option<Seconds> = Deserialize::deserialize(deserializer)?;
seconds
.map(|sec| {
super::seconds_to_utc(&sec).map_err(|_| {
serde::de::Error::custom(format!(
"failed to parse `{}` as UTC datetime (in seconds)",
*sec
))
})
})
.transpose()
}
pub fn serialize<S>(v: &Option<DateTime<Utc>>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
v.map(|sec| super::utc_to_seconds(&sec))
.serialize(serializer)
}
}
}
mod serde_base64url_byte_array {
use base64;
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serializer};
use serde_json::{from_value, Value};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
{
let value: Value = Deserialize::deserialize(deserializer)?;
let base64_encoded: String = from_value(value).map_err(D::Error::custom)?;
base64::decode_config(&base64_encoded, base64::URL_SAFE_NO_PAD).map_err(|err| {
D::Error::custom(format!(
"invalid base64url encoding `{}`: {:?}",
base64_encoded, err
))
})
}
pub fn serialize<S>(v: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let base64_encoded = base64::encode_config(v, base64::URL_SAFE_NO_PAD);
serializer.serialize_str(&base64_encoded)
}
}
#[cfg(test)]
mod tests {
use serde_json;
use super::super::IssuerUrl;
#[test]
fn test_issuer_url_append() {
assert_eq!(
"http://example.com/.well-known/openid-configuration",
IssuerUrl::new("http://example.com".to_string())
.unwrap()
.join(".well-known/openid-configuration")
.unwrap()
.to_string()
);
assert_eq!(
"http://example.com/.well-known/openid-configuration",
IssuerUrl::new("http://example.com/".to_string())
.unwrap()
.join(".well-known/openid-configuration")
.unwrap()
.to_string()
);
assert_eq!(
"http://example.com/x/.well-known/openid-configuration",
IssuerUrl::new("http://example.com/x".to_string())
.unwrap()
.join(".well-known/openid-configuration")
.unwrap()
.to_string()
);
assert_eq!(
"http://example.com/x/.well-known/openid-configuration",
IssuerUrl::new("http://example.com/x/".to_string())
.unwrap()
.join(".well-known/openid-configuration")
.unwrap()
.to_string()
);
}
#[test]
fn test_url_serialize() {
let issuer_url =
IssuerUrl::new("http://example.com/.well-known/openid-configuration".to_string())
.unwrap();
let serialized_url = serde_json::to_string(&issuer_url).unwrap();
assert_eq!(
"\"http://example.com/.well-known/openid-configuration\"",
serialized_url
);
let deserialized_url = serde_json::from_str(&serialized_url).unwrap();
assert_eq!(issuer_url, deserialized_url);
assert_eq!(
serde_json::to_string(&IssuerUrl::new("http://example.com".to_string()).unwrap())
.unwrap(),
"\"http://example.com\"",
);
}
}