1use std::borrow::Cow;
14
15use utoipa::openapi::RefOr;
16use utoipa::openapi::schema::{
17 AdditionalProperties, ArrayBuilder, ArrayItems, ObjectBuilder, OneOfBuilder, Ref, RefBuilder,
18 Schema, SchemaFormat, SchemaType, Type,
19};
20use utoipa::{PartialSchema, ToSchema};
21
22use crate::did::{DidDocument, MethodRef, Service, VerificationMethod};
23use crate::jose::{Jwk, JwsAlgorithm};
24
25const DID_PATTERN: &str =
28 r"^did:[a-z0-9]+:(([A-Za-z0-9._-]|%[0-9A-Fa-f]{2})*:)*([A-Za-z0-9._-]|%[0-9A-Fa-f]{2})+$";
29
30type Schemas = Vec<(String, RefOr<Schema>)>;
31
32fn collect<T: ToSchema>(schemas: &mut Schemas) {
34 let name = T::name();
35 if schemas.iter().any(|(present, _)| *present == name) {
36 return;
37 }
38 schemas.push((name.into_owned(), T::schema()));
39 T::schemas(schemas);
40}
41
42fn schema_ref<T: ToSchema>() -> Ref {
43 Ref::from_schema_name(T::name().into_owned())
44}
45
46fn string(description: &str) -> ObjectBuilder {
47 ObjectBuilder::new()
48 .schema_type(Type::String)
49 .description(Some(description))
50}
51
52fn did(description: &str) -> ObjectBuilder {
53 string(description).pattern(Some(DID_PATTERN))
54}
55
56fn uri(description: &str) -> ObjectBuilder {
57 string(description).format(Some(SchemaFormat::Custom("uri".to_owned())))
58}
59
60fn any_object(description: &str) -> ObjectBuilder {
62 ObjectBuilder::new()
63 .description(Some(description))
64 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
65}
66
67fn array(items: impl Into<RefOr<Schema>>, description: &str) -> ArrayBuilder {
68 ArrayBuilder::new()
69 .items(ArrayItems::RefOrSchema(Box::new(items.into())))
70 .description(Some(description))
71}
72
73fn one_of<const N: usize>(items: [RefOr<Schema>; N], description: &str) -> OneOfBuilder {
74 items
75 .into_iter()
76 .fold(OneOfBuilder::new(), OneOfBuilder::item)
77 .description(Some(description))
78}
79
80fn string_or_strings(item: &str, description: &str) -> OneOfBuilder {
82 one_of(
83 [
84 string(item).into(),
85 array(string(item), "")
86 .min_items(Some(1))
87 .description(None::<String>)
88 .into(),
89 ],
90 description,
91 )
92}
93
94#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
99impl PartialSchema for DidDocument {
100 fn schema() -> RefOr<Schema> {
101 let relationship = |name: &str, section: &str| {
102 array(
103 schema_ref::<MethodRef>(),
104 &format!("The `{name}` relationship (DID Core §{section})."),
105 )
106 };
107 ObjectBuilder::new()
108 .description(Some(
109 "A DID document (DID Core §4): the core properties, plus any extension \
110 property.",
111 ))
112 .property(
113 "@context",
114 one_of(
115 [
116 uri("A context URI.").into(),
117 array(
118 ObjectBuilder::new().schema_type(SchemaType::AnyValue),
119 "Context URIs and objects.",
120 )
121 .min_items(Some(1))
122 .into(),
123 any_object("A context object.").into(),
124 ],
125 "The JSON-LD context, carried as data (DID Core §6.3.1).",
126 ),
127 )
128 .property("id", did("The DID of the subject (DID Core §5.1.1)."))
129 .required("id")
130 .property(
131 "alsoKnownAs",
132 array(
133 uri("A URI."),
134 "Other identifiers of the subject (DID Core §5.1.3).",
135 ),
136 )
137 .property(
138 "controller",
139 one_of(
140 [
141 did("The DID of the controller.").into(),
142 array(did("The DID of a controller."), "")
143 .description(None::<String>)
144 .into(),
145 ],
146 "The DIDs whose controllers may change this document (DID Core §5.1.2); \
147 an empty set means none.",
148 ),
149 )
150 .property(
151 "verificationMethod",
152 array(
153 schema_ref::<VerificationMethod>(),
154 "Verification methods the relationships may reference (DID Core §5.2).",
155 ),
156 )
157 .property("authentication", relationship("authentication", "5.3.1"))
158 .property("assertionMethod", relationship("assertionMethod", "5.3.2"))
159 .property("keyAgreement", relationship("keyAgreement", "5.3.3"))
160 .property(
161 "capabilityInvocation",
162 relationship("capabilityInvocation", "5.3.4"),
163 )
164 .property(
165 "capabilityDelegation",
166 relationship("capabilityDelegation", "5.3.5"),
167 )
168 .property(
169 "service",
170 array(
171 schema_ref::<Service>(),
172 "Ways of interacting with the subject (DID Core §5.4).",
173 ),
174 )
175 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
176 .into()
177 }
178}
179
180#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
181impl ToSchema for DidDocument {
182 fn name() -> Cow<'static, str> {
183 Cow::Borrowed("DidDocument")
184 }
185
186 fn schemas(schemas: &mut Schemas) {
187 collect::<VerificationMethod>(schemas);
188 collect::<MethodRef>(schemas);
189 collect::<Service>(schemas);
190 }
191}
192
193#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
194impl PartialSchema for VerificationMethod {
195 fn schema() -> RefOr<Schema> {
196 ObjectBuilder::new()
197 .description(Some(
198 "A verification method (DID Core §5.2): a public key, carried as a JWK or a \
199 multibase string, with its type and controller.",
200 ))
201 .property(
202 "id",
203 string("The DID URL of the method, usually the document's DID with a fragment."),
204 )
205 .required("id")
206 .property(
207 "type",
208 string(
209 "The verification method type, such as `JsonWebKey2020`, `JsonWebKey` or \
210 `Multikey`.",
211 ),
212 )
213 .required("type")
214 .property("controller", did("The DID of the controller of the key."))
215 .required("controller")
216 .property(
217 "publicKeyJwk",
218 RefBuilder::new()
219 .ref_location_from_schema_name(Jwk::name().into_owned())
220 .description(Some("The public key as a JWK (DID Core §5.2.1)."))
221 .build(),
222 )
223 .property(
224 "publicKeyMultibase",
225 string("The public key as a multibase string (DID Core §5.2.1)."),
226 )
227 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
228 .into()
229 }
230}
231
232#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
233impl ToSchema for VerificationMethod {
234 fn name() -> Cow<'static, str> {
235 Cow::Borrowed("VerificationMethod")
236 }
237
238 fn schemas(schemas: &mut Schemas) {
239 collect::<Jwk>(schemas);
240 }
241}
242
243#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
244impl PartialSchema for MethodRef {
245 fn schema() -> RefOr<Schema> {
246 one_of(
247 [
248 string("A DID URL, possibly relative, of a method under `verificationMethod`.")
249 .into(),
250 schema_ref::<VerificationMethod>().into(),
251 ],
252 "An entry of a verification relationship (DID Core §5.3): a reference to a \
253 verification method, or a method embedded in the relationship.",
254 )
255 .into()
256 }
257}
258
259#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
260impl ToSchema for MethodRef {
261 fn name() -> Cow<'static, str> {
262 Cow::Borrowed("MethodRef")
263 }
264
265 fn schemas(schemas: &mut Schemas) {
266 collect::<VerificationMethod>(schemas);
267 }
268}
269
270#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
271impl PartialSchema for Service {
272 fn schema() -> RefOr<Schema> {
273 ObjectBuilder::new()
274 .description(Some(
275 "A service (DID Core §5.4): a way of interacting with the DID subject.",
276 ))
277 .property(
278 "id",
279 string("The DID URL of the service, usually the document's DID with a fragment."),
280 )
281 .required("id")
282 .property(
283 "type",
284 string_or_strings("A service type.", "The service type or types."),
285 )
286 .required("type")
287 .property(
288 "serviceEndpoint",
289 one_of(
290 [
291 uri("The endpoint URI.").into(),
292 array(uri("An endpoint URI."), "")
293 .min_items(Some(1))
294 .description(None::<String>)
295 .into(),
296 any_object("An endpoint map.").into(),
297 ],
298 "Where the service is: a URI, a set of URIs, or a map.",
299 ),
300 )
301 .required("serviceEndpoint")
302 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
303 .into()
304 }
305}
306
307#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
308impl ToSchema for Service {
309 fn name() -> Cow<'static, str> {
310 Cow::Borrowed("Service")
311 }
312}
313
314#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
319impl PartialSchema for Jwk {
320 fn schema() -> RefOr<Schema> {
321 let base64url = |description: &str| string(description);
322 ObjectBuilder::new()
323 .description(Some(
324 "A JSON Web Key (RFC 7517 §4): the key type, the parameters of RFC 7518 §6 and \
325 RFC 8037 §2 for that type, and the common members. Parameters are \
326 base64url-encoded without padding.",
327 ))
328 .property(
329 "kty",
330 string("The key type (RFC 7517 §4.1): `EC`, `OKP`, `RSA` or `oct`."),
331 )
332 .required("kty")
333 .property(
334 "crv",
335 string("The curve of an `EC` or `OKP` key: `P-256`, `secp256k1`, `Ed25519`, ..."),
336 )
337 .property("x", base64url("The x coordinate of an `EC` key, or the `OKP` public key."))
338 .property("y", base64url("The y coordinate of an `EC` key."))
339 .property("n", base64url("The modulus of an `RSA` key."))
340 .property("e", base64url("The public exponent of an `RSA` key."))
341 .property(
342 "d",
343 base64url("The private key of an `EC` or `OKP` key, or the private exponent of an `RSA` key."),
344 )
345 .property("p", base64url("The first prime factor of an `RSA` private key."))
346 .property("q", base64url("The second prime factor of an `RSA` private key."))
347 .property("dp", base64url("The first factor CRT exponent of an `RSA` private key."))
348 .property("dq", base64url("The second factor CRT exponent of an `RSA` private key."))
349 .property("qi", base64url("The first CRT coefficient of an `RSA` private key."))
350 .property(
351 "oth",
352 array(
353 ObjectBuilder::new()
354 .property("r", base64url("The prime factor."))
355 .required("r")
356 .property("d", base64url("The factor CRT exponent."))
357 .required("d")
358 .property("t", base64url("The factor CRT coefficient."))
359 .required("t"),
360 "Other primes of an `RSA` private key (RFC 7518 §6.3.2.7).",
361 ),
362 )
363 .property("k", base64url("The value of an `oct` key."))
364 .property(
365 "use",
366 string("The intended use of the key (RFC 7517 §4.2): `sig` or `enc`."),
367 )
368 .property(
369 "key_ops",
370 array(
371 string("An operation, such as `sign` or `verify`."),
372 "The operations the key is intended for (RFC 7517 §4.3).",
373 ),
374 )
375 .property(
376 "alg",
377 string("The algorithm intended for use with the key (RFC 7517 §4.4)."),
378 )
379 .property("kid", string("The key id (RFC 7517 §4.5)."))
380 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
381 .into()
382 }
383}
384
385#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
386impl ToSchema for Jwk {
387 fn name() -> Cow<'static, str> {
388 Cow::Borrowed("Jwk")
389 }
390}
391
392#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
393impl PartialSchema for JwsAlgorithm {
394 fn schema() -> RefOr<Schema> {
395 string(
396 "A JWS algorithm (RFC 7518 §3.1, RFC 8037, RFC 8812): its registered name, among \
397 the asymmetric algorithms this crate speaks.",
398 )
399 .enum_values(Some(JwsAlgorithm::ALL.iter().map(|alg| alg.as_str())))
400 .into()
401 }
402}
403
404#[cfg_attr(docsrs, doc(cfg(feature = "utoipa")))]
405impl ToSchema for JwsAlgorithm {
406 fn name() -> Cow<'static, str> {
407 Cow::Borrowed("JwsAlgorithm")
408 }
409}
410
411#[cfg(feature = "domain-linkage")]
416#[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "domain-linkage"))))]
417impl PartialSchema for crate::domain_linkage::DomainLinkageConfiguration {
418 fn schema() -> RefOr<Schema> {
419 ObjectBuilder::new()
420 .description(Some(
421 "A DID configuration resource (Well Known DID Configuration §3.1): the context \
422 and the domain linkage credentials, one per linked DID.",
423 ))
424 .property(
425 "@context",
426 string("The DID configuration context.")
427 .enum_values(Some([crate::domain_linkage::CONTEXT])),
428 )
429 .required("@context")
430 .property(
431 "linked_dids",
432 array(
433 one_of(
434 [
435 string(
436 "A domain linkage credential in the JSON Web Token proof \
437 format: a compact JWS.",
438 )
439 .into(),
440 any_object(
441 "A domain linkage credential in the Linked Data Proof format.",
442 )
443 .into(),
444 ],
445 "A domain linkage credential.",
446 ),
447 "The domain linkage credentials.",
448 ),
449 )
450 .required("linked_dids")
451 .additional_properties(Some(AdditionalProperties::FreeForm(false)))
452 .into()
453 }
454}
455
456#[cfg(feature = "domain-linkage")]
457#[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "domain-linkage"))))]
458impl ToSchema for crate::domain_linkage::DomainLinkageConfiguration {
459 fn name() -> Cow<'static, str> {
460 Cow::Borrowed("DomainLinkageConfiguration")
461 }
462}
463
464#[cfg(feature = "vc")]
469mod credentials {
470 use super::{
471 AdditionalProperties, ArrayBuilder, Cow, ObjectBuilder, OneOfBuilder, PartialSchema, RefOr,
472 Schema, SchemaFormat, SchemaType, Schemas, ToSchema, array, collect, one_of, schema_ref,
473 string, string_or_strings, uri,
474 };
475 use crate::vc::{
476 CONTEXT_V1, CONTEXT_V2, Credential, CredentialV2, EnvelopedVc, EnvelopedVp, Presentation,
477 PresentationV2,
478 };
479
480 const JWS_PATTERN: &str = r"^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*$";
482
483 fn context(base: &'static str, section: &str) -> OneOfBuilder {
486 one_of(
487 [
488 string("The base context alone.")
489 .enum_values(Some([base]))
490 .into(),
491 ArrayBuilder::new()
492 .prefix_items([Schema::from(
493 string("The base context.").enum_values(Some([base])),
494 )])
495 .items(ObjectBuilder::new().schema_type(SchemaType::AnyValue))
496 .min_items(Some(1))
497 .description(Some("The base context first, then further contexts."))
498 .into(),
499 ],
500 &format!("The JSON-LD context, carried as data ({section}); it begins with `{base}`."),
501 )
502 }
503
504 fn types(required: &str, section: &str) -> OneOfBuilder {
506 string_or_strings(
507 "A type.",
508 &format!("The types ({section}), including `{required}`."),
509 )
510 }
511
512 fn date_time(description: &str) -> ObjectBuilder {
513 string(description).format(Some(SchemaFormat::KnownFormat(
514 utoipa::openapi::KnownFormat::DateTime,
515 )))
516 }
517
518 fn party(description: &str) -> OneOfBuilder {
520 one_of(
521 [
522 uri("The identifier.").into(),
523 ObjectBuilder::new()
524 .property("id", uri("The identifier."))
525 .required("id")
526 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
527 .description(Some("The identifier with further properties."))
528 .into(),
529 ],
530 description,
531 )
532 }
533
534 fn subject() -> ObjectBuilder {
535 ObjectBuilder::new()
536 .property("id", uri("The identifier of the subject."))
537 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
538 .description(Some("A subject and the claims about it."))
539 }
540
541 fn subjects(section: &str) -> OneOfBuilder {
543 one_of(
544 [
545 subject().into(),
546 array(subject(), "")
547 .min_items(Some(1))
548 .description(None::<String>)
549 .into(),
550 ],
551 &format!("The subjects and the claims about them ({section})."),
552 )
553 }
554
555 fn typed(description: &str) -> ObjectBuilder {
557 ObjectBuilder::new()
558 .property("id", uri("The identifier."))
559 .property("type", string_or_strings("A type.", "The types."))
560 .required("type")
561 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
562 .description(Some(description))
563 }
564
565 fn typed_one_or_many(item: &str, description: &str) -> OneOfBuilder {
567 one_of(
568 [
569 typed(item).into(),
570 array(typed(item), "").description(None::<String>).into(),
571 ],
572 description,
573 )
574 }
575
576 fn language_value(description: &str) -> ObjectBuilder {
579 ObjectBuilder::new()
580 .schema_type(SchemaType::AnyValue)
581 .description(Some(description))
582 }
583
584 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
585 impl PartialSchema for Credential {
586 fn schema() -> RefOr<Schema> {
587 ObjectBuilder::new()
588 .description(Some(
589 "A verifiable credential of the VC Data Model 1.1 (§4), as the `vc` claim \
590 of a JWT carries it once `iss`, `jti`, `nbf`, `exp` and `sub` are mapped \
591 back.",
592 ))
593 .property("@context", context(CONTEXT_V1, "VCDM 1.1 §4.1"))
594 .required("@context")
595 .property("id", uri("The identifier of the credential (VCDM 1.1 §4.2)."))
596 .property("type", types(Credential::TYPE, "VCDM 1.1 §4.3"))
597 .required("type")
598 .property("issuer", party("The issuer (VCDM 1.1 §4.5)."))
599 .required("issuer")
600 .property(
601 "issuanceDate",
602 date_time("When the credential becomes valid (VCDM 1.1 §4.6)."),
603 )
604 .required("issuanceDate")
605 .property(
606 "expirationDate",
607 date_time("When the credential ceases to be valid (VCDM 1.1 §4.8)."),
608 )
609 .property("credentialSubject", subjects("VCDM 1.1 §4.4"))
610 .required("credentialSubject")
611 .property(
612 "credentialStatus",
613 typed("Where to learn whether the credential is revoked or suspended (VCDM 1.1 §4.9)."),
614 )
615 .property(
616 "credentialSchema",
617 typed_one_or_many("A data schema.", "Data schemas (VCDM 1.1 §5.4)."),
618 )
619 .property(
620 "refreshService",
621 typed_one_or_many("A refresh service.", "Refresh services (VCDM 1.1 §5.6)."),
622 )
623 .property(
624 "termsOfUse",
625 typed_one_or_many("Terms of use.", "Terms of use (VCDM 1.1 §5.7)."),
626 )
627 .property(
628 "evidence",
629 typed_one_or_many("Evidence.", "Evidence (VCDM 1.1 §5.8)."),
630 )
631 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
632 .into()
633 }
634 }
635
636 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
637 impl ToSchema for Credential {
638 fn name() -> Cow<'static, str> {
639 Cow::Borrowed("Credential")
640 }
641 }
642
643 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
644 impl PartialSchema for Presentation {
645 fn schema() -> RefOr<Schema> {
646 ObjectBuilder::new()
647 .description(Some(
648 "A verifiable presentation of the VC Data Model 1.1 (§4.10), as the `vp` \
649 claim of a JWT carries it once `iss` and `jti` are mapped back.",
650 ))
651 .property("@context", context(CONTEXT_V1, "VCDM 1.1 §4.1"))
652 .required("@context")
653 .property(
654 "id",
655 uri("The identifier of the presentation (VCDM 1.1 §4.2)."),
656 )
657 .property("type", types(Presentation::TYPE, "VCDM 1.1 §4.3"))
658 .required("type")
659 .property(
660 "verifiableCredential",
661 array(
662 string("A credential JWT: a compact JWS.").pattern(Some(JWS_PATTERN)),
663 "The presented credentials, as JWTs (VCDM 1.1 §6.3.1).",
664 ),
665 )
666 .property("holder", party("The holder presenting the credentials."))
667 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
668 .into()
669 }
670 }
671
672 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
673 impl ToSchema for Presentation {
674 fn name() -> Cow<'static, str> {
675 Cow::Borrowed("Presentation")
676 }
677 }
678
679 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
680 impl PartialSchema for CredentialV2 {
681 fn schema() -> RefOr<Schema> {
682 ObjectBuilder::new()
683 .description(Some(
684 "A verifiable credential of the VC Data Model 2.0 (§4), the payload of a \
685 `vc+jwt` (VC-JOSE-COSE §3.1.1).",
686 ))
687 .property("@context", context(CONTEXT_V2, "VCDM 2.0 §4.3"))
688 .required("@context")
689 .property("id", uri("The identifier of the credential (VCDM 2.0 §4.4)."))
690 .property("type", types(CredentialV2::TYPE, "VCDM 2.0 §4.5"))
691 .required("type")
692 .property(
693 "name",
694 language_value("A name for the credential (VCDM 2.0 §4.6)."),
695 )
696 .property(
697 "description",
698 language_value("A description of the credential (VCDM 2.0 §4.6)."),
699 )
700 .property("issuer", party("The issuer (VCDM 2.0 §4.7)."))
701 .required("issuer")
702 .property(
703 "validFrom",
704 date_time("When the credential becomes valid (VCDM 2.0 §4.9)."),
705 )
706 .property(
707 "validUntil",
708 date_time("When the credential ceases to be valid (VCDM 2.0 §4.9)."),
709 )
710 .property(
711 "credentialStatus",
712 typed_one_or_many(
713 "A status entry.",
714 "Where to learn whether the credential is revoked or suspended (VCDM 2.0 §4.10).",
715 ),
716 )
717 .property("credentialSubject", subjects("VCDM 2.0 §4.8"))
718 .required("credentialSubject")
719 .property(
720 "credentialSchema",
721 typed_one_or_many("A data schema.", "Data schemas (VCDM 2.0 §4.11)."),
722 )
723 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
724 .into()
725 }
726 }
727
728 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
729 impl ToSchema for CredentialV2 {
730 fn name() -> Cow<'static, str> {
731 Cow::Borrowed("CredentialV2")
732 }
733 }
734
735 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
736 impl PartialSchema for PresentationV2 {
737 fn schema() -> RefOr<Schema> {
738 ObjectBuilder::new()
739 .description(Some(
740 "A verifiable presentation of the VC Data Model 2.0 (§4.13), the payload of \
741 a `vp+jwt` (VC-JOSE-COSE §3.1.2), carrying enveloped credentials.",
742 ))
743 .property("@context", context(CONTEXT_V2, "VCDM 2.0 §4.3"))
744 .required("@context")
745 .property(
746 "id",
747 uri("The identifier of the presentation (VCDM 2.0 §4.4)."),
748 )
749 .property("type", types(PresentationV2::TYPE, "VCDM 2.0 §4.5"))
750 .required("type")
751 .property("holder", party("The holder presenting the credentials."))
752 .property(
753 "verifiableCredential",
754 array(
755 schema_ref::<EnvelopedVc>(),
756 "The presented credentials, enveloped (VCDM 2.0 §4.13).",
757 ),
758 )
759 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
760 .into()
761 }
762 }
763
764 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
765 impl ToSchema for PresentationV2 {
766 fn name() -> Cow<'static, str> {
767 Cow::Borrowed("PresentationV2")
768 }
769
770 fn schemas(schemas: &mut Schemas) {
771 collect::<EnvelopedVc>(schemas);
772 }
773 }
774
775 fn enveloped(object_type: &str, media_type: &str, section: &str) -> RefOr<Schema> {
777 ObjectBuilder::new()
778 .description(Some(format!(
779 "An `{object_type}` (VCDM 2.0 {section}): a secured object carried in a `data:` \
780 URL, `{media_type}` for a JWT."
781 )))
782 .property("@context", context(CONTEXT_V2, "VCDM 2.0 §4.3"))
783 .required("@context")
784 .property(
785 "id",
786 uri("The `data:` URL holding the secured form (RFC 2397).").pattern(Some("^data:")),
787 )
788 .required("id")
789 .property("type", types(object_type, "VCDM 2.0 §4.5"))
790 .required("type")
791 .into()
792 }
793
794 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
795 impl PartialSchema for EnvelopedVc {
796 fn schema() -> RefOr<Schema> {
797 enveloped(EnvelopedVc::TYPE, EnvelopedVc::JWT_MEDIA_TYPE, "§4.12.1")
798 }
799 }
800
801 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
802 impl ToSchema for EnvelopedVc {
803 fn name() -> Cow<'static, str> {
804 Cow::Borrowed("EnvelopedVc")
805 }
806 }
807
808 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
809 impl PartialSchema for EnvelopedVp {
810 fn schema() -> RefOr<Schema> {
811 enveloped(EnvelopedVp::TYPE, EnvelopedVp::JWT_MEDIA_TYPE, "§4.13.1")
812 }
813 }
814
815 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
816 impl ToSchema for EnvelopedVp {
817 fn name() -> Cow<'static, str> {
818 Cow::Borrowed("EnvelopedVp")
819 }
820 }
821}
822
823#[cfg(feature = "vc")]
828mod claim_path {
829 use super::{
830 Cow, ObjectBuilder, PartialSchema, RefOr, Schema, ToSchema, Type, array, one_of, string,
831 };
832 use crate::vc::ClaimPath;
833
834 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
835 impl PartialSchema for ClaimPath {
836 fn schema() -> RefOr<Schema> {
837 array(
838 one_of(
839 [
840 string("An object key.").into(),
841 ObjectBuilder::new()
842 .schema_type(Type::Integer)
843 .minimum(Some(0))
844 .description(Some("An array index."))
845 .into(),
846 ObjectBuilder::new()
847 .schema_type(Type::Null)
848 .description(Some("Every element of an array."))
849 .into(),
850 ],
851 "A path component.",
852 ),
853 "A claim path (SD-JWT VC §5.6.1), the claims path pointer of OID4VP §7: where a \
854 claim sits in a JSON credential.",
855 )
856 .min_items(Some(1))
857 .into()
858 }
859 }
860
861 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "vc"))))]
862 impl ToSchema for ClaimPath {
863 fn name() -> Cow<'static, str> {
864 Cow::Borrowed("ClaimPath")
865 }
866 }
867}
868
869#[cfg(feature = "sd-jwt")]
874mod type_metadata {
875 use super::{
876 AdditionalProperties, Cow, ObjectBuilder, PartialSchema, RefBuilder, RefOr, Schema,
877 Schemas, ToSchema, Type, any_object, array, collect, schema_ref, string, uri,
878 };
879 use crate::sd_jwt_vc::{ClaimDisplay, ClaimMetadata, DisplayMetadata, TypeMetadata};
880 use crate::vc::ClaimPath;
881
882 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
883 impl PartialSchema for TypeMetadata {
884 fn schema() -> RefOr<Schema> {
885 ObjectBuilder::new()
886 .description(Some(
887 "SD-JWT VC type metadata (SD-JWT VC §5): what a `vct` means, how to \
888 display it, and which claims it carries.",
889 ))
890 .property(
891 "vct",
892 string("The type described: a case-sensitive collision-resistant name."),
893 )
894 .required("vct")
895 .property("name", string("A name for developers."))
896 .property("description", string("A description for developers."))
897 .property("extends", uri("The type this one extends."))
898 .property(
899 "extends#integrity",
900 string(
901 "The integrity metadata of the extended type's document (SD-JWT VC §6).",
902 ),
903 )
904 .property(
905 "display",
906 array(
907 schema_ref::<DisplayMetadata>(),
908 "Display information per locale (SD-JWT VC §5.5).",
909 ),
910 )
911 .property(
912 "claims",
913 array(
914 schema_ref::<ClaimMetadata>(),
915 "Claim metadata (SD-JWT VC §5.6).",
916 ),
917 )
918 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
919 .into()
920 }
921 }
922
923 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
924 impl ToSchema for TypeMetadata {
925 fn name() -> Cow<'static, str> {
926 Cow::Borrowed("TypeMetadata")
927 }
928
929 fn schemas(schemas: &mut Schemas) {
930 collect::<DisplayMetadata>(schemas);
931 collect::<ClaimMetadata>(schemas);
932 }
933 }
934
935 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
936 impl PartialSchema for DisplayMetadata {
937 fn schema() -> RefOr<Schema> {
938 ObjectBuilder::new()
939 .description(Some(
940 "Display information for a type in one locale (SD-JWT VC §5.5).",
941 ))
942 .property("locale", string("The language tag (RFC 5646)."))
943 .required("locale")
944 .property("name", string("The name of the type for end users."))
945 .required("name")
946 .property("description", string("A description for end users."))
947 .property(
948 "rendering",
949 any_object("Rendering information: `simple`, `svg_templates`."),
950 )
951 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
952 .into()
953 }
954 }
955
956 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
957 impl ToSchema for DisplayMetadata {
958 fn name() -> Cow<'static, str> {
959 Cow::Borrowed("DisplayMetadata")
960 }
961 }
962
963 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
964 impl PartialSchema for ClaimMetadata {
965 fn schema() -> RefOr<Schema> {
966 ObjectBuilder::new()
967 .description(Some(
968 "Metadata for the claim or claims at a path (SD-JWT VC §5.6).",
969 ))
970 .property(
971 "path",
972 RefBuilder::new()
973 .ref_location_from_schema_name(ClaimPath::name())
974 .description(Some("The claim path (SD-JWT VC §5.6.1).")),
975 )
976 .required("path")
977 .property(
978 "display",
979 array(
980 schema_ref::<ClaimDisplay>(),
981 "Display information per locale (SD-JWT VC §5.6.2).",
982 ),
983 )
984 .property(
985 "mandatory",
986 ObjectBuilder::new()
987 .schema_type(Type::Boolean)
988 .description(Some(
989 "Whether the issuer must include the claim; default false.",
990 )),
991 )
992 .property(
993 "sd",
994 string(
995 "Whether the claim is selectively disclosable (SD-JWT VC §5.6.4); \
996 default `allowed`.",
997 )
998 .enum_values(Some(["always", "allowed", "never"])),
999 )
1000 .property("svg_id", string("The id of the claim in SVG templates."))
1001 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
1002 .into()
1003 }
1004 }
1005
1006 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
1007 impl ToSchema for ClaimMetadata {
1008 fn name() -> Cow<'static, str> {
1009 Cow::Borrowed("ClaimMetadata")
1010 }
1011
1012 fn schemas(schemas: &mut Schemas) {
1013 collect::<ClaimDisplay>(schemas);
1014 collect::<ClaimPath>(schemas);
1015 }
1016 }
1017
1018 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
1019 impl PartialSchema for ClaimDisplay {
1020 fn schema() -> RefOr<Schema> {
1021 ObjectBuilder::new()
1022 .description(Some(
1023 "Display information for a claim in one locale (SD-JWT VC §5.6.2).",
1024 ))
1025 .property("locale", string("The language tag (RFC 5646)."))
1026 .required("locale")
1027 .property("label", string("The label of the claim for end users."))
1028 .required("label")
1029 .property("description", string("A description for end users."))
1030 .additional_properties(Some(AdditionalProperties::FreeForm(true)))
1031 .into()
1032 }
1033 }
1034
1035 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "sd-jwt"))))]
1036 impl ToSchema for ClaimDisplay {
1037 fn name() -> Cow<'static, str> {
1038 Cow::Borrowed("ClaimDisplay")
1039 }
1040 }
1041}
1042
1043#[cfg(feature = "status-list")]
1048mod status {
1049 use super::{Cow, ObjectBuilder, PartialSchema, RefOr, Schema, ToSchema, Type, one_of, string};
1050 use crate::status_list::StatusType;
1051
1052 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "status-list"))))]
1053 impl PartialSchema for StatusType {
1054 fn schema() -> RefOr<Schema> {
1055 one_of(
1056 [
1057 string("A status type with a registered name of its own.")
1058 .enum_values(Some(["VALID", "INVALID", "SUSPENDED"]))
1059 .into(),
1060 ObjectBuilder::new()
1061 .schema_type(Type::Integer)
1062 .minimum(Some(0))
1063 .maximum(Some(255))
1064 .description(Some("Any other status type value."))
1065 .into(),
1066 ],
1067 "A status type (Token Status List §7): its registered name, or its value.",
1068 )
1069 .into()
1070 }
1071 }
1072
1073 #[cfg_attr(docsrs, doc(cfg(all(feature = "utoipa", feature = "status-list"))))]
1074 impl ToSchema for StatusType {
1075 fn name() -> Cow<'static, str> {
1076 Cow::Borrowed("StatusType")
1077 }
1078 }
1079}