use core::fmt;
use core::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use super::{HashAlgorithm, SdJwtError};
use crate::jose::base64url;
#[derive(Clone, PartialEq, Eq)]
pub struct Disclosure {
encoded: String,
salt: String,
name: Option<String>,
value: Value,
}
impl Disclosure {
pub fn property(salt: impl Into<String>, name: impl Into<String>, value: Value) -> Self {
let salt = salt.into();
let name = name.into();
let array = Value::Array(vec![
Value::String(salt.clone()),
Value::String(name.clone()),
value.clone(),
]);
Self {
encoded: encode(&array),
salt,
name: Some(name),
value,
}
}
pub fn element(salt: impl Into<String>, value: Value) -> Self {
let salt = salt.into();
let array = Value::Array(vec![Value::String(salt.clone()), value.clone()]);
Self {
encoded: encode(&array),
salt,
name: None,
value,
}
}
pub fn parse(encoded: &str) -> Result<Self, SdJwtError> {
let invalid = |reason: &str| SdJwtError::InvalidDisclosure {
disclosure: encoded.to_owned(),
reason: reason.to_owned(),
};
let bytes = base64url::decode(encoded).map_err(|_| invalid("not base64url"))?;
let array: Vec<Value> =
serde_json::from_slice(&bytes).map_err(|_| invalid("not a JSON array"))?;
let mut array = array.into_iter();
let Some(Value::String(salt)) = array.next() else {
return Err(invalid("the salt is not a string"));
};
let (name, value) = match (array.next(), array.next(), array.next()) {
(Some(value), None, None) => (None, value),
(Some(Value::String(name)), Some(value), None) => (Some(name), value),
(Some(_), Some(_), None) => return Err(invalid("the claim name is not a string")),
_ => return Err(invalid("not an array of two or three elements")),
};
Ok(Self {
encoded: encoded.to_owned(),
salt,
name,
value,
})
}
pub fn as_str(&self) -> &str {
&self.encoded
}
pub fn salt(&self) -> &str {
&self.salt
}
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn value(&self) -> &Value {
&self.value
}
pub fn is_element(&self) -> bool {
self.name.is_none()
}
pub fn digest(&self, alg: HashAlgorithm) -> String {
alg.digest(self.encoded.as_bytes())
}
}
fn encode(array: &Value) -> String {
let json = serde_json::to_vec(array).expect("a JSON value serializes");
base64url::encode(&json)
}
impl fmt::Debug for Disclosure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Disclosure")
.field("salt", &self.salt)
.field("name", &self.name)
.field("value", &self.value)
.finish_non_exhaustive()
}
}
impl fmt::Display for Disclosure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.encoded)
}
}
impl AsRef<str> for Disclosure {
fn as_ref(&self) -> &str {
&self.encoded
}
}
impl FromStr for Disclosure {
type Err = SdJwtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl Serialize for Disclosure {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.encoded)
}
}
impl<'de> Deserialize<'de> for Disclosure {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let encoded = String::deserialize(deserializer)?;
Self::parse(&encoded).map_err(serde::de::Error::custom)
}
}