use std::collections::BTreeMap;
use chrono::{DateTime, FixedOffset};
use iref::{Iri, IriBuf, UriBuf};
use non_empty_string::NonEmptyString;
use nonempty_collections::NEVec;
use serde::{Deserialize, Serialize};
use crate::{
crypto::asn1::rfc3161::TstInfo,
identity::{
claim_aggregation::w3c_vc::credential::{CredentialV2, VerifiableCredentialSubtype},
identity_assertion::signature_verifier::ToCredentialSummary,
SignerPayload,
},
};
pub type IcaCredential = CredentialV2<IdentityClaimsAggregationVc>;
impl ToCredentialSummary for IcaCredential {
type CredentialSummary = IcaCredentialSummary;
fn to_summary(&self) -> Self::CredentialSummary {
IcaCredentialSummary::from_credential(self)
}
}
pub const IDENTITY_CLAIMS_AGGREGATION_CONTEXT_IRI: &Iri =
static_iref::iri!("https://cawg.io/identity/1.1/ica/context/");
pub const IDENTITY_CLAIMS_AGGREGATION_CREDENTIAL_TYPE: &str = "IdentityClaimsAggregationCredential";
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct IdentityClaimsAggregationVc {
#[serde(rename = "verifiedIdentities")]
pub verified_identities: NEVec<VerifiedIdentity>,
#[serde(rename = "c2paAsset")]
pub c2pa_asset: SignerPayload,
#[serde(skip)]
pub time_stamp: Option<TstInfo>,
}
impl VerifiableCredentialSubtype for IdentityClaimsAggregationVc {
fn required_contexts(&self) -> &[&'static Iri] {
&[IDENTITY_CLAIMS_AGGREGATION_CONTEXT_IRI]
}
fn required_types(&self) -> &[&'static str] {
&[IDENTITY_CLAIMS_AGGREGATION_CREDENTIAL_TYPE]
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct VerifiedIdentity {
#[serde(rename = "type")]
pub type_: NonEmptyString,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<NonEmptyString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<NonEmptyString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<NonEmptyString>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uri: Option<UriBuf>,
#[serde(rename = "verifiedAt")]
pub verified_at: DateTime<FixedOffset>,
pub provider: IdentityProvider,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct IdentityProvider {
pub id: UriBuf,
pub name: NonEmptyString,
}
#[doc(hidden)]
#[derive(Serialize)]
pub struct IcaCredentialSummary {
#[serde(rename = "@context")]
contexts: NEVec<IriBuf>,
#[serde(
default,
deserialize_with = "not_null",
skip_serializing_if = "Option::is_none"
)]
id: Option<UriBuf>,
#[serde(rename = "type")]
types: NEVec<String>,
issuer: UriBuf,
#[serde(rename = "validFrom")]
#[serde(default, skip_serializing_if = "Option::is_none")]
valid_from: Option<DateTime<FixedOffset>>,
#[serde(rename = "validUntil")]
#[serde(default, skip_serializing_if = "Option::is_none")]
valid_until: Option<DateTime<FixedOffset>>,
#[serde(rename = "verifiedIdentities")]
verified_identities: NEVec<VerifiedIdentity>,
#[serde(flatten)]
extra_properties: BTreeMap<String, serde_json::Value>,
}
impl IcaCredentialSummary {
fn from_credential(ica: &IcaCredential) -> Self {
let subject = ica.credential_subjects.first();
Self {
contexts: ica.contexts.clone(),
id: ica.id.clone(),
issuer: ica.issuer.clone(),
types: ica.types.clone(),
valid_from: ica.valid_from,
valid_until: ica.valid_until,
verified_identities: subject.verified_identities.clone(),
extra_properties: ica.extra_properties.clone(),
}
}
}