Skip to content

Commit

Permalink
feat(dataverse): add dedicated errors to credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
amimart committed Feb 5, 2024
1 parent b4fe886 commit 4e8890e
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 41 deletions.
22 changes: 22 additions & 0 deletions contracts/okp4-dataverse/src/credential/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use thiserror::Error;

#[derive(Error, Debug, PartialEq)]
pub enum InvalidCredentialError {
#[error("Missing identifier")]
MissingIdentifier,

#[error("Missing issuer")]
MissingIssuer,

#[error("Missing issuance date")]
MissingIssuanceDate,

#[error("Missing proof")]
MissingProof,

#[error("Missing proof type")]
MissingProofType,

#[error("Malformed: {0}")]
Malformed(String),
}
1 change: 1 addition & 0 deletions contracts/okp4-dataverse/src/credential/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod error;
mod proof;
mod rdf_markers;
mod vc;
18 changes: 10 additions & 8 deletions contracts/okp4-dataverse/src/credential/proof.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
use crate::credential::error::InvalidCredentialError;
use crate::credential::rdf_markers::RDF_TYPE;
use crate::ContractError;
use itertools::Itertools;
use okp4_rdf::dataset::{Dataset, QuadIterator};
use rio_api::model::Term;

#[allow(dead_code)]
pub struct Proof<'a> {
type_: String,
inner: Dataset<'a>,
}

impl<'a> TryFrom<Dataset<'a>> for Proof<'a> {
type Error = ContractError;
type Error = InvalidCredentialError;

