baseid_vc/
credential.rs

1//! Verifiable Credential types per W3C VC Data Model 2.0.
2
3use serde::{Deserialize, Serialize};
4
5/// A W3C Verifiable Credential.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct VerifiableCredential {
9    /// JSON-LD context.
10    #[serde(rename = "@context")]
11    pub context: Vec<String>,
12
13    /// Credential identifier.
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub id: Option<String>,
16
17    /// Credential types.
18    pub r#type: Vec<String>,
19
20    /// Credential issuer.
21    pub issuer: Issuer,
22
23    /// Date the credential becomes valid.
24    pub valid_from: Option<String>,
25
26    /// Date the credential expires.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub valid_until: Option<String>,
29
30    /// Credential subject(s).
31    pub credential_subject: serde_json::Value,
32
33    /// Credential status for revocation checking.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub credential_status: Option<serde_json::Value>,
36
37    /// Cryptographic proof(s).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub proof: Option<serde_json::Value>,
40}
41
42/// Credential issuer — either a URI string or an object with an id.
43#[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        // Subject has an id
74        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}