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;
17use serde::de::DeserializeOwned;
18
19use crate::credential::Credential;
20use crate::credential::CredentialV2;
21use crate::credential::Policy;
22use crate::credential::Proof;
23use crate::credential::RefreshService;
24use crate::error::Error;
25use crate::error::Result;
26
27use super::jwt_serialization::JwtPresentationV2Claims;
28use super::jwt_serialization::PresentationJwtClaims;
29use super::JwtPresentationOptions;
30use super::PresentationBuilder;
31
32#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
34pub struct Presentation<CRED, T = Object> {
35 #[serde(rename = "@context")]
37 pub context: OneOrMany<Context>,
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub id: Option<Url>,
41 #[serde(rename = "type")]
43 pub types: OneOrMany<String>,
44 #[rustfmt::skip]
46 #[serde(default = "Default::default", rename = "verifiableCredential", skip_serializing_if = "Vec::is_empty", deserialize_with = "deserialize_verifiable_credential", bound(deserialize = "CRED: serde::de::DeserializeOwned"))]
47 pub verifiable_credential: Vec<CRED>,
48 pub holder: Url,
50 #[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")]
52 pub refresh_service: OneOrMany<RefreshService>,
53 #[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")]
55 pub terms_of_use: OneOrMany<Policy>,
56 #[serde(flatten)]
58 pub properties: T,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub proof: Option<Proof>,
62}
63
64fn deserialize_verifiable_credential<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Vec<T>, D::Error>
66where
67 D: de::Deserializer<'de>,
68{
69 let verifiable_credentials = Vec::<T>::deserialize(deserializer)?;
70
71 (!verifiable_credentials.is_empty())
72 .then_some(verifiable_credentials)
73 .ok_or_else(|| de::Error::custom(Error::EmptyVerifiableCredentialArray))
74}
75
76impl<CRED, T> Presentation<CRED, T> {
77 #[deprecated(since = "1.8.0", note = "use `credential_context` instead")]
79 pub fn base_context() -> &'static Context {
80 Credential::<Object>::base_context()
81 }
82
83 pub fn credential_context(&self) -> &Context {
85 self.context.first().expect("context has at least one element")
86 }
87
88 pub fn is_v2(&self) -> bool {
90 self.credential_context() == CredentialV2::<()>::base_context()
91 }
92
93 pub const fn base_type() -> &'static str {
95 "VerifiablePresentation"
96 }
97
98 pub fn builder(holder: Url, properties: T) -> PresentationBuilder<CRED, T> {
102 PresentationBuilder::new(holder, properties)
103 }
104
105 pub fn from_builder(builder: PresentationBuilder<CRED, T>) -> Result<Self> {
107 let this: Self = Self {
108 context: builder.context.into(),
109 id: builder.id,
110 types: builder.types.into(),
111 verifiable_credential: builder.credentials,
112 holder: builder.holder,
113 refresh_service: builder.refresh_service.into(),
114 terms_of_use: builder.terms_of_use.into(),
115 properties: builder.properties,
116 proof: None,
117 };
118 this.check_structure()?;
119
120 Ok(this)
121 }
122
123 pub fn check_structure(&self) -> Result<()> {
130 let is_valid_base_context =
131 |ctx: &Context| ctx == Credential::<()>::base_context() || ctx == CredentialV2::<()>::base_context();
132 match self.context.first() {
134 Some(context) if is_valid_base_context(context) => {}
135 Some(_) | None => return Err(Error::MissingBaseContext),
136 }
137
138 if !self.types.iter().any(|type_| type_ == Self::base_type()) {
140 return Err(Error::MissingBaseType);
141 }
142 Ok(())
143 }
144
145 pub fn set_proof(&mut self, proof: Option<Proof>) {
149 self.proof = proof;
150 }
151}
152
153impl<C, T> Presentation<C, T>
154where
155 T: Clone + Serialize + DeserializeOwned,
156 C: Clone + Serialize + DeserializeOwned,
157{
158 pub fn serialize_jwt(&self, options: &JwtPresentationOptions) -> Result<String> {
165 let context = self.credential_context();
166 if context == Credential::<()>::base_context() {
167 let jwt_representation: PresentationJwtClaims<'_, C, T> = PresentationJwtClaims::new(self, options)?;
169 jwt_representation
170 .to_json()
171 .map_err(|err| Error::JwtClaimsSetSerializationError(err.into()))
172 } else if context == CredentialV2::<()>::base_context() {
173 JwtPresentationV2Claims::from_options(self.clone(), options)
175 .to_json()
176 .map_err(|err| Error::JwtClaimsSetSerializationError(err.into()))
177 } else {
178 Err(Error::MissingBaseContext)
179 }
180 }
181}
182
183impl<T> Display for Presentation<T>
184where
185 T: Serialize,
186{
187 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
188 self.fmt_json(f)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use serde_json::json;
195 use std::error::Error;
196
197 use identity_core::common::Object;
198 use identity_core::convert::FromJson;
199
200 use crate::presentation::Presentation;
201
202 #[test]
203 fn test_presentation_deserialization() {
204 assert!(Presentation::<Object>::from_json_value(json!({
208 "@context": [
209 "https://www.w3.org/2018/credentials/v1",
210 "https://www.w3.org/2018/credentials/examples/v1"
211 ],
212 "holder": "did:test:abc1",
213 "type": "VerifiablePresentation",
214 "verifiableCredential": [{
215 "@context": [
216 "https://www.w3.org/2018/credentials/v1",
217 "https://www.w3.org/2018/credentials/examples/v1"
218 ],
219 "id": "http://example.edu/credentials/1872",
220 "type": ["VerifiableCredential", "AlumniCredential"],
221 "issuer": "https://example.edu/issuers/565049",
222 "issuanceDate": "2010-01-01T19:23:24Z",
223 "credentialSubject": {
224 "id": "did:example:ebfeb1f712ebc6f1c276e12ec21",
225 "alumniOf": {
226 "id": "did:example:c276e12ec21ebfeb1f712ebc6f1",
227 "name": [{
228 "value": "Example University",
229 "lang": "en"
230 }, {
231 "value": "Exemple d'Université",
232 "lang": "fr"
233 }]
234 }
235 },
236 "proof": {
237 "type": "RsaSignature2018",
238 "created": "2017-06-18T21:19:10Z",
239 "proofPurpose": "assertionMethod",
240 "verificationMethod": "https://example.edu/issuers/565049#key-1",
241 "jws": "eyJhb...dBBPM"
242 }
243 }],
244 }))
245 .is_ok());
246 }
247
248 #[test]
249 fn test_presentation_deserialization_without_credentials() {
250 assert!(Presentation::<()>::from_json_value(json!({
252 "@context": [
253 "https://www.w3.org/2018/credentials/v1",
254 "https://www.w3.org/2018/credentials/examples/v1"
255 ],
256 "holder": "did:test:abc1",
257 "type": "VerifiablePresentation"
258 }))
259 .is_ok());
260 }
261
262 #[test]
263 fn test_presentation_deserialization_with_empty_credential_array() {
264 assert_eq!(
265 Presentation::<()>::from_json_value(json!({
266 "@context": [
267 "https://www.w3.org/2018/credentials/v1",
268 "https://www.w3.org/2018/credentials/examples/v1"
269 ],
270 "holder": "did:test:abc1",
271 "type": "VerifiablePresentation",
272 "verifiableCredential": []
273 }))
274 .unwrap_err()
275 .source()
276 .unwrap()
277 .to_string(),
278 crate::error::Error::EmptyVerifiableCredentialArray.to_string()
279 );
280 }
281}