fn try_from(dataset: Dataset<'a>) -> Result<Self, Self::Error> {
Ok(Self {
type_: dataset
.match_pattern(None, Some(RDF_TYPE), None, None)
.objects()
.exactly_one()
.map_err(|_| {
ContractError::InvalidCredential(
"Credential proof can must have only one type".to_string(),
)
.map_err(|e| match e.size_hint() {
(_, Some(_)) => InvalidCredentialError::MissingProofType,
_ => InvalidCredentialError::Malformed(
"Proof cannot have more than one type".to_string(),
),
})
.and_then(|o| match o {
Term::NamedNode(n) => Ok(n.iri.to_string()),
_ => Err(ContractError::InvalidCredential(
"Credential proof type must be a named node".to_string(),
_ => Err(InvalidCredentialError::Malformed(
"Proof type must be a named node".to_string(),
)),
})?,
inner: dataset,
Expand Down
80 changes: 47 additions & 33 deletions contracts/okp4-dataverse/src/credential/vc.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::credential::error::InvalidCredentialError;
use crate::credential::proof::Proof;
use crate::credential::rdf_markers::*;
use crate::ContractError;
use itertools::Itertools;
use okp4_rdf::dataset::Dataset;
use okp4_rdf::dataset::QuadIterator;
use rio_api::model::{BlankNode, Literal, NamedNode, Subject, Term};

#[allow(dead_code)]
pub struct VerifiableCredential<'a> {
id: &'a str,
types: Vec<&'a str>,
Expand All @@ -18,26 +19,32 @@ pub struct VerifiableCredential<'a> {
unsecured_document: Dataset<'a>,
}

#[allow(dead_code)]
pub struct Claim<'a> {
id: &'a str,
content: Dataset<'a>,
}

#[allow(dead_code)]
pub struct Status<'a> {
id: &'a str,
type_: &'a str,
content: Dataset<'a>,
}

impl<'a> TryFrom<&'a Dataset<'a>> for VerifiableCredential<'a> {
type Error = ContractError;
type Error = InvalidCredentialError;

fn try_from(dataset: &'a Dataset<'a>) -> Result<Self, Self::Error> {
let id = Self::extract_identifier(&dataset)?;

let (proofs, proof_graphs): (Vec<Proof<'a>>, Vec<BlankNode<'a>>) =
Self::extract_proofs(dataset, id)?.into_iter().unzip();

if proofs.is_empty() {
return Err(InvalidCredentialError::MissingProof);
}

let unsecured_filter = proof_graphs
.into_iter()
.map(|g| (None, None, None, Some(Some(g.into()))).into())
Expand All @@ -64,19 +71,22 @@ impl<'a> TryFrom<&'a Dataset<'a>> for VerifiableCredential<'a> {
}

impl<'a> VerifiableCredential<'a> {
fn extract_identifier(dataset: &'a Dataset<'a>) -> Result<NamedNode<'a>, ContractError> {
fn extract_identifier(
dataset: &'a Dataset<'a>,
) -> Result<NamedNode<'a>, InvalidCredentialError> {
dataset
.match_pattern(None, Some(RDF_TYPE), Some(VC_RDF_TYPE), None)
.subjects()
.exactly_one()
.map_err(|_| {
ContractError::InvalidCredential(
"Credential must contains one identifier".to_string(),
)
.map_err(|e| match e.size_hint() {
(_, Some(_)) => InvalidCredentialError::MissingIdentifier,
_ => InvalidCredentialError::Malformed(
"Credential cannot have more than one id".to_string(),
),
})
.and_then(|s| match s {
Subject::NamedNode(n) => Ok(n),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential identifier must be a named node".to_string(),
)),
})
Expand All @@ -85,13 +95,13 @@ impl<'a> VerifiableCredential<'a> {
fn extract_types(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<Vec<&'a str>, ContractError> {
) -> Result<Vec<&'a str>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(RDF_TYPE), None, None)
.objects()
.map(|o| match o {
Term::NamedNode(n) => Ok(n.iri),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential type must be a named node".to_string(),
)),
})
Expand All @@ -101,17 +111,20 @@ impl<'a> VerifiableCredential<'a> {
fn extract_issuer(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<NamedNode<'a>, ContractError> {
) -> Result<NamedNode<'a>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_ISSUER), None, None)
.objects()
.exactly_one()
.map_err(|_| {
ContractError::InvalidCredential("Credential must contains one issuer".to_string())
.map_err(|e| match e.size_hint() {
(_, Some(_)) => InvalidCredentialError::MissingIssuer,
_ => InvalidCredentialError::Malformed(
"Credential cannot have more than one issuer".to_string(),
),
})
.and_then(|o| match o {
Term::NamedNode(n) => Ok(n),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential issuer must be a named node".to_string(),
)),
})
Expand All @@ -120,21 +133,22 @@ impl<'a> VerifiableCredential<'a> {
fn extract_issuance_date(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<&'a str, ContractError> {
) -> Result<&'a str, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_ISSUANCE_DATE), None, None)
.objects()
.exactly_one()
.map_err(|_| {
ContractError::InvalidCredential(
"Credential must contains one issuance date".to_string(),
)
.map_err(|e| match e.size_hint() {
(_, Some(_)) => InvalidCredentialError::MissingIssuanceDate,
_ => InvalidCredentialError::Malformed(
"Credential cannot have more than one issuance date".to_string(),
),
})
.and_then(|o| match o {
Term::Literal(Literal::Typed { value, datatype }) if datatype == RDF_DATE_TYPE => {
Ok(value)
}
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential issuance date must be a date".to_string(),
)),
})
Expand All @@ -143,14 +157,14 @@ impl<'a> VerifiableCredential<'a> {
fn extract_expiration_date(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<Option<&'a str>, ContractError> {
) -> Result<Option<&'a str>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_EXPIRATION_DATE), None, None)
.objects()
.at_most_one()
.map_err(|_| {
ContractError::InvalidCredential(
"Credential may contains one expiration date".to_string(),
InvalidCredentialError::Malformed(
"Credential cannot have more than one expiration date".to_string(),
)
})
.and_then(|o| match o {
Expand All @@ -160,7 +174,7 @@ impl<'a> VerifiableCredential<'a> {
{
Ok(Some(value))
}
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential expiration date must be a date".to_string(),
)),
},
Expand All @@ -171,13 +185,13 @@ impl<'a> VerifiableCredential<'a> {
fn extract_claims(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<Vec<Claim<'a>>, ContractError> {
) -> Result<Vec<Claim<'a>>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_CREDENTIAL_SUBJECT), None, None)
.objects()
.map(|claim_id| match claim_id {
Term::NamedNode(n) => Ok(n),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential claim ids must be named nodes".to_string(),
)),
})
Expand All @@ -196,14 +210,14 @@ impl<'a> VerifiableCredential<'a> {
fn extract_status(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<Option<Status<'a>>, ContractError> {
) -> Result<Option<Status<'a>>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_CREDENTIAL_STATUS), None, None)
.objects()
.at_most_one()
.map_err(|_| {
ContractError::InvalidCredential(
"Credential can contains at most one status".to_string(),
InvalidCredentialError::Malformed(
"Credential cannot have more than one expiration date".to_string(),
)
})
.and_then(|maybe_term| match maybe_term {
Expand All @@ -214,7 +228,7 @@ impl<'a> VerifiableCredential<'a> {
.iter()
.exactly_one()
.map_err(|_| {
ContractError::InvalidCredential(
InvalidCredentialError::Malformed(
"Credential status can only have one type".to_string(),
)
})?,
Expand All @@ -225,7 +239,7 @@ impl<'a> VerifiableCredential<'a> {
.collect(),
),
})),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential status id must be a named node".to_string(),
)),
},
Expand All @@ -236,14 +250,14 @@ impl<'a> VerifiableCredential<'a> {
fn extract_proofs(
dataset: &'a Dataset<'a>,
id: NamedNode<'a>,
) -> Result<Vec<(Proof<'a>, BlankNode<'a>)>, ContractError> {
) -> Result<Vec<(Proof<'a>, BlankNode<'a>)>, InvalidCredentialError> {
dataset
.match_pattern(Some(id.into()), Some(VC_RDF_PROOF), None, None)
.objects()
.map(|o| {
match o {
Term::BlankNode(n) => Ok(n),
_ => Err(ContractError::InvalidCredential(
_ => Err(InvalidCredentialError::Malformed(
"Credential proof must be encapsulated in blank node graph names"
.to_string(),
)),
Expand Down

0 comments on commit 4e8890e

Please sign in to comment.