Skip to main content

dtg_credentials/
create.rs

1/*!
2*   Builder methods for creating new entities.
3*/
4
5#[allow(deprecated)]
6use crate::{
7    CredentialSubject, CredentialSubjectBasic, CredentialSubjectEndorsement,
8    CredentialSubjectRCard, CredentialSubjectWitness, DTGCommon, DTGCredential, DTGCredentialType,
9    WitnessContext,
10};
11use chrono::{DateTime, Utc};
12use serde_json::Value;
13
14impl DTGCredential {
15    /// Creates a new Verified Memebrship Credential (VMC)
16    /// issuer: The issuer DID of the credential
17    /// subject: The DID of the subject of this credential
18    /// valid_from: The datetime from which this credential is valid
19    /// valid_until: Optional: The datetime this credential is valid until
20    /// personhood: Whether this VMC can be used as a form of Personhood Credential
21    ///             - Adds PersonhoodCredential to the type array if true
22    pub fn new_vmc(
23        issuer: String,
24        subject: String,
25        valid_from: DateTime<Utc>,
26        valid_until: Option<DateTime<Utc>>,
27        personhood: bool,
28    ) -> Self {
29        let mut vmc = DTGCommon {
30            issuer,
31            valid_from,
32            valid_until,
33            credential_subject: CredentialSubject::Basic(CredentialSubjectBasic { id: subject }),
34            ..Default::default()
35        };
36
37        vmc.type_.push(DTGCredentialType::Membership.to_string());
38
39        if personhood {
40            vmc.type_.push("PersonhoodCredential".to_string());
41        }
42
43        DTGCredential {
44            credential: vmc,
45            type_: DTGCredentialType::Membership,
46            version: crate::W3CVCVersion::V2_0,
47        }
48    }
49
50    /// Creates a new Verified Relationship Credential (VRC)
51    /// issuer: The issuer DID of the credential
52    /// subject: The DID of the subject of this credential
53    /// valid_from: The datetime from which this credential is valid
54    /// valid_until: Optional: The datetime this credential is valid until
55    pub fn new_vrc(
56        issuer: String,
57        subject: String,
58        valid_from: DateTime<Utc>,
59        valid_until: Option<DateTime<Utc>>,
60    ) -> Self {
61        let mut vrc = DTGCommon {
62            issuer,
63            valid_from,
64            valid_until,
65            credential_subject: CredentialSubject::Basic(CredentialSubjectBasic { id: subject }),
66            ..Default::default()
67        };
68
69        vrc.type_.push(DTGCredentialType::Relationship.to_string());
70
71        DTGCredential {
72            credential: vrc,
73            type_: DTGCredentialType::Relationship,
74            version: crate::W3CVCVersion::V2_0,
75        }
76    }
77
78    /// Creates a new Verified Invitation Credential (VIC)
79    /// issuer: The issuer DID of the credential
80    /// subject: The DID of the subject of this credential
81    /// valid_from: The datetime from which this credential is valid
82    /// valid_until: Optional: The datetime this credential is valid until
83    pub fn new_vic(
84        issuer: String,
85        subject: String,
86        valid_from: DateTime<Utc>,
87        valid_until: Option<DateTime<Utc>>,
88    ) -> Self {
89        let mut vic = DTGCommon {
90            issuer,
91            valid_from,
92            valid_until,
93            credential_subject: CredentialSubject::Basic(CredentialSubjectBasic { id: subject }),
94            ..Default::default()
95        };
96
97        vic.type_.push(DTGCredentialType::Invitation.to_string());
98
99        DTGCredential {
100            credential: vic,
101            type_: DTGCredentialType::Invitation,
102            version: crate::W3CVCVersion::V2_0,
103        }
104    }
105
106    /// Creates a new Verified Persona Credential (VPC)
107    /// issuer: The issuer DID of the credential
108    /// subject: The DID of the subject of this credential
109    /// valid_from: The datetime from which this credential is valid
110    /// valid_until: Optional: The datetime this credential is valid until
111    pub fn new_vpc(
112        issuer: String,
113        subject: String,
114        valid_from: DateTime<Utc>,
115        valid_until: Option<DateTime<Utc>>,
116    ) -> Self {
117        let mut vpc = DTGCommon {
118            issuer,
119            valid_from,
120            valid_until,
121            credential_subject: CredentialSubject::Basic(CredentialSubjectBasic { id: subject }),
122            ..Default::default()
123        };
124
125        vpc.type_.push(DTGCredentialType::Persona.to_string());
126
127        DTGCredential {
128            credential: vpc,
129            type_: DTGCredentialType::Persona,
130            version: crate::W3CVCVersion::V2_0,
131        }
132    }
133
134    /// Creates a new Verified Endorsement Credential (VEC)
135    /// issuer: The issuer DID of the credential
136    /// subject: The DID of the subject of this credential
137    /// valid_from: The datetime from which this credential is valid
138    /// valid_until: Optional: The datetime this credential is valid until
139    /// endorsement: The endorsement details for this credential
140    pub fn new_vec(
141        issuer: String,
142        subject: String,
143        valid_from: DateTime<Utc>,
144        valid_until: Option<DateTime<Utc>>,
145        endorsement: Value,
146    ) -> Self {
147        let mut vec = DTGCommon {
148            issuer,
149            valid_from,
150            valid_until,
151            credential_subject: CredentialSubject::Endorsement(CredentialSubjectEndorsement {
152                id: subject,
153                endorsement,
154            }),
155            ..Default::default()
156        };
157
158        vec.type_.push(DTGCredentialType::Endorsement.to_string());
159
160        DTGCredential {
161            credential: vec,
162            type_: DTGCredentialType::Endorsement,
163            version: crate::W3CVCVersion::V2_0,
164        }
165    }
166
167    /// Creates a new Verified Witness Credential (VWC)
168    /// issuer: The issuer DID of the credential - an M-DID, or the DID of a VTA acting
169    ///         according to VTC policy
170    /// subject: The DID of the observed party. For a witnessed bi-directional exchange this
171    ///          MUST be the issuer of the VRC that this VWC attests (the VRC referenced by
172    ///          `digest`), so that the two VWCs of an exchange are unambiguously bound to
173    ///          their respective directions. The witness should issue one VWC per direction.
174    /// valid_from: The datetime from which this credential is valid
175    /// valid_until: Optional: The datetime this credential is valid until
176    /// task_context: Required `threadId` of the trust task exchange the witnessing occurred in
177    /// digest: Optional Witness cryptographic hash of the witnessed VRC (prevents misuse).
178    ///         Produce this with [DTGCredential::digest_multibase] on the witnessed VRC.
179    /// witness_context: Optional Semantic context for the witness
180    pub fn new_vwc(
181        issuer: String,
182        subject: String,
183        valid_from: DateTime<Utc>,
184        valid_until: Option<DateTime<Utc>>,
185        task_context: String,
186        digest: Option<String>,
187        witness_context: Option<WitnessContext>,
188    ) -> Self {
189        let mut vwc = DTGCommon {
190            issuer,
191            valid_from,
192            valid_until,
193            task_context: Some(task_context),
194            credential_subject: CredentialSubject::Witness(CredentialSubjectWitness {
195                id: subject,
196                digest,
197                witness_context,
198            }),
199            ..Default::default()
200        };
201
202        vwc.type_.push(DTGCredentialType::Witness.to_string());
203
204        DTGCredential {
205            credential: vwc,
206            type_: DTGCredentialType::Witness,
207            version: crate::W3CVCVersion::V2_0,
208        }
209    }
210
211    /// Creates a new Verified RCard Credential (VWC)
212    /// issuer: The issuer DID of the credential
213    /// subject: The DID of the subject of this credential
214    /// valid_from: The datetime from which this credential is valid
215    /// valid_until: Optional: The datetime this credential is valid until
216    /// card: JSON Value representing a Jcard (RFC 7095) format
217    #[deprecated(
218        since = "0.2.0",
219        note = "The r-card is a verifiable data structure (VDS), not a DTGCredential subtype. \
220                It was removed from the DTG Core Credentials specification in Working Draft 01 \
221                and will be defined by the planned DTG Verifiable Data Structures specification. \
222                This constructor will be removed in a future release."
223    )]
224    #[allow(deprecated)]
225    pub fn new_rcard(
226        issuer: String,
227        subject: String,
228        valid_from: DateTime<Utc>,
229        valid_until: Option<DateTime<Utc>>,
230        card: Value,
231    ) -> Self {
232        let mut rcard = DTGCommon {
233            issuer,
234            valid_from,
235            valid_until,
236            credential_subject: CredentialSubject::RCard(CredentialSubjectRCard {
237                id: subject,
238                card,
239            }),
240            ..Default::default()
241        };
242
243        rcard.type_.push(DTGCredentialType::RCard.to_string());
244
245        DTGCredential {
246            credential: rcard,
247            type_: DTGCredentialType::RCard,
248            version: crate::W3CVCVersion::V2_0,
249        }
250    }
251}
252
253#[cfg(test)]
254#[allow(deprecated)]
255mod tests {
256    use crate::{DTGCredential, WitnessContext};
257    use chrono::{DateTime, Utc};
258    use serde_json::json;
259
260    #[test]
261    fn test_vmc_serialization() {
262        let vmc = DTGCredential::new_vmc(
263            "did:example:issuer".to_string(),
264            "did:example:subject".to_string(),
265            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
266                .unwrap()
267                .with_timezone(&Utc),
268            None,
269            false,
270        );
271
272        let txt = serde_json::to_string_pretty(&vmc).unwrap();
273        let sample = r#"{
274  "@context": [
275    "https://www.w3.org/ns/credentials/v2",
276    "https://firstperson.network/credentials/dtg/v1"
277  ],
278  "type": [
279    "VerifiableCredential",
280    "DTGCredential",
281    "MembershipCredential"
282  ],
283  "issuer": "did:example:issuer",
284  "validFrom": "2025-12-11T00:00:00Z",
285  "credentialSubject": {
286    "id": "did:example:subject"
287  }
288}"#;
289
290        assert_eq!(txt, sample);
291    }
292
293    #[test]
294    fn test_vmc_phc_serialization() {
295        let vmc = DTGCredential::new_vmc(
296            "did:example:issuer".to_string(),
297            "did:example:subject".to_string(),
298            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
299                .unwrap()
300                .with_timezone(&Utc),
301            None,
302            true,
303        );
304
305        let txt = serde_json::to_string_pretty(&vmc).unwrap();
306        let sample = r#"{
307  "@context": [
308    "https://www.w3.org/ns/credentials/v2",
309    "https://firstperson.network/credentials/dtg/v1"
310  ],
311  "type": [
312    "VerifiableCredential",
313    "DTGCredential",
314    "MembershipCredential",
315    "PersonhoodCredential"
316  ],
317  "issuer": "did:example:issuer",
318  "validFrom": "2025-12-11T00:00:00Z",
319  "credentialSubject": {
320    "id": "did:example:subject"
321  }
322}"#;
323
324        assert_eq!(txt, sample);
325    }
326    #[test]
327    fn test_vrc_serialization() {
328        let vrc = DTGCredential::new_vrc(
329            "did:example:issuer".to_string(),
330            "did:example:subject".to_string(),
331            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
332                .unwrap()
333                .with_timezone(&Utc),
334            None,
335        );
336
337        let txt = serde_json::to_string_pretty(&vrc).unwrap();
338        let sample = r#"{
339  "@context": [
340    "https://www.w3.org/ns/credentials/v2",
341    "https://firstperson.network/credentials/dtg/v1"
342  ],
343  "type": [
344    "VerifiableCredential",
345    "DTGCredential",
346    "RelationshipCredential"
347  ],
348  "issuer": "did:example:issuer",
349  "validFrom": "2025-12-11T00:00:00Z",
350  "credentialSubject": {
351    "id": "did:example:subject"
352  }
353}"#;
354
355        assert_eq!(txt, sample);
356    }
357
358    #[test]
359    fn test_vic_serialization() {
360        let vic = DTGCredential::new_vic(
361            "did:example:issuer".to_string(),
362            "did:example:subject".to_string(),
363            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
364                .unwrap()
365                .with_timezone(&Utc),
366            None,
367        );
368
369        let txt = serde_json::to_string_pretty(&vic).unwrap();
370        let sample = r#"{
371  "@context": [
372    "https://www.w3.org/ns/credentials/v2",
373    "https://firstperson.network/credentials/dtg/v1"
374  ],
375  "type": [
376    "VerifiableCredential",
377    "DTGCredential",
378    "InvitationCredential"
379  ],
380  "issuer": "did:example:issuer",
381  "validFrom": "2025-12-11T00:00:00Z",
382  "credentialSubject": {
383    "id": "did:example:subject"
384  }
385}"#;
386
387        assert_eq!(txt, sample);
388    }
389
390    #[test]
391    fn test_vpc_serialization() {
392        let vpc = DTGCredential::new_vpc(
393            "did:example:issuer".to_string(),
394            "did:example:subject".to_string(),
395            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
396                .unwrap()
397                .with_timezone(&Utc),
398            None,
399        );
400
401        let txt = serde_json::to_string_pretty(&vpc).unwrap();
402        let sample = r#"{
403  "@context": [
404    "https://www.w3.org/ns/credentials/v2",
405    "https://firstperson.network/credentials/dtg/v1"
406  ],
407  "type": [
408    "VerifiableCredential",
409    "DTGCredential",
410    "PersonaCredential"
411  ],
412  "issuer": "did:example:issuer",
413  "validFrom": "2025-12-11T00:00:00Z",
414  "credentialSubject": {
415    "id": "did:example:subject"
416  }
417}"#;
418
419        assert_eq!(txt, sample);
420    }
421
422    #[test]
423    fn test_vec_serialization() {
424        let vec = DTGCredential::new_vec(
425            "did:example:issuer".to_string(),
426            "did:example:subject".to_string(),
427            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
428                .unwrap()
429                .with_timezone(&Utc),
430            None,
431            json!({
432              "type": "SkillEndorsement",
433              "name": "Software Development",
434              "competencyLevel": "expert"
435            }),
436        );
437
438        let txt = serde_json::to_string_pretty(&vec).unwrap();
439        let sample = r#"{
440  "@context": [
441    "https://www.w3.org/ns/credentials/v2",
442    "https://firstperson.network/credentials/dtg/v1"
443  ],
444  "type": [
445    "VerifiableCredential",
446    "DTGCredential",
447    "EndorsementCredential"
448  ],
449  "issuer": "did:example:issuer",
450  "validFrom": "2025-12-11T00:00:00Z",
451  "credentialSubject": {
452    "id": "did:example:subject",
453    "endorsement": {
454      "competencyLevel": "expert",
455      "name": "Software Development",
456      "type": "SkillEndorsement"
457    }
458  }
459}"#;
460
461        assert_eq!(txt, sample);
462    }
463
464    #[test]
465    fn test_vwc_serialization() {
466        let vwc = DTGCredential::new_vwc(
467            "did:example:issuer".to_string(),
468            "did:example:subject".to_string(),
469            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
470                .unwrap()
471                .with_timezone(&Utc),
472            None,
473            "thread-abc-123".to_string(),
474            Some("zQmbGXRT3v1RmfWkQ7Y3Z5Uj9pKq2NcXhLd8sVtA4eB6nMw".to_string()),
475            Some(WitnessContext {
476                event: Some("EthDenver 2024".to_string()),
477                session_id: Some("session-8822-nonce".to_string()),
478                method: Some("in-person-proximity".to_string()),
479            }),
480        );
481
482        let txt = serde_json::to_string_pretty(&vwc).unwrap();
483
484        let sample = r#"{
485  "@context": [
486    "https://www.w3.org/ns/credentials/v2",
487    "https://firstperson.network/credentials/dtg/v1"
488  ],
489  "type": [
490    "VerifiableCredential",
491    "DTGCredential",
492    "WitnessCredential"
493  ],
494  "issuer": "did:example:issuer",
495  "validFrom": "2025-12-11T00:00:00Z",
496  "taskContext": "thread-abc-123",
497  "credentialSubject": {
498    "id": "did:example:subject",
499    "digest": "zQmbGXRT3v1RmfWkQ7Y3Z5Uj9pKq2NcXhLd8sVtA4eB6nMw",
500    "witnessContext": {
501      "event": "EthDenver 2024",
502      "sessionId": "session-8822-nonce",
503      "method": "in-person-proximity"
504    }
505  }
506}"#;
507
508        assert_eq!(txt, sample);
509    }
510
511    #[test]
512    fn test_rcard_serialization() {
513        let rcard = DTGCredential::new_rcard(
514            "did:example:issuer".to_string(),
515            "did:example:subject".to_string(),
516            DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
517                .unwrap()
518                .with_timezone(&Utc),
519            None,
520            json!([
521                "vcard",
522                [
523                    ["fn", {}, "text", "Alice Smith"],
524                    ["email", {}, "text", "alice@example.com"]
525                ]
526            ]),
527        );
528
529        let txt = serde_json::to_string_pretty(&rcard).unwrap();
530
531        let sample = r#"{
532  "@context": [
533    "https://www.w3.org/ns/credentials/v2",
534    "https://firstperson.network/credentials/dtg/v1"
535  ],
536  "type": [
537    "VerifiableCredential",
538    "DTGCredential",
539    "RCardCredential"
540  ],
541  "issuer": "did:example:issuer",
542  "validFrom": "2025-12-11T00:00:00Z",
543  "credentialSubject": {
544    "id": "did:example:subject",
545    "card": [
546      "vcard",
547      [
548        [
549          "fn",
550          {},
551          "text",
552          "Alice Smith"
553        ],
554        [
555          "email",
556          {},
557          "text",
558          "alice@example.com"
559        ]
560      ]
561    ]
562  }
563}"#;
564
565        assert_eq!(txt, sample);
566    }
567}