identity_credential/presentation/
presentation.rs1use core::fmt::Display;
5use core::fmt::Formatter;
6
7use serde::de;
8use serde::Deserialize;
9use serde::Serialize;
10
11use identity_core::common::Context;
12use identity_core::common::Object;
13use identity_core::common::OneOrMany;
14use identity_core::common::Url;
15use identity_core::convert::FmtJson;
16use identity_core::convert::ToJson;
17
18use crate::credential::Credential;
19use crate::credential::Policy;
20use crate::credential::Proof;
21use crate::credential::RefreshService;
22use crate::error::Error;
23use crate::error::Result;
24
25use super::jwt_serialization::PresentationJwtClaims;
26use super::JwtPresentationOptions;
27use super::PresentationBuilder;
28
29#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
31pub struct Presentation<CRED, T = Object> {
32 #[serde(rename = "@context")]
34 pub context: OneOrMany<Context>,
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub id: Option<Url>,
38 #[serde(rename = "type")]
40 pub types: OneOrMany<String>,
41 #[rustfmt::skip]
43 #[serde(default = "Default::default", rename = "verifiableCredential", skip_serializing_if = "Vec::is_empty", deserialize_with = "deserialize_verifiable_credential", bound(deserialize = "CRED: serde::de::DeserializeOwned"))]
44 pub verifiable_credential: Vec<CRED>,
45 pub holder: Url,
47 #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")]
49 pub refresh_service: OneOrMany<RefreshService>,
50 #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")]
52 pub terms_of_use: OneOrMany<Policy>,
53 #[serde(flatten)]
55 pub properties: T,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub proof: Option<Proof>,
59}
60
61fn deserialize_verifiable_credential<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Vec<T>, D::Error>
63where
64 D: de::Deserializer<'de>,
65{
66 let verifiable_credentials = Vec::<T>::deserialize(deserializer)?;
67
68 (!verifiable_credentials.is_empty())
69 .then_some(verifiable_credentials)
70 .ok_or_else(|| de::Error::custom(Error::EmptyVerifiableCredentialArray))
71}
72
73impl<CRED, T> Presentation<CRED, T> {
74 pub fn base_context() -> &'static Context {
76 Credential::<Object>::base_context()
77 }
78
79 pub const fn base_type() -> &'static str {
81 "VerifiablePresentation"
82 }
83
84 pub fn builder(holder: Url, properties: T) -> PresentationBuilder<CRED, T> {
88 PresentationBuilder::new(holder, properties)
89 }
90
91 pub fn from_builder(builder: PresentationBuilder<CRED, T>) -> Result<Self> {
93 let this: Self = Self {
94 context: builder.context.into(),
95 id: builder.id,
96 types: builder.types.into(),
97 verifiable_credential: builder.credentials,
98 holder: builder.holder,
99 refresh_service: builder.refresh_service.into(),
100 terms_of_use: builder.terms_of_use.into(),
101 properties: builder.properties,
102 proof: None,
103 };
104 this.check_structure()?;
105
106 Ok(this)
107 }
108
109 pub fn check_structure(&self) -> Result<()> {
116 match self.context.get(0) {
118 Some(context) if context == Self::base_context() => {}
119 Some(_) | None => return Err(Error::MissingBaseContext),
120 }
121
122 if !self.types.iter().any(|type_| type_ == Self::base_type()) {
124 return Err(Error::MissingBaseType);
125 }
126 Ok(())
127 }
128
129 pub fn serialize_jwt(&self, options: &JwtPresentationOptions) -> Result<String>
134 where
135 T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
136 CRED: ToOwned<Owned = CRED> + serde::Serialize + serde::de::DeserializeOwned + Clone,
137 {
138 let jwt_representation: PresentationJwtClaims<'_, CRED, T> = PresentationJwtClaims::new(self, options)?;
139 jwt_representation
140 .to_json()
141 .map_err(|err| Error::JwtClaimsSetSerializationError(err.into()))
142 }
143
144 pub fn set_proof(&mut self, proof: Option<Proof>) {
148 self.proof = proof;
149 }
150}
151
152impl<T> Display for Presentation<T>
153where
154 T: Serialize,
155{
156 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
157 self.fmt_json(f)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use serde_json::json;
164 use std::error::Error;
165
166 use identity_core::common::Object;
167 use identity_core::convert::FromJson;
168
169 use crate::presentation::Presentation;
170
171 #[test]
172 fn test_presentation_deserialization() {
173 assert!(Presentation::<Object>::from_json_value(json!({
177 "@context": [
178 "https://www.w3.org/2018/credentials/v1",
179 "https://www.w3.org/2018/credentials/examples/v1"
180 ],
181 "holder": "did:test:abc1",
182 "type": "VerifiablePresentation",
183 "verifiableCredential": [{
184 "@context": [
185 "https://www.w3.org/2018/credentials/v1",
186 "https://www.w3.org/2018/credentials/examples/v1"
187 ],
188 "id": "http://example.edu/credentials/1872",
189 "type": ["VerifiableCredential", "AlumniCredential"],
190 "issuer": "https://example.edu/issuers/565049",
191 "issuanceDate": "2010-01-01T19:23:24Z",
192 "credentialSubject": {
193 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
194 "alumniOf": {
195 "id": "did:example:c276e12ec21ebfeb1f712ebc6f1",
196 "name": [{
197 "value": "Example University",
198 "lang": "en"
199 }, {
200 "value": "Exemple d'Université",
201 "lang": "fr"
202 }]
203 }
204 },
205 "proof": {
206 "type": "RsaSignature2018",
207 "created": "2017-06-18T21:19:10Z",
208 "proofPurpose": "assertionMethod",
209 "verificationMethod": "https://example.edu/issuers/565049#key-1",
210 "jws": "eyJhb...dBBPM"
211 }
212 }],
213 }))
214 .is_ok());
215 }
216
217 #[test]
218 fn test_presentation_deserialization_without_credentials() {
219 assert!(Presentation::<()>::from_json_value(json!({
221 "@context": [
222 "https://www.w3.org/2018/credentials/v1",
223 "https://www.w3.org/2018/credentials/examples/v1"
224 ],
225 "holder": "did:test:abc1",
226 "type": "VerifiablePresentation"
227 }))
228 .is_ok());
229 }
230
231 #[test]
232 fn test_presentation_deserialization_with_empty_credential_array() {
233 assert_eq!(
234 Presentation::<()>::from_json_value(json!({
235 "@context": [
236 "https://www.w3.org/2018/credentials/v1",
237 "https://www.w3.org/2018/credentials/examples/v1"
238 ],
239 "holder": "did:test:abc1",
240 "type": "VerifiablePresentation",
241 "verifiableCredential": []
242 }))
243 .unwrap_err()
244 .source()
245 .unwrap()
246 .to_string(),
247 crate::error::Error::EmptyVerifiableCredentialArray.to_string()
248 );
249 }
250}