use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
use serde_json::Value;
use crate::did::{Context, OneOrMany, Uri};
use super::model::{CONTEXT_V1, append_context, check_context_and_type, push_type};
use super::{CredentialError, Jwt, JwtClaims, Party, claims};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(remote = "Self")]
pub struct Presentation {
#[serde(rename = "@context")]
pub context: Context,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Uri>,
#[serde(rename = "type")]
pub types: OneOrMany<String>,
#[serde(
rename = "verifiableCredential",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub verifiable_credential: Vec<Jwt>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub holder: Option<Party>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl Serialize for Presentation {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Self::serialize(self, serializer)
}
}
impl<'de> Deserialize<'de> for Presentation {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let presentation = Self::deserialize(deserializer)?;
presentation.check().map_err(de::Error::custom)?;
Ok(presentation)
}
}
impl Default for Presentation {
fn default() -> Self {
Self::new()
}
}
impl Presentation {
pub const CONTEXT: &'static str = CONTEXT_V1;
pub const TYPE: &'static str = "VerifiablePresentation";
pub fn new() -> Self {
Self {
context: Context::uri(Self::CONTEXT),
id: None,
types: OneOrMany::One(Self::TYPE.to_owned()),
verifiable_credential: Vec::new(),
holder: None,
properties: IndexMap::new(),
}
}
#[must_use]
pub fn with_context(mut self, context: impl Into<String>) -> Self {
self.context = append_context(self.context, context.into());
self
}
#[must_use]
pub fn with_id(mut self, id: impl Into<Uri>) -> Self {
self.id = Some(id.into());
self
}
#[must_use]
pub fn with_type(mut self, presentation_type: impl Into<String>) -> Self {
push_type(&mut self.types, presentation_type.into());
self
}
#[must_use]
pub fn with_holder(mut self, holder: impl Into<Party>) -> Self {
self.holder = Some(holder.into());
self
}
#[must_use]
pub fn with_credential(mut self, credential: Jwt) -> Self {
self.verifiable_credential.push(credential);
self
}
#[must_use]
pub fn with_property(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.properties.insert(name.into(), value.into());
self
}
pub fn has_type(&self, presentation_type: &str) -> bool {
self.types.iter().any(|t| t == presentation_type)
}
pub fn check(&self) -> Result<(), CredentialError> {
check_context_and_type(&self.context, Self::CONTEXT, &self.types, Self::TYPE)
}
pub fn to_jwt_claims(&self) -> Result<JwtClaims, CredentialError> {
self.check()?;
let mut vp = claims::to_object(self);
let jti = claims::take_id(&mut vp, "id");
let iss = claims::take_id(&mut vp, "holder");
Ok(JwtClaims {
iss,
jti,
additional: IndexMap::from([("vp".to_owned(), Value::Object(vp))]),
..JwtClaims::default()
})
}
pub fn from_jwt_claims(jwt_claims: &JwtClaims) -> Result<Self, CredentialError> {
let mut vp = claims::claim_object(jwt_claims, "vp")?;
claims::merge_id(&mut vp, "id", "jti", jwt_claims.jti.as_deref())?;
claims::merge_id(&mut vp, "holder", "iss", jwt_claims.iss.as_deref())?;
serde_json::from_value(Value::Object(vp)).map_err(|e| CredentialError::InvalidClaim {
claim: "vp",
reason: e.to_string(),
})
}
}