1#[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 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 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 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 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 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 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 #[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 pub fn with_id(mut self, id: impl Into<String>) -> Self {
278 self.credential.id = Some(id.into());
279 self
280 }
281
282 pub fn set_id(&mut self, id: impl Into<String>) {
287 self.credential.id = Some(id.into());
288 }
289}
290
291#[cfg(test)]
292#[allow(deprecated)]
293mod tests {
294 use crate::{DTGCredential, WitnessContext};
295 use chrono::{DateTime, Utc};
296 use serde_json::json;
297
298 #[test]
299 fn test_vmc_serialization() {
300 let vmc = DTGCredential::new_vmc(
301 "did:example:issuer".to_string(),
302 "did:example:subject".to_string(),
303 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
304 .unwrap()
305 .with_timezone(&Utc),
306 None,
307 false,
308 );
309
310 let txt = serde_json::to_string_pretty(&vmc).unwrap();
311 let sample = r#"{
312 "@context": [
313 "https://www.w3.org/ns/credentials/v2",
314 "https://firstperson.network/credentials/dtg/v1"
315 ],
316 "type": [
317 "VerifiableCredential",
318 "DTGCredential",
319 "MembershipCredential"
320 ],
321 "issuer": "did:example:issuer",
322 "validFrom": "2025-12-11T00:00:00Z",
323 "credentialSubject": {
324 "id": "did:example:subject"
325 }
326}"#;
327
328 assert_eq!(txt, sample);
329 }
330
331 #[test]
332 fn test_vmc_phc_serialization() {
333 let vmc = DTGCredential::new_vmc(
334 "did:example:issuer".to_string(),
335 "did:example:subject".to_string(),
336 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
337 .unwrap()
338 .with_timezone(&Utc),
339 None,
340 true,
341 );
342
343 let txt = serde_json::to_string_pretty(&vmc).unwrap();
344 let sample = r#"{
345 "@context": [
346 "https://www.w3.org/ns/credentials/v2",
347 "https://firstperson.network/credentials/dtg/v1"
348 ],
349 "type": [
350 "VerifiableCredential",
351 "DTGCredential",
352 "MembershipCredential",
353 "PersonhoodCredential"
354 ],
355 "issuer": "did:example:issuer",
356 "validFrom": "2025-12-11T00:00:00Z",
357 "credentialSubject": {
358 "id": "did:example:subject"
359 }
360}"#;
361
362 assert_eq!(txt, sample);
363 }
364 #[test]
367 fn test_vmc_without_id_omits_the_property() {
368 let vmc = DTGCredential::new_vmc(
369 "did:example:issuer".to_string(),
370 "did:example:subject".to_string(),
371 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
372 .unwrap()
373 .with_timezone(&Utc),
374 None,
375 false,
376 );
377
378 assert_eq!(vmc.id(), None);
379 let value: serde_json::Value = serde_json::to_value(&vmc).unwrap();
380 assert!(
381 value.get("id").is_none(),
382 "an unset id must not appear on the wire at all: {value}"
383 );
384 }
385
386 #[test]
390 fn test_vmc_with_id_serialization() {
391 let vmc = DTGCredential::new_vmc(
392 "did:example:issuer".to_string(),
393 "did:example:subject".to_string(),
394 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
395 .unwrap()
396 .with_timezone(&Utc),
397 None,
398 false,
399 )
400 .with_id("urn:uuid:1e2d3c4b-5a69-4788-9099-aabbccddeeff");
401
402 let txt = serde_json::to_string_pretty(&vmc).unwrap();
403 let sample = r#"{
404 "@context": [
405 "https://www.w3.org/ns/credentials/v2",
406 "https://firstperson.network/credentials/dtg/v1"
407 ],
408 "type": [
409 "VerifiableCredential",
410 "DTGCredential",
411 "MembershipCredential"
412 ],
413 "id": "urn:uuid:1e2d3c4b-5a69-4788-9099-aabbccddeeff",
414 "issuer": "did:example:issuer",
415 "validFrom": "2025-12-11T00:00:00Z",
416 "credentialSubject": {
417 "id": "did:example:subject"
418 }
419}"#;
420
421 assert_eq!(txt, sample);
422 }
423
424 #[test]
428 fn test_id_round_trips_through_deserialization() {
429 let vmc = DTGCredential::new_vmc(
430 "did:example:issuer".to_string(),
431 "did:example:subject".to_string(),
432 Utc::now(),
433 None,
434 false,
435 )
436 .with_id("urn:uuid:1e2d3c4b-5a69-4788-9099-aabbccddeeff");
437
438 let txt = serde_json::to_string(&vmc).unwrap();
439 let parsed: DTGCredential = serde_json::from_str(&txt).unwrap();
440 assert_eq!(
441 parsed.id(),
442 Some("urn:uuid:1e2d3c4b-5a69-4788-9099-aabbccddeeff")
443 );
444 }
445
446 #[test]
449 fn test_missing_id_deserializes_as_none() {
450 let parsed: DTGCredential = serde_json::from_str(
451 r#"{
452 "@context": ["https://www.w3.org/ns/credentials/v2"],
453 "type": ["VerifiableCredential", "DTGCredential", "MembershipCredential"],
454 "issuer": "did:example:issuer",
455 "validFrom": "2025-12-11T00:00:00Z",
456 "credentialSubject": { "id": "did:example:subject" }
457 }"#,
458 )
459 .unwrap();
460 assert_eq!(parsed.id(), None);
461 }
462
463 #[test]
465 fn test_set_id_matches_with_id() {
466 let build = || {
467 DTGCredential::new_vrc(
468 "did:example:issuer".to_string(),
469 "did:example:subject".to_string(),
470 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
471 .unwrap()
472 .with_timezone(&Utc),
473 None,
474 )
475 };
476 let mut in_place = build();
477 in_place.set_id("urn:uuid:abc");
478 assert_eq!(
479 serde_json::to_value(&in_place).unwrap(),
480 serde_json::to_value(build().with_id("urn:uuid:abc")).unwrap()
481 );
482 }
483
484 #[test]
485 fn test_vrc_serialization() {
486 let vrc = DTGCredential::new_vrc(
487 "did:example:issuer".to_string(),
488 "did:example:subject".to_string(),
489 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
490 .unwrap()
491 .with_timezone(&Utc),
492 None,
493 );
494
495 let txt = serde_json::to_string_pretty(&vrc).unwrap();
496 let sample = r#"{
497 "@context": [
498 "https://www.w3.org/ns/credentials/v2",
499 "https://firstperson.network/credentials/dtg/v1"
500 ],
501 "type": [
502 "VerifiableCredential",
503 "DTGCredential",
504 "RelationshipCredential"
505 ],
506 "issuer": "did:example:issuer",
507 "validFrom": "2025-12-11T00:00:00Z",
508 "credentialSubject": {
509 "id": "did:example:subject"
510 }
511}"#;
512
513 assert_eq!(txt, sample);
514 }
515
516 #[test]
517 fn test_vic_serialization() {
518 let vic = DTGCredential::new_vic(
519 "did:example:issuer".to_string(),
520 "did:example:subject".to_string(),
521 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
522 .unwrap()
523 .with_timezone(&Utc),
524 None,
525 );
526
527 let txt = serde_json::to_string_pretty(&vic).unwrap();
528 let sample = r#"{
529 "@context": [
530 "https://www.w3.org/ns/credentials/v2",
531 "https://firstperson.network/credentials/dtg/v1"
532 ],
533 "type": [
534 "VerifiableCredential",
535 "DTGCredential",
536 "InvitationCredential"
537 ],
538 "issuer": "did:example:issuer",
539 "validFrom": "2025-12-11T00:00:00Z",
540 "credentialSubject": {
541 "id": "did:example:subject"
542 }
543}"#;
544
545 assert_eq!(txt, sample);
546 }
547
548 #[test]
549 fn test_vpc_serialization() {
550 let vpc = DTGCredential::new_vpc(
551 "did:example:issuer".to_string(),
552 "did:example:subject".to_string(),
553 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
554 .unwrap()
555 .with_timezone(&Utc),
556 None,
557 );
558
559 let txt = serde_json::to_string_pretty(&vpc).unwrap();
560 let sample = r#"{
561 "@context": [
562 "https://www.w3.org/ns/credentials/v2",
563 "https://firstperson.network/credentials/dtg/v1"
564 ],
565 "type": [
566 "VerifiableCredential",
567 "DTGCredential",
568 "PersonaCredential"
569 ],
570 "issuer": "did:example:issuer",
571 "validFrom": "2025-12-11T00:00:00Z",
572 "credentialSubject": {
573 "id": "did:example:subject"
574 }
575}"#;
576
577 assert_eq!(txt, sample);
578 }
579
580 #[test]
581 fn test_vec_serialization() {
582 let vec = DTGCredential::new_vec(
583 "did:example:issuer".to_string(),
584 "did:example:subject".to_string(),
585 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
586 .unwrap()
587 .with_timezone(&Utc),
588 None,
589 json!({
590 "type": "SkillEndorsement",
591 "name": "Software Development",
592 "competencyLevel": "expert"
593 }),
594 );
595
596 let txt = serde_json::to_string_pretty(&vec).unwrap();
597 let sample = r#"{
598 "@context": [
599 "https://www.w3.org/ns/credentials/v2",
600 "https://firstperson.network/credentials/dtg/v1"
601 ],
602 "type": [
603 "VerifiableCredential",
604 "DTGCredential",
605 "EndorsementCredential"
606 ],
607 "issuer": "did:example:issuer",
608 "validFrom": "2025-12-11T00:00:00Z",
609 "credentialSubject": {
610 "id": "did:example:subject",
611 "endorsement": {
612 "competencyLevel": "expert",
613 "name": "Software Development",
614 "type": "SkillEndorsement"
615 }
616 }
617}"#;
618
619 assert_eq!(txt, sample);
620 }
621
622 #[test]
623 fn test_vwc_serialization() {
624 let vwc = DTGCredential::new_vwc(
625 "did:example:issuer".to_string(),
626 "did:example:subject".to_string(),
627 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
628 .unwrap()
629 .with_timezone(&Utc),
630 None,
631 "thread-abc-123".to_string(),
632 Some("zQmbGXRT3v1RmfWkQ7Y3Z5Uj9pKq2NcXhLd8sVtA4eB6nMw".to_string()),
633 Some(WitnessContext {
634 event: Some("EthDenver 2024".to_string()),
635 session_id: Some("session-8822-nonce".to_string()),
636 method: Some("in-person-proximity".to_string()),
637 }),
638 );
639
640 let txt = serde_json::to_string_pretty(&vwc).unwrap();
641
642 let sample = r#"{
643 "@context": [
644 "https://www.w3.org/ns/credentials/v2",
645 "https://firstperson.network/credentials/dtg/v1"
646 ],
647 "type": [
648 "VerifiableCredential",
649 "DTGCredential",
650 "WitnessCredential"
651 ],
652 "issuer": "did:example:issuer",
653 "validFrom": "2025-12-11T00:00:00Z",
654 "taskContext": "thread-abc-123",
655 "credentialSubject": {
656 "id": "did:example:subject",
657 "digest": "zQmbGXRT3v1RmfWkQ7Y3Z5Uj9pKq2NcXhLd8sVtA4eB6nMw",
658 "witnessContext": {
659 "event": "EthDenver 2024",
660 "sessionId": "session-8822-nonce",
661 "method": "in-person-proximity"
662 }
663 }
664}"#;
665
666 assert_eq!(txt, sample);
667 }
668
669 #[test]
670 fn test_rcard_serialization() {
671 let rcard = DTGCredential::new_rcard(
672 "did:example:issuer".to_string(),
673 "did:example:subject".to_string(),
674 DateTime::parse_from_rfc3339("2025-12-11T00:00:00Z")
675 .unwrap()
676 .with_timezone(&Utc),
677 None,
678 json!([
679 "vcard",
680 [
681 ["fn", {}, "text", "Alice Smith"],
682 ["email", {}, "text", "alice@example.com"]
683 ]
684 ]),
685 );
686
687 let txt = serde_json::to_string_pretty(&rcard).unwrap();
688
689 let sample = r#"{
690 "@context": [
691 "https://www.w3.org/ns/credentials/v2",
692 "https://firstperson.network/credentials/dtg/v1"
693 ],
694 "type": [
695 "VerifiableCredential",
696 "DTGCredential",
697 "RCardCredential"
698 ],
699 "issuer": "did:example:issuer",
700 "validFrom": "2025-12-11T00:00:00Z",
701 "credentialSubject": {
702 "id": "did:example:subject",
703 "card": [
704 "vcard",
705 [
706 [
707 "fn",
708 {},
709 "text",
710 "Alice Smith"
711 ],
712 [
713 "email",
714 {},
715 "text",
716 "alice@example.com"
717 ]
718 ]
719 ]
720 }
721}"#;
722
723 assert_eq!(txt, sample);
724 }
725}