1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct VerifiableCredential {
9 #[serde(rename = "@context")]
11 pub context: Vec<String>,
12
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub id: Option<String>,
16
17 pub r#type: Vec<String>,
19
20 pub issuer: Issuer,
22
23 pub valid_from: Option<String>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub valid_until: Option<String>,
29
30 pub credential_subject: serde_json::Value,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub credential_status: Option<serde_json::Value>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub proof: Option<serde_json::Value>,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44#[serde(untagged)]
45pub enum Issuer {
46 Uri(String),
47 Object {
48 id: String,
49 #[serde(flatten)]
50 properties: serde_json::Map<String, serde_json::Value>,
51 },
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use crate::presentation::VerifiablePresentation;
58
59 #[test]
60 fn parse_minimal_vc() {
61 let vc: VerifiableCredential =
62 serde_json::from_str(baseid_test_vectors::vc::MINIMAL_VC).unwrap();
63
64 assert_eq!(vc.context, vec!["https://www.w3.org/ns/credentials/v2"]);
65 assert_eq!(vc.r#type, vec!["VerifiableCredential"]);
66 assert!(matches!(vc.issuer, Issuer::Uri(ref s) if s.starts_with("did:key:")));
67 assert_eq!(vc.valid_from.as_deref(), Some("2024-01-01T00:00:00Z"));
68 assert!(vc.valid_until.is_none());
69 assert!(vc.id.is_none());
70 assert!(vc.credential_status.is_none());
71 assert!(vc.proof.is_none());
72
73 assert_eq!(
75 vc.credential_subject["id"],
76 "did:key:z6MkjRagNiMu91DduvCvgEsqKiCIjpH3DBnJT7BJfBquBb4o"
77 );
78 }
79
80 #[test]
81 fn parse_vc_issuer_object() {
82 let vc: VerifiableCredential =
83 serde_json::from_str(baseid_test_vectors::vc::VC_ISSUER_OBJECT).unwrap();
84
85 match &vc.issuer {
86 Issuer::Object { id, properties } => {
87 assert_eq!(id, "did:web:example.com");
88 assert_eq!(properties["name"], "Example University");
89 }
90 Issuer::Uri(_) => panic!("Expected Issuer::Object"),
91 }
92
93 assert_eq!(vc.valid_until.as_deref(), Some("2030-01-01T00:00:00Z"));
94 assert_eq!(vc.credential_subject["degree"]["type"], "BachelorDegree");
95 }
96
97 #[test]
98 fn parse_vc_with_status() {
99 let vc: VerifiableCredential =
100 serde_json::from_str(baseid_test_vectors::vc::VC_WITH_STATUS).unwrap();
101
102 assert_eq!(
103 vc.id.as_deref(),
104 Some("urn:uuid:58172aac-d8ba-11ed-83dd-0b3aef56cc33")
105 );
106 assert_eq!(
107 vc.r#type,
108 vec!["VerifiableCredential", "UniversityDegreeCredential"]
109 );
110
111 let status = vc.credential_status.as_ref().unwrap();
112 assert_eq!(status["type"], "BitstringStatusListEntry");
113 assert_eq!(status["statusPurpose"], "revocation");
114 }
115
116 #[test]
117 fn parse_minimal_vp() {
118 let vp: VerifiablePresentation =
119 serde_json::from_str(baseid_test_vectors::vc::MINIMAL_VP).unwrap();
120
121 assert_eq!(vp.context, vec!["https://www.w3.org/ns/credentials/v2"]);
122 assert_eq!(vp.r#type, vec!["VerifiablePresentation"]);
123 assert_eq!(
124 vp.holder.as_deref(),
125 Some("did:key:z6MkjRagNiMu91DduvCvgEsqKiCIjpH3DBnJT7BJfBquBb4o")
126 );
127 assert!(vp.verifiable_credential.is_empty());
128 }
129
130 #[test]
131 fn vc_json_roundtrip() {
132 let vc: VerifiableCredential =
133 serde_json::from_str(baseid_test_vectors::vc::VC_ISSUER_OBJECT).unwrap();
134 let json = serde_json::to_string(&vc).unwrap();
135 let vc2: VerifiableCredential = serde_json::from_str(&json).unwrap();
136
137 assert_eq!(vc.context, vc2.context);
138 assert_eq!(vc.r#type, vc2.r#type);
139 assert_eq!(vc.issuer, vc2.issuer);
140 assert_eq!(vc.valid_from, vc2.valid_from);
141 assert_eq!(vc.valid_until, vc2.valid_until);
142 }
143}