use indexmap::IndexMap;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value;
use super::OneOrMany;
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct JwtClaims {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub iss: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub sub: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub aud: Option<OneOrMany<String>>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "numeric_date"
)]
pub exp: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "numeric_date"
)]
pub nbf: Option<i64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "numeric_date"
)]
pub iat: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub jti: Option<String>,
#[serde(flatten)]
pub additional: IndexMap<String, Value>,
}
pub(crate) fn numeric_date<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<i64>, D::Error> {
let Some(number) = Option::<serde_json::Number>::deserialize(deserializer)? else {
return Ok(None);
};
number
.as_i64()
.or_else(|| {
number
.as_f64()
.filter(|seconds| (i64::MIN as f64..i64::MAX as f64).contains(seconds))
.map(|seconds| seconds.floor() as i64)
})
.map(Some)
.ok_or_else(|| D::Error::custom(format!("NumericDate {number} is out of range")))
}
impl JwtClaims {
pub const REGISTERED: [&'static str; 7] = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"];
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_iss(mut self, issuer: impl Into<String>) -> Self {
self.iss = Some(issuer.into());
self
}
#[must_use]
pub fn with_sub(mut self, subject: impl Into<String>) -> Self {
self.sub = Some(subject.into());
self
}
#[must_use]
pub fn with_jti(mut self, id: impl Into<String>) -> Self {
self.jti = Some(id.into());
self
}
pub fn audiences(&self) -> impl Iterator<Item = &str> {
self.aud
.iter()
.flat_map(OneOrMany::iter)
.map(String::as_str)
}
#[must_use]
pub fn with_aud(mut self, audience: impl Into<String>) -> Self {
self.aud = Some(OneOrMany::One(audience.into()));
self
}
#[must_use]
pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.additional.insert(name.into(), value.into());
self
}
}