Skip to content

Commit

Permalink
feat: add ProblemDetails module for Verifiable Credentials Data Model…
Browse files Browse the repository at this point in the history
… v2.0
  • Loading branch information
laysakura committed Jul 23, 2024
1 parent 04720d4 commit 524ca52
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
5 changes: 5 additions & 0 deletions crates/claims/crates/vc/src/v2/algorithm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//! [Algorithms](https://www.w3.org/TR/vc-data-model-2.0/#algorithms) in Verifiable Credentials Data Model v2.0
mod problem_details;

pub use problem_details::ProblemDetails;
169 changes: 169 additions & 0 deletions crates/claims/crates/vc/src/v2/algorithm/problem_details.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
use std::fmt;

use serde::{Serialize, Serializer};

/// [Problem Details](https://www.w3.org/TR/vc-data-model-2.0/#problem-details).
#[derive(Debug, Serialize)]
pub struct ProblemDetails {
#[serde(rename = "type", serialize_with = "serialize_problem_type")]
problem_type: Box<dyn ProblemType>,

#[serde(skip_serializing_if = "Option::is_none")]
code: Option<i32>,

title: String,
detail: String,
}

impl ProblemDetails {
pub fn new<T: ProblemType>(problem_type: T, title: String, detail: String) -> Self {
let code = problem_type.code();
Self {
problem_type: Box::new(problem_type),
code: Some(code),
title,
detail,
}
}

/// `type` property.
pub fn r#type(&self) -> &str {
self.problem_type.url()
}

/// `code` property.
pub fn code(&self) -> Option<i32> {
self.code
}

/// `title` property.
pub fn title(&self) -> &str {
&self.title
}

/// `detail` property.
pub fn detail(&self) -> &str {
&self.detail
}
}

fn serialize_problem_type<S>(
problem_type: &Box<dyn ProblemType>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&problem_type.to_string())
}

/// Problem `type`.
///
/// Implementations can define custom problem types.
pub trait ProblemType: fmt::Display + fmt::Debug + Send + Sync + 'static {
fn url(&self) -> &'static str;
fn code(&self) -> i32;
}

/// Predefined `type`s in <https://www.w3.org/TR/vc-data-model-2.0/#problem-details>.
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PredefinedProblemType {
ParsingError,
CryptographicSecurityError,
MalformedValueError,
RangeError,
}

impl ProblemType for PredefinedProblemType {
fn url(&self) -> &'static str {
match self {
PredefinedProblemType::ParsingError => {
"https://www.w3.org/TR/vc-data-model#PARSING_ERROR"
}
PredefinedProblemType::CryptographicSecurityError => {
"https://www.w3.org/TR/vc-data-model#CRYPTOGRAPHIC_SECURITY_ERROR"
}
PredefinedProblemType::MalformedValueError => {
"https://www.w3.org/TR/vc-data-model#MALFORMED_VALUE_ERROR"
}
PredefinedProblemType::RangeError => "https://www.w3.org/TR/vc-data-model#RANGE_ERROR",
}
}

fn code(&self) -> i32 {
match self {
PredefinedProblemType::ParsingError => -64,
PredefinedProblemType::CryptographicSecurityError => -65,
PredefinedProblemType::MalformedValueError => -66,
PredefinedProblemType::RangeError => -67,
}
}
}

impl fmt::Display for PredefinedProblemType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.url())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_serialize_problem_details_parsing_error() {
let problem = ProblemDetails::new(
PredefinedProblemType::ParsingError,
"Parsing Error".to_string(),
"Failed to parse the request body.".to_string(),
);
let json = serde_json::to_string(&problem).expect("Failed to serialize ProblemDetails");
assert_eq!(
json,
r#"{"type":"https://www.w3.org/TR/vc-data-model#PARSING_ERROR","code":-64,"title":"Parsing Error","detail":"Failed to parse the request body."}"#
);
}

#[test]
fn test_serialize_problem_details_cryptographic_security_error() {
let problem = ProblemDetails::new(
PredefinedProblemType::CryptographicSecurityError,
"Cryptographic Security Error".to_string(),
"Failed to verify the cryptographic proof.".to_string(),
);
let json = serde_json::to_string(&problem).expect("Failed to serialize ProblemDetails");
assert_eq!(
json,
r#"{"type":"https://www.w3.org/TR/vc-data-model#CRYPTOGRAPHIC_SECURITY_ERROR","code":-65,"title":"Cryptographic Security Error","detail":"Failed to verify the cryptographic proof."}"#
);
}

#[test]
fn test_serialize_problem_details_malformed_value_error() {
let problem = ProblemDetails::new(
PredefinedProblemType::MalformedValueError,
"Malformed Value Error".to_string(),
"The request body contains a malformed value.".to_string(),
);
let json = serde_json::to_string(&problem).expect("Failed to serialize ProblemDetails");
assert_eq!(
json,
r#"{"type":"https://www.w3.org/TR/vc-data-model#MALFORMED_VALUE_ERROR","code":-66,"title":"Malformed Value Error","detail":"The request body contains a malformed value."}"#
);
}

#[test]
fn test_serialize_problem_details_range_error() {
let problem = ProblemDetails::new(
PredefinedProblemType::RangeError,
"Range Error".to_string(),
"The request body contains a value out of range.".to_string(),
);
let json = serde_json::to_string(&problem).expect("Failed to serialize ProblemDetails");
assert_eq!(
json,
r#"{"type":"https://www.w3.org/TR/vc-data-model#RANGE_ERROR","code":-67,"title":"Range Error","detail":"The request body contains a value out of range."}"#
);
}
}
2 changes: 2 additions & 0 deletions crates/claims/crates/vc/src/v2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use iref::Iri;

use crate::syntax::RequiredContext;

mod algorithm;
mod data_model;
pub mod syntax;

pub use algorithm::ProblemDetails;
pub use data_model::*;
pub use syntax::{Context, JsonCredential, JsonCredentialTypes, SpecializedJsonCredential};

Expand Down

0 comments on commit 524ca52

Please sign in to comment.