identity_credential/credential/
credential.rsuse core::fmt::Display;
use core::fmt::Formatter;
use identity_core::convert::ToJson;
#[cfg(feature = "jpt-bbs-plus")]
use jsonprooftoken::jpt::claims::JptClaims;
use once_cell::sync::Lazy;
use serde::Deserialize;
use serde::Serialize;
use identity_core::common::Context;
use identity_core::common::Object;
use identity_core::common::OneOrMany;
use identity_core::common::Timestamp;
use identity_core::common::Url;
use identity_core::convert::FmtJson;
use crate::credential::CredentialBuilder;
use crate::credential::Evidence;
use crate::credential::Issuer;
use crate::credential::Policy;
use crate::credential::RefreshService;
use crate::credential::Schema;
use crate::credential::Status;
use crate::credential::Subject;
use crate::error::Error;
use crate::error::Result;
use super::jwt_serialization::CredentialJwtClaims;
use super::Proof;
static BASE_CONTEXT: Lazy<Context> =
Lazy::new(|| Context::Url(Url::parse("https://www.w3.org/2018/credentials/v1").unwrap()));
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Credential<T = Object> {
#[serde(rename = "@context")]
pub context: OneOrMany<Context>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Url>,
#[serde(rename = "type")]
pub types: OneOrMany<String>,
#[serde(rename = "credentialSubject")]
pub credential_subject: OneOrMany<Subject>,
pub issuer: Issuer,
#[serde(rename = "issuanceDate")]
pub issuance_date: Timestamp,
#[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")]
pub expiration_date: Option<Timestamp>,
#[serde(default, rename = "credentialStatus", skip_serializing_if = "Option::is_none")]
pub credential_status: Option<Status>,
#[serde(default, rename = "credentialSchema", skip_serializing_if = "OneOrMany::is_empty")]
pub credential_schema: OneOrMany<Schema>,
#[serde(default, rename = "refreshService", skip_serializing_if = "OneOrMany::is_empty")]
pub refresh_service: OneOrMany<RefreshService>,
#[serde(default, rename = "termsOfUse", skip_serializing_if = "OneOrMany::is_empty")]
pub terms_of_use: OneOrMany<Policy>,
#[serde(default, skip_serializing_if = "OneOrMany::is_empty")]
pub evidence: OneOrMany<Evidence>,
#[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")]
pub non_transferable: Option<bool>,
#[serde(flatten)]
pub properties: T,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<Proof>,
}
impl<T> Credential<T> {
pub fn base_context() -> &'static Context {
&BASE_CONTEXT
}
pub const fn base_type() -> &'static str {
"VerifiableCredential"
}
pub fn builder(properties: T) -> CredentialBuilder<T> {
CredentialBuilder::new(properties)
}
pub fn from_builder(builder: CredentialBuilder<T>) -> Result<Self> {
let this: Self = Self {
context: builder.context.into(),
id: builder.id,
types: builder.types.into(),
credential_subject: builder.subject.into(),
issuer: builder.issuer.ok_or(Error::MissingIssuer)?,
issuance_date: builder.issuance_date.unwrap_or_default(),
expiration_date: builder.expiration_date,
credential_status: builder.status,
credential_schema: builder.schema.into(),
refresh_service: builder.refresh_service.into(),
terms_of_use: builder.terms_of_use.into(),
evidence: builder.evidence.into(),
non_transferable: builder.non_transferable,
properties: builder.properties,
proof: builder.proof,
};
this.check_structure()?;
Ok(this)
}
pub fn check_structure(&self) -> Result<()> {
match self.context.get(0) {
Some(context) if context == Self::base_context() => {}
Some(_) | None => return Err(Error::MissingBaseContext),
}
if !self.types.iter().any(|type_| type_ == Self::base_type()) {
return Err(Error::MissingBaseType);
}
if self.credential_subject.is_empty() {
return Err(Error::MissingSubject);
}
for subject in self.credential_subject.iter() {
if subject.id.is_none() && subject.properties.is_empty() {
return Err(Error::InvalidSubject);
}
}
Ok(())
}
pub fn set_proof(&mut self, proof: Option<Proof>) {
self.proof = proof;
}
pub fn serialize_jwt(&self, custom_claims: Option<Object>) -> Result<String>
where
T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
{
let jwt_representation: CredentialJwtClaims<'_, T> = CredentialJwtClaims::new(self, custom_claims)?;
jwt_representation
.to_json()
.map_err(|err| Error::JwtClaimsSetSerializationError(err.into()))
}
#[cfg(feature = "jpt-bbs-plus")]
pub fn serialize_jpt(&self, custom_claims: Option<Object>) -> Result<JptClaims>
where
T: ToOwned<Owned = T> + serde::Serialize + serde::de::DeserializeOwned,
{
let jwt_representation: CredentialJwtClaims<'_, T> = CredentialJwtClaims::new(self, custom_claims)?;
Ok(jwt_representation.into())
}
}
impl<T> Display for Credential<T>
where
T: Serialize,
{
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
self.fmt_json(f)
}
}
#[cfg(test)]
mod tests {
use identity_core::convert::FromJson;
use crate::credential::Credential;
const JSON1: &str = include_str!("../../tests/fixtures/credential-1.json");
const JSON2: &str = include_str!("../../tests/fixtures/credential-2.json");
const JSON3: &str = include_str!("../../tests/fixtures/credential-3.json");
const JSON4: &str = include_str!("../../tests/fixtures/credential-4.json");
const JSON5: &str = include_str!("../../tests/fixtures/credential-5.json");
const JSON6: &str = include_str!("../../tests/fixtures/credential-6.json");
const JSON7: &str = include_str!("../../tests/fixtures/credential-7.json");
const JSON8: &str = include_str!("../../tests/fixtures/credential-8.json");
const JSON9: &str = include_str!("../../tests/fixtures/credential-9.json");
const JSON10: &str = include_str!("../../tests/fixtures/credential-10.json");
const JSON11: &str = include_str!("../../tests/fixtures/credential-11.json");
const JSON12: &str = include_str!("../../tests/fixtures/credential-12.json");
#[test]
fn test_from_json() {
let _credential: Credential = Credential::from_json(JSON1).unwrap();
let _credential: Credential = Credential::from_json(JSON2).unwrap();
let _credential: Credential = Credential::from_json(JSON3).unwrap();
let _credential: Credential = Credential::from_json(JSON4).unwrap();
let _credential: Credential = Credential::from_json(JSON5).unwrap();
let _credential: Credential = Credential::from_json(JSON6).unwrap();
let _credential: Credential = Credential::from_json(JSON7).unwrap();
let _credential: Credential = Credential::from_json(JSON8).unwrap();
let _credential: Credential = Credential::from_json(JSON9).unwrap();
let _credential: Credential = Credential::from_json(JSON10).unwrap();
let _credential: Credential = Credential::from_json(JSON11).unwrap();
let _credential: Credential = Credential::from_json(JSON12).unwrap();
}
}