From 524ca528e143640a0d4d671404323431a10d85ef Mon Sep 17 00:00:00 2001 From: Sho Nakatani Date: Tue, 23 Jul 2024 13:25:38 +0900 Subject: [PATCH] feat: add ProblemDetails module for Verifiable Credentials Data Model v2.0 --- .../claims/crates/vc/src/v2/algorithm/mod.rs | 5 + .../vc/src/v2/algorithm/problem_details.rs | 169 ++++++++++++++++++ crates/claims/crates/vc/src/v2/mod.rs | 2 + 3 files changed, 176 insertions(+) create mode 100644 crates/claims/crates/vc/src/v2/algorithm/mod.rs create mode 100644 crates/claims/crates/vc/src/v2/algorithm/problem_details.rs diff --git a/crates/claims/crates/vc/src/v2/algorithm/mod.rs b/crates/claims/crates/vc/src/v2/algorithm/mod.rs new file mode 100644 index 000000000..827506892 --- /dev/null +++ b/crates/claims/crates/vc/src/v2/algorithm/mod.rs @@ -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; diff --git a/crates/claims/crates/vc/src/v2/algorithm/problem_details.rs b/crates/claims/crates/vc/src/v2/algorithm/problem_details.rs new file mode 100644 index 000000000..abc0aef0e --- /dev/null +++ b/crates/claims/crates/vc/src/v2/algorithm/problem_details.rs @@ -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, + + #[serde(skip_serializing_if = "Option::is_none")] + code: Option, + + title: String, + detail: String, +} + +impl ProblemDetails { + pub fn new(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 { + self.code + } + + /// `title` property. + pub fn title(&self) -> &str { + &self.title + } + + /// `detail` property. + pub fn detail(&self) -> &str { + &self.detail + } +} + +fn serialize_problem_type( + problem_type: &Box, + serializer: S, +) -> Result +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 . +#[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."}"# + ); + } +} diff --git a/crates/claims/crates/vc/src/v2/mod.rs b/crates/claims/crates/vc/src/v2/mod.rs index c4c6f73ff..aac6f120c 100644 --- a/crates/claims/crates/vc/src/v2/mod.rs +++ b/crates/claims/crates/vc/src/v2/mod.rs @@ -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};