Skip to main content

dtg_credentials/
create.rs

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