From 4917d99a30511b8c0a1d795e8f418e60cc5f0001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Haudebourg?= Date: Thu, 11 Jul 2024 06:27:25 -0300 Subject: [PATCH] Fully specializable credentials. (#577) --- .../crates/vc/src/v1/syntax/credential.rs | 272 +++++++++++++++--- .../crates/vc/src/v2/syntax/credential.rs | 272 +++++++++++++++--- 2 files changed, 466 insertions(+), 78 deletions(-) diff --git a/crates/claims/crates/vc/src/v1/syntax/credential.rs b/crates/claims/crates/vc/src/v1/syntax/credential.rs index d8e2ce4bd..9dcb6e5ac 100644 --- a/crates/claims/crates/vc/src/v1/syntax/credential.rs +++ b/crates/claims/crates/vc/src/v1/syntax/credential.rs @@ -8,10 +8,13 @@ use ssi_rdf::{Interpretation, LdEnvironment}; use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; use xsd_types::DateTime; -use crate::syntax::{ - not_null, value_or_array, IdOr, IdentifiedObject, IdentifiedTypedObject, - MaybeIdentifiedTypedObject, RequiredContextList, RequiredType, RequiredTypeSet, - TypeSerializationPolicy, Types, +use crate::{ + syntax::{ + not_null, value_or_array, IdOr, IdentifiedObject, IdentifiedTypedObject, + MaybeIdentifiedTypedObject, RequiredContextList, RequiredType, RequiredTypeSet, + TypeSerializationPolicy, Types, + }, + Identified, MaybeIdentified, Typed, }; use super::Context; @@ -32,23 +35,35 @@ pub type JsonCredentialTypes = Types; /// JSON Credential, without required context nor type. /// -/// If you care about required context and/or type, use the -/// [`SpecializedJsonCredential`] type directly. +/// If you care about required context and/or type, or want to customize other +/// aspects of the credential, use the [`SpecializedJsonCredential`] type +/// directly. pub type JsonCredential = SpecializedJsonCredential; -/// Specialized JSON Credential with custom required context and type. +/// Specialized JSON Credential with custom types for each component. /// -/// If you don't care about required context and/or type, you can use the +/// If you don't care about the type of each component, you can use the /// [`JsonCredential`] type alias instead. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(bound( - serialize = "S: Serialize", - deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet" + serialize = "Subject: Serialize, Issuer: Serialize, Status: Serialize, Evidence: Serialize, Schema: Serialize, RefreshService: Serialize, TermsOfUse: Serialize, ExtraProperties: Serialize", + deserialize = "Subject: Deserialize<'de>, RequiredContext: RequiredContextList, RequiredType: RequiredTypeSet, Issuer: Deserialize<'de>, Status: Deserialize<'de>, Evidence: Deserialize<'de>, Schema: Deserialize<'de>, RefreshService: Deserialize<'de>, TermsOfUse: Deserialize<'de>, ExtraProperties: Deserialize<'de>" ))] -pub struct SpecializedJsonCredential { +pub struct SpecializedJsonCredential< + Subject = json_syntax::Object, + RequiredContext = (), + RequiredType = (), + Issuer = IdOr, + Status = IdentifiedTypedObject, + Evidence = MaybeIdentifiedTypedObject, + Schema = IdentifiedTypedObject, + RefreshService = IdentifiedTypedObject, + TermsOfUse = MaybeIdentifiedTypedObject, + ExtraProperties = BTreeMap, +> { /// JSON-LD context. #[serde(rename = "@context")] - pub context: Context, + pub context: Context, /// Credential identifier. #[serde( @@ -60,7 +75,7 @@ pub struct SpecializedJsonCredential { /// Credential type. #[serde(rename = "type")] - pub types: JsonCredentialTypes, + pub types: JsonCredentialTypes, /// Credential subjects. #[serde(rename = "credentialSubject")] @@ -69,10 +84,10 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_subjects: Vec, + pub credential_subjects: Vec, /// Issuer. - pub issuer: IdOr, + pub issuer: Issuer, /// Issuance date. /// @@ -92,7 +107,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_status: Vec, + pub credential_status: Vec, /// Terms of use. #[serde(rename = "termsOfUse")] @@ -101,7 +116,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub terms_of_use: Vec, + pub terms_of_use: Vec, /// Evidence. #[serde( @@ -109,7 +124,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub evidence: Vec, + pub evidence: Vec, #[serde(rename = "credentialSchema")] #[serde( @@ -117,7 +132,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_schema: Vec, + pub credential_schema: Vec, #[serde(rename = "refreshService")] #[serde( @@ -125,19 +140,47 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub refresh_services: Vec, + pub refresh_services: Vec, #[serde(flatten)] - pub additional_properties: BTreeMap, + pub additional_properties: ExtraProperties, } -impl SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > + SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +where + RequiredContext: RequiredContextList, + RequiredType: RequiredTypeSet, + ExtraProperties: Default, +{ /// Creates a new credential. pub fn new( id: Option, - issuer: IdOr, + issuer: Issuer, issuance_date: xsd_types::DateTime, - credential_subjects: Vec, + credential_subjects: Vec, ) -> Self { Self { context: Context::default(), @@ -152,24 +195,97 @@ impl SpecializedJsonCredential JsonLdObject for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > JsonLdObject + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn json_ld_context(&self) -> Option> { Some(Cow::Borrowed(self.context.as_ref())) } } -impl JsonLdNodeObject for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > JsonLdNodeObject + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn json_ld_type(&self) -> JsonLdTypes { self.types.to_json_ld_types() } } -impl ValidateClaims for SpecializedJsonCredential +impl< + Subject, + RequiredContext, + RequiredType, + Issuer: Identified, + Status: Identified + Typed, + Evidence: MaybeIdentified + Typed, + Schema: Identified + Typed, + RefreshService: Identified + Typed, + TermsOfUse: MaybeIdentified + Typed, + ExtraProperties, + E, + P, + > ValidateClaims + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > where E: DateTimeProvider, { @@ -178,20 +294,68 @@ where } } -impl crate::MaybeIdentified for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > crate::MaybeIdentified + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn id(&self) -> Option<&Uri> { self.id.as_deref() } } -impl crate::v1::Credential for SpecializedJsonCredential { - type Subject = S; - type Issuer = IdOr; - type Status = IdentifiedTypedObject; - type RefreshService = IdentifiedTypedObject; - type TermsOfUse = MaybeIdentifiedTypedObject; - type Evidence = MaybeIdentifiedTypedObject; - type Schema = IdentifiedTypedObject; +impl< + Subject, + RequiredContext, + RequiredType, + Issuer: Identified, + Status: Identified + Typed, + Evidence: MaybeIdentified + Typed, + Schema: Identified + Typed, + RefreshService: Identified + Typed, + TermsOfUse: MaybeIdentified + Typed, + ExtraProperties, + > crate::v1::Credential + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ + type Subject = Subject; + type Issuer = Issuer; + type Status = Status; + type RefreshService = RefreshService; + type TermsOfUse = TermsOfUse; + type Evidence = Evidence; + type Schema = Schema; fn id(&self) -> Option<&Uri> { self.id.as_deref() @@ -238,9 +402,39 @@ impl crate::v1::Credential for SpecializedJsonCredential { } } -impl ssi_json_ld::Expandable for SpecializedJsonCredential +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > ssi_json_ld::Expandable + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > where - S: Serialize, + Subject: Serialize, + Issuer: Serialize, + Status: Serialize, + Evidence: Serialize, + Schema: Serialize, + RefreshService: Serialize, + TermsOfUse: Serialize, + ExtraProperties: Serialize, { type Error = JsonLdError; diff --git a/crates/claims/crates/vc/src/v2/syntax/credential.rs b/crates/claims/crates/vc/src/v2/syntax/credential.rs index eda771e68..559b6c973 100644 --- a/crates/claims/crates/vc/src/v2/syntax/credential.rs +++ b/crates/claims/crates/vc/src/v2/syntax/credential.rs @@ -1,10 +1,13 @@ use std::{borrow::Cow, collections::BTreeMap, hash::Hash}; use super::{Context, InternationalString, RelatedResource}; -use crate::syntax::{ - non_empty_value_or_array, not_null, value_or_array, IdOr, IdentifiedObject, - IdentifiedTypedObject, MaybeIdentifiedTypedObject, NonEmptyObject, NonEmptyVec, - RequiredContextList, RequiredTypeSet, TypedObject, +use crate::{ + syntax::{ + non_empty_value_or_array, not_null, value_or_array, IdOr, IdentifiedObject, + IdentifiedTypedObject, MaybeIdentifiedTypedObject, NonEmptyObject, NonEmptyVec, + RequiredContextList, RequiredTypeSet, TypedObject, + }, + Identified, MaybeIdentified, Typed, }; use iref::{Uri, UriBuf}; use rdf_types::VocabularyMut; @@ -18,23 +21,35 @@ pub use crate::v1::syntax::{CredentialType, JsonCredentialTypes, VERIFIABLE_CRED /// JSON Credential, without required context nor type. /// -/// If you care about required context and/or type, use the -/// [`SpecializedJsonCredential`] type directly. +/// If you care about required context and/or type, or want to customize other +/// aspects of the credential, use the [`SpecializedJsonCredential`] type +/// directly. pub type JsonCredential = SpecializedJsonCredential; -/// Specialized JSON Credential with custom required context and type. +/// Specialized JSON Credential with custom types for each component. /// -/// If you don't care about required context and/or type, you can use the +/// If you don't care about the type of each component, you can use the /// [`JsonCredential`] type alias instead. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(bound( - serialize = "S: Serialize", - deserialize = "S: Deserialize<'de>, C: RequiredContextList, T: RequiredTypeSet" + serialize = "Subject: Serialize, Issuer: Serialize, Status: Serialize, Evidence: Serialize, Schema: Serialize, RefreshService: Serialize, TermsOfUse: Serialize, ExtraProperties: Serialize", + deserialize = "Subject: Deserialize<'de>, RequiredContext: RequiredContextList, RequiredType: RequiredTypeSet, Issuer: Deserialize<'de>, Status: Deserialize<'de>, Evidence: Deserialize<'de>, Schema: Deserialize<'de>, RefreshService: Deserialize<'de>, TermsOfUse: Deserialize<'de>, ExtraProperties: Deserialize<'de>" ))] -pub struct SpecializedJsonCredential { +pub struct SpecializedJsonCredential< + Subject = NonEmptyObject, + RequiredContext = (), + RequiredType = (), + Issuer = IdOr, + Status = MaybeIdentifiedTypedObject, + Evidence = MaybeIdentifiedTypedObject, + Schema = IdentifiedTypedObject, + RefreshService = TypedObject, + TermsOfUse = MaybeIdentifiedTypedObject, + ExtraProperties = BTreeMap, +> { /// JSON-LD context. #[serde(rename = "@context")] - pub context: Context, + pub context: Context, /// Credential identifier. #[serde( @@ -46,15 +61,15 @@ pub struct SpecializedJsonCredential { /// Credential type. #[serde(rename = "type")] - pub types: JsonCredentialTypes, + pub types: JsonCredentialTypes, /// Credential subjects. #[serde(rename = "credentialSubject")] #[serde(with = "non_empty_value_or_array")] - pub credential_subjects: NonEmptyVec, + pub credential_subjects: NonEmptyVec, /// Issuer. - pub issuer: IdOr, + pub issuer: Issuer, /// Issuance date. #[serde(rename = "validFrom")] @@ -73,7 +88,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_status: Vec, + pub credential_status: Vec, /// Terms of use. #[serde(rename = "termsOfUse")] @@ -82,7 +97,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub terms_of_use: Vec, + pub terms_of_use: Vec, /// Evidence. #[serde( @@ -90,7 +105,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub evidence: Vec, + pub evidence: Vec, #[serde(rename = "credentialSchema")] #[serde( @@ -98,7 +113,7 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub credential_schema: Vec, + pub credential_schema: Vec, #[serde(rename = "refreshService")] #[serde( @@ -106,18 +121,46 @@ pub struct SpecializedJsonCredential { default, skip_serializing_if = "Vec::is_empty" )] - pub refresh_services: Vec, + pub refresh_services: Vec, #[serde(flatten)] - pub extra_properties: BTreeMap, + pub extra_properties: ExtraProperties, } -impl SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > + SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +where + RequiredContext: RequiredContextList, + RequiredType: RequiredTypeSet, + ExtraProperties: Default, +{ /// Creates a new credential. pub fn new( id: Option, - issuer: IdOr, - credential_subjects: NonEmptyVec, + issuer: Issuer, + credential_subjects: NonEmptyVec, ) -> Self { Self { context: Context::default(), @@ -132,24 +175,97 @@ impl SpecializedJsonCredential JsonLdObject for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > JsonLdObject + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn json_ld_context(&self) -> Option> { Some(Cow::Borrowed(self.context.as_ref())) } } -impl JsonLdNodeObject for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > JsonLdNodeObject + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn json_ld_type(&self) -> JsonLdTypes { self.types.to_json_ld_types() } } -impl ValidateClaims for SpecializedJsonCredential +impl< + Subject, + RequiredContext, + RequiredType, + Issuer: Identified, + Status: MaybeIdentified + Typed, + Evidence: MaybeIdentified + Typed, + Schema: Identified + Typed, + RefreshService: Typed, + TermsOfUse: MaybeIdentified + Typed, + ExtraProperties, + E, + P, + > ValidateClaims + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > where E: DateTimeProvider, { @@ -158,21 +274,69 @@ where } } -impl crate::MaybeIdentified for SpecializedJsonCredential { +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > crate::MaybeIdentified + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ fn id(&self) -> Option<&Uri> { self.id.as_deref() } } -impl crate::v2::Credential for SpecializedJsonCredential { - type Subject = S; +impl< + Subject, + RequiredContext, + RequiredType, + Issuer: Identified, + Status: MaybeIdentified + Typed, + Evidence: MaybeIdentified + Typed, + Schema: Identified + Typed, + RefreshService: Typed, + TermsOfUse: MaybeIdentified + Typed, + ExtraProperties, + > crate::v2::Credential + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > +{ + type Subject = Subject; type Description = InternationalString; - type Issuer = IdOr; - type Status = MaybeIdentifiedTypedObject; - type RefreshService = TypedObject; - type TermsOfUse = MaybeIdentifiedTypedObject; - type Evidence = MaybeIdentifiedTypedObject; - type Schema = IdentifiedTypedObject; + type Issuer = Issuer; + type Status = Status; + type RefreshService = RefreshService; + type TermsOfUse = TermsOfUse; + type Evidence = Evidence; + type Schema = Schema; type RelatedResource = RelatedResource; fn additional_types(&self) -> &[String] { @@ -216,9 +380,39 @@ impl crate::v2::Credential for SpecializedJsonCredential { } } -impl ssi_json_ld::Expandable for SpecializedJsonCredential +impl< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > ssi_json_ld::Expandable + for SpecializedJsonCredential< + Subject, + RequiredContext, + RequiredType, + Issuer, + Status, + Evidence, + Schema, + RefreshService, + TermsOfUse, + ExtraProperties, + > where - S: Serialize, + Subject: Serialize, + Issuer: Serialize, + Status: Serialize, + Evidence: Serialize, + Schema: Serialize, + RefreshService: Serialize, + TermsOfUse: Serialize, + ExtraProperties: Serialize, { type Error = JsonLdError;