1use getset::Getters;
2use serde::{Deserialize, Serialize};
3use serde_with::skip_serializing_none;
4use std::collections::HashMap;
5
6#[allow(dead_code)]
8#[skip_serializing_none]
9#[derive(Deserialize, Debug, Getters, PartialEq, Clone, Serialize)]
10pub struct PresentationDefinition {
11 #[getset(get = "pub")]
12 pub(crate) id: String,
13 #[getset(get = "pub")]
16 pub(crate) input_descriptors: Vec<InputDescriptor>,
17 pub(crate) name: Option<String>,
18 pub(crate) purpose: Option<String>,
19 pub(crate) format: Option<HashMap<ClaimFormatDesignation, ClaimFormatProperty>>,
20}
21
22#[allow(dead_code)]
25#[skip_serializing_none]
26#[derive(Deserialize, Debug, Getters, PartialEq, Clone, Serialize)]
27pub struct InputDescriptor {
28 #[getset(get = "pub")]
30 pub(crate) id: String,
31 pub(crate) name: Option<String>,
32 pub(crate) purpose: Option<String>,
33 pub(crate) format: Option<HashMap<ClaimFormatDesignation, ClaimFormatProperty>>,
34 #[getset(get = "pub")]
35 pub(crate) constraints: Constraints,
36 pub(crate) schema: Option<String>,
37}
38
39#[allow(dead_code)]
42#[derive(Deserialize, Debug, PartialEq, Eq, Hash, Clone, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub enum ClaimFormatDesignation {
45 Jwt,
46 JwtVc,
47 JwtVcJson,
48 JwtVp,
49 JwtVpJson,
50 Ldp,
51 LdpVc,
52 LdpVp,
53 AcVc,
54 AcVp,
55 MsoMdoc,
56}
57
58#[allow(dead_code)]
59#[derive(Deserialize, Debug, PartialEq, Clone, Serialize)]
60#[serde(rename_all = "snake_case")]
61pub enum ClaimFormatProperty {
62 Alg(Vec<serde_json::Value>),
63 ProofType(Vec<serde_json::Value>),
64}
65
66#[allow(dead_code)]
67#[skip_serializing_none]
68#[derive(Deserialize, Debug, Getters, Default, PartialEq, Clone, Serialize)]
69pub struct Constraints {
70 #[getset(get = "pub")]
71 pub(crate) fields: Option<Vec<Field>>,
72 #[getset(get = "pub")]
75 pub(crate) limit_disclosure: Option<LimitDisclosure>,
76}
77
78#[allow(dead_code)]
79#[derive(Deserialize, Debug, PartialEq, Clone, Serialize)]
80#[serde(rename_all = "snake_case")]
81pub enum LimitDisclosure {
82 Required,
83 Preferred,
84}
85
86#[allow(dead_code)]
87#[skip_serializing_none]
88#[derive(Deserialize, Debug, Getters, Default, PartialEq, Clone, Serialize)]
89pub struct Field {
90 #[getset(get = "pub")]
93 pub(crate) path: Vec<String>,
94 pub(crate) id: Option<String>,
95 pub(crate) purpose: Option<String>,
96 pub(crate) name: Option<String>,
97 #[getset(get = "pub")]
98 pub(crate) filter: Option<serde_json::Value>,
99 #[getset(get = "pub")]
101 pub(crate) optional: Option<bool>,
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use serde::de::DeserializeOwned;
108 use std::{fs::File, path::Path};
109
110 fn json_example<T>(path: &str) -> T
111 where
112 T: DeserializeOwned,
113 {
114 let file_path = Path::new(path);
115 let file = File::open(file_path).expect("file does not exist");
116 serde_json::from_reader::<_, T>(file).expect("could not parse json")
117 }
118
119 #[test]
120 fn test_deserialize_presentation_definition() {
121 assert_eq!(
122 PresentationDefinition {
123 id: "example_vc_ac_sd".to_string(),
124 name: None,
125 format: None,
126 input_descriptors: vec![InputDescriptor {
127 id: "id_credential".to_string(),
128 name: None,
129 purpose: None,
130 format: Some(HashMap::from_iter(vec![(
131 ClaimFormatDesignation::AcVc,
132 ClaimFormatProperty::ProofType(vec![serde_json::json!("CLSignature2019")])
133 )])),
134 constraints: Constraints {
135 limit_disclosure: Some(LimitDisclosure::Required),
136 fields: Some(vec![
137 Field {
138 path: vec!["$.schema_id".to_string()],
139 filter: Some(serde_json::json!({
140 "type": "string",
141 "const": "did:indy:idu:test:3QowxFtwciWceMFr7WbwnM:2:BasicScheme:0\\.1"
142 })),
143 ..Default::default()
144 },
145 Field {
146 path: vec!["$.values.first_name".to_string()],
147 ..Default::default()
148 },
149 Field {
150 path: vec!["$.values.last_name".to_string()],
151 ..Default::default()
152 }
153 ]),
154 },
155 schema: None,
156 }],
157 purpose: None,
158 },
159 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ac_vc_sd.json")
160 );
161
162 assert_eq!(
163 PresentationDefinition {
164 id: "example_vc_ac".to_string(),
165 name: None,
166 format: None,
167 input_descriptors: vec![InputDescriptor {
168 id: "id_credential".to_string(),
169 name: None,
170 purpose: None,
171 format: Some(HashMap::from_iter(vec![(
172 ClaimFormatDesignation::AcVc,
173 ClaimFormatProperty::ProofType(vec![serde_json::json!("CLSignature2019")])
174 )])),
175 constraints: Constraints {
176 fields: Some(vec![Field {
177 path: vec!["$.schema_id".to_string()],
178 filter: Some(serde_json::json!({
179 "type": "string",
180 "const": "did:indy:idu:test:3QowxFtwciWceMFr7WbwnM:2:BasicScheme:0\\.1"
181 })),
182 ..Default::default()
183 }]),
184 limit_disclosure: None,
185 },
186 schema: None,
187 }],
188 purpose: None,
189 },
190 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ac_vc.json")
191 );
192
193 assert_eq!(
194 PresentationDefinition {
195 id: "example_jwt_vc".to_string(),
196 name: None,
197 format: None,
198 input_descriptors: vec![InputDescriptor {
199 id: "id_credential".to_string(),
200 name: None,
201 purpose: None,
202 format: Some(HashMap::from_iter(vec![(
203 ClaimFormatDesignation::JwtVcJson,
204 ClaimFormatProperty::ProofType(vec![serde_json::json!("JsonWebSignature2020")])
205 )])),
206 constraints: Constraints {
207 fields: Some(vec![Field {
208 path: vec!["$.vc.type".to_string()],
209 filter: Some(serde_json::json!({
210 "type": "array",
211 "contains": {
212 "const": "IDCredential"
213 }
214 })),
215 ..Default::default()
216 }]),
217 limit_disclosure: None,
218 },
219 schema: None,
220 }],
221 purpose: None,
222 },
223 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_jwt_vc.json")
224 );
225
226 assert_eq!(
227 PresentationDefinition {
228 id: "example_ldp_vc".to_string(),
229 name: None,
230 format: None,
231 input_descriptors: vec![InputDescriptor {
232 id: "id_credential".to_string(),
233 name: None,
234 purpose: None,
235 format: Some(HashMap::from_iter(vec![(
236 ClaimFormatDesignation::LdpVc,
237 ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
238 )])),
239 constraints: Constraints {
240 fields: Some(vec![Field {
241 path: vec!["$.type".to_string()],
242 filter: Some(serde_json::json!({
243 "type": "array",
244 "contains": {
245 "const": "IDCredential"
246 }
247 })),
248 ..Default::default()
249 }]),
250 limit_disclosure: None,
251 },
252 schema: None,
253 }],
254 purpose: None,
255 },
256 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_ldp_vc.json")
257 );
258
259 assert_eq!(
261 PresentationDefinition {
262 id: "mDL-sample-req".to_string(),
263 name: None,
264 format: None,
265 input_descriptors: vec![InputDescriptor {
266 id: "mDL".to_string(),
267 name: None,
268 purpose: None,
269 format: Some(HashMap::from_iter(vec![(
270 ClaimFormatDesignation::MsoMdoc,
271 ClaimFormatProperty::Alg(vec![serde_json::json!("EdDSA"), serde_json::json!("ES256")])
272 )])),
273 constraints: Constraints {
274 limit_disclosure: Some(LimitDisclosure::Required),
275 fields: Some(vec![
276 Field {
277 path: vec!["$.mdoc.doctype".to_string()],
278 filter: Some(serde_json::json!({
279 "type": "string",
280 "const": "org.iso.18013.5.1.mDL"
281 })),
282 ..Default::default()
283 },
284 Field {
285 path: vec!["$.mdoc.namespace".to_string()],
286 filter: Some(serde_json::json!({
287 "type": "string",
288 "const": "org.iso.18013.5.1"
289 })),
290 ..Default::default()
291 },
292 Field {
293 path: vec!["$.mdoc.family_name".to_string()],
294 ..Default::default()
295 },
296 Field {
297 path: vec!["$.mdoc.portrait".to_string()],
298 ..Default::default()
299 },
300 Field {
301 path: vec!["$.mdoc.driving_privileges".to_string()],
302 ..Default::default()
303 },
304 ]),
305 },
306 schema: None,
307 }],
308 purpose: None,
309 },
310 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/pd_mdl_iso_cbor.json")
311 );
312
313 assert_eq!(
314 PresentationDefinition {
315 id: "example with selective disclosure".to_string(),
316 name: None,
317 format: None,
318 input_descriptors: vec![InputDescriptor {
319 id: "ID card with constraints".to_string(),
320 name: None,
321 purpose: None,
322 format: Some(HashMap::from_iter(vec![(
323 ClaimFormatDesignation::LdpVc,
324 ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
325 )])),
326 constraints: Constraints {
327 fields: Some(vec![
328 Field {
329 path: vec!["$.type".to_string()],
330 filter: Some(serde_json::json!({
331 "type": "string",
332 "pattern": "IDCardCredential"
333 })),
334 ..Default::default()
335 },
336 Field {
337 path: vec!["$.credentialSubject.given_name".to_string()],
338 ..Default::default()
339 },
340 Field {
341 path: vec!["$.credentialSubject.family_name".to_string()],
342 ..Default::default()
343 },
344 Field {
345 path: vec!["$.credentialSubject.birthdate".to_string()],
346 ..Default::default()
347 }
348 ]),
349 limit_disclosure: Some(LimitDisclosure::Required),
350 },
351 schema: None,
352 }],
353 purpose: None,
354 },
355 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/vp_token_type_and_claims.json")
356 );
357
358 assert_eq!(
359 PresentationDefinition {
360 id: "vp token example".to_string(),
361 name: None,
362 format: None,
363 input_descriptors: vec![InputDescriptor {
364 id: "id card credential".to_string(),
365 name: None,
366 purpose: None,
367 format: Some(HashMap::from_iter(vec![(
368 ClaimFormatDesignation::LdpVc,
369 ClaimFormatProperty::ProofType(vec![serde_json::json!("Ed25519Signature2018")])
370 )])),
371 constraints: Constraints {
372 fields: Some(vec![Field {
373 path: vec!["$.type".to_string()],
374 filter: Some(serde_json::json!({
375 "type": "string",
376 "pattern": "IDCardCredential"
377 })),
378 ..Default::default()
379 }]),
380 limit_disclosure: None,
381 },
382 schema: None,
383 }],
384 purpose: None,
385 },
386 json_example::<PresentationDefinition>("../oid4vp/tests/examples/request/vp_token_type_only.json")
387 );
388 }
389}