identity_credential/presentation/
presentation.rsuse core::fmt::Display;
use core::fmt::Formatter;
use serde::de;
use serde::Deserialize;
use serde::Serialize;
use identity_core::common::Context;
use identity_core::common::Object;
use identity_core::common::OneOrMany;
use identity_core::common::Url;
use identity_core::convert::FmtJson;
use identity_core::convert::ToJson;
use crate::credential::Credential;
use crate::credential::Policy;
use crate::credential::Proof;
use crate::credential::RefreshService;
use crate::error::Error;
use crate::error::Result;
use super::jwt_serialization::PresentationJwtClaims;
use super::JwtPresentationOptions;
use super::PresentationBuilder;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Presentation<CRED, T = Object> {
#[serde(rename = "@context")]
pub context: OneOrMany<Context>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Url>,
#[serde(rename = "type")]
pub types: OneOrMany<String>,
#[rustfmt::skip]
#[serde(default = "Default::default", rename = "verifiableCredential", skip_serializing_if = "Vec::is_empty", deserialize_with = "deserialize_verifiable_credential", bound(deserialize = "CRED: serde::de::DeserializeOwned"))]
pub verifiable_credential: Vec<CRED>,
pub holder: Url,
#[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")]
pub refresh_service: OneOrMany<RefreshService>,
#[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")]
pub terms_of_use: OneOrMany<Policy>,
#[serde(flatten)]
pub properties: T,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<Proof>,
}
fn deserialize_verifiable_credential<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: de::Deserializer<'de>,
{
let verifiable_credentials = Vec::<T>::deserialize(deserializer)?;
(!verifiable_credentials.is_empty())
.then_some(verifiable_credentials)
.ok_or_else(|| de::Error::custom(Error::EmptyVerifiableCredentialArray))
}
impl<CRED, T> Presentation<CRED, T> {
pub fn base_context() -> &'static Context {
Credential::<Object>::base_context()
}
pub const fn base_type() -> &'static str {
"VerifiablePresentation"
}
pub fn builder(holder: Url, properties: T) -> PresentationBuilder<CRED, T> {
PresentationBuilder::new(holder, properties)
}
pub fn from_builder(builder: PresentationBuilder<CRED, T>) -> Result<Self> {
let this: Self = Self {
context: builder.context.into(),
id: builder.id,
types: builder.types.into(),
verifiable_credential: builder.credentials,
holder: builder.holder,
refresh_service: builder.refresh_service.into(),
terms_of_use: builder.terms_of_use.into(),
properties: builder.properties,
proof: None,
};
this.check_structure()?;
Ok(this)
}
pub fn check_structure(&self) -> Result<()> {
match self.context.get(0) {
Some(context) if context == Self::base_context() => {}
Some(_) | None => return Err(Error::MissingBaseContext),
}
if !self.types.iter().any(|type_| type_ == Self::base_type()) {
return Err(Error::MissingBaseType);
}
Ok(())
}
pub fn serialize_jwt(&self, options: &JwtPresentationOptions) -> Result<String>
where
T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
CRED: ToOwned<Owned = CRED> + serde::Serialize + serde::de::DeserializeOwned + Clone,
{
let jwt_representation: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::new(self, options)?;
jwt_representation
.to_json()
.map_err(|err| Error::JwtClaimsSetSerializationError(err.into()))
}
pub fn set_proof(&mut self, proof: Option<Proof>) {
self.proof = proof;
}
}
impl<T> Display for Presentation<T>
where
T: Serialize,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.fmt_json(f)
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use std::error::Error;
use identity_core::common::Object;
use identity_core::convert::FromJson;
use crate::presentation::Presentation;
#[test]
fn test_presentation_deserialization() {
assert!(Presentation::<Object>::from_json_value(json!({
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"holder": "did:test:abc1",
"type": "VerifiablePresentation",
"verifiableCredential": [{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"id": "http://example.edu/credentials/1872",
"type": ["VerifiableCredential", "AlumniCredential"],
"issuer": "https://example.edu/issuers/565049",
"issuanceDate": "2010-01-01T19:23:24Z",
"credentialSubject": {
"id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
"alumniOf": {
"id": "did:example:c276e12ec21ebfeb1f712ebc6f1",
"name": [{
"value": "Example University",
"lang": "en"
}, {
"value": "Exemple d'Université",
"lang": "fr"
}]
}
},
"proof": {
"type": "RsaSignature2018",
"created": "2017-06-18T21:19:10Z",
"proofPurpose": "assertionMethod",
"verificationMethod": "https://example.edu/issuers/565049#key-1",
"jws": "eyJhb...dBBPM"
}
}],
}))
.is_ok());
}
#[test]
fn test_presentation_deserialization_without_credentials() {
assert!(Presentation::<()>::from_json_value(json!({
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"holder": "did:test:abc1",
"type": "VerifiablePresentation"
}))
.is_ok());
}
#[test]
fn test_presentation_deserialization_with_empty_credential_array() {
assert_eq!(
Presentation::<()>::from_json_value(json!({
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"
],
"holder": "did:test:abc1",
"type": "VerifiablePresentation",
"verifiableCredential": []
}))
.unwrap_err()
.source()
.unwrap()
.to_string(),
crate::error::Error::EmptyVerifiableCredentialArray.to_string()
);
}
}