Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue Credential V2.0 message structures #990

Merged
merged 16 commits into from
Sep 28, 2023
Merged
18 changes: 17 additions & 1 deletion aries_vcx/src/handlers/util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use messages::{
msg_fields::protocols::{
connection::{invitation::Invitation, Connection},
cred_issuance::CredentialIssuance,
cred_issuance::{v2::CredentialIssuanceV2, CredentialIssuance},
bobozaur marked this conversation as resolved.
Show resolved Hide resolved
discover_features::DiscoverFeatures,
notification::Notification,
out_of_band::{invitation::Invitation as OobInvitation, OutOfBand},
Expand Down Expand Up @@ -109,6 +109,22 @@ pub fn verify_thread_id(thread_id: &str, message: &AriesMessage) -> VcxResult<()
AriesMessage::CredentialIssuance(CredentialIssuance::ProblemReport(msg)) => {
matches_opt_thread_id!(msg, thread_id)
}
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::Ack(msg)) => matches_thread_id!(msg, thread_id),
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::IssueCredential(msg)) => {
matches_thread_id!(msg, thread_id)
}
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::OfferCredential(msg)) => {
matches_opt_thread_id!(msg, thread_id)
}
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::ProposeCredential(msg)) => {
matches_opt_thread_id!(msg, thread_id)
}
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::RequestCredential(msg)) => {
matches_opt_thread_id!(msg, thread_id)
}
AriesMessage::CredentialIssuanceV2(CredentialIssuanceV2::ProblemReport(msg)) => {
matches_opt_thread_id!(msg, thread_id)
}
AriesMessage::DiscoverFeatures(DiscoverFeatures::Query(msg)) => msg.id == thread_id,
AriesMessage::DiscoverFeatures(DiscoverFeatures::Disclose(msg)) => matches_thread_id!(msg, thread_id),
AriesMessage::Notification(Notification::Ack(msg)) => matches_thread_id!(msg, thread_id),
Expand Down
19 changes: 16 additions & 3 deletions messages/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ pub mod msg_types;

use derive_more::From;
use misc::utils;
use msg_types::{report_problem::ReportProblemTypeV1_0, routing::RoutingTypeV1_0, MsgWithType};
use msg_fields::protocols::cred_issuance::v2::CredentialIssuanceV2;
use msg_types::{
cred_issuance::CredentialIssuanceType, report_problem::ReportProblemTypeV1_0, routing::RoutingTypeV1_0, MsgWithType,
};
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

use crate::{
Expand Down Expand Up @@ -52,6 +55,7 @@ pub enum AriesMessage {
Connection(Connection),
Revocation(Revocation),
CredentialIssuance(CredentialIssuance),
CredentialIssuanceV2(CredentialIssuanceV2),
bobozaur marked this conversation as resolved.
Show resolved Hide resolved
bobozaur marked this conversation as resolved.
Show resolved Hide resolved
ReportProblem(ProblemReport),
PresentProof(PresentProof),
TrustPing(TrustPing),
Expand Down Expand Up @@ -97,8 +101,16 @@ impl DelayedSerde for AriesMessage {
Protocol::RevocationType(msg_type) => {
Revocation::delayed_deserialize((msg_type, kind_str), deserializer).map(From::from)
}
Protocol::CredentialIssuanceType(msg_type) => {
CredentialIssuance::delayed_deserialize((msg_type, kind_str), deserializer).map(From::from)
Protocol::CredentialIssuanceType(CredentialIssuanceType::V1(msg_type)) => {
CredentialIssuance::delayed_deserialize((CredentialIssuanceType::V1(msg_type), kind_str), deserializer)
.map(From::from)
}
Protocol::CredentialIssuanceType(CredentialIssuanceType::V2(msg_type)) => {
CredentialIssuanceV2::delayed_deserialize(
(CredentialIssuanceType::V2(msg_type), kind_str),
deserializer,
)
.map(From::from)
}
Protocol::ReportProblemType(msg_type) => {
let kind = match msg_type {
Expand Down Expand Up @@ -145,6 +157,7 @@ impl DelayedSerde for AriesMessage {
Self::Connection(v) => v.delayed_serialize(serializer),
Self::Revocation(v) => v.delayed_serialize(serializer),
Self::CredentialIssuance(v) => v.delayed_serialize(serializer),
Self::CredentialIssuanceV2(v) => v.delayed_serialize(serializer),
Self::ReportProblem(v) => MsgWithType::from(v).serialize(serializer),
Self::PresentProof(v) => v.delayed_serialize(serializer),
Self::TrustPing(v) => v.delayed_serialize(serializer),
Expand Down
6 changes: 6 additions & 0 deletions messages/src/msg_fields/protocols/cred_issuance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod offer_credential;
pub mod problem_report;
pub mod propose_credential;
pub mod request_credential;
pub mod v2;

use std::str::FromStr;

Expand Down Expand Up @@ -58,6 +59,11 @@ impl DelayedSerde for CredentialIssuance {
let (protocol, kind_str) = msg_type;
let kind = match protocol {
CredentialIssuanceKind::V1(CredentialIssuanceTypeV1::V1_0(kind)) => kind.kind_from_str(kind_str),
CredentialIssuanceKind::V2(_) => {
return Err(D::Error::custom(
"Cannot deserialize issue-credential-v2 message type into issue-credential-v1",
))
}
};

match kind.map_err(D::Error::custom)? {
Expand Down
78 changes: 78 additions & 0 deletions messages/src/msg_fields/protocols/cred_issuance/v2/ack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

use crate::{
msg_fields::protocols::notification::ack::{Ack, AckContent, AckDecorators},
msg_parts::MsgParts,
};

pub type AckCredentialV2 = MsgParts<AckCredentialV2Content, AckDecorators>;

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)]
#[serde(transparent)]
pub struct AckCredentialV2Content {
pub inner: AckContent,
}

impl From<AckContent> for AckCredentialV2Content {
fn from(value: AckContent) -> Self {
Self { inner: value }
}
}

impl From<AckCredentialV2> for Ack {
fn from(value: AckCredentialV2) -> Self {
Self::builder()
.id(value.id)
.content(value.content.inner)
.decorators(value.decorators)
.build()
}
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use serde_json::json;

use super::*;
use crate::{
decorators::{thread::tests::make_extended_thread, timing::tests::make_extended_timing},
misc::test_utils,
msg_fields::protocols::{notification::ack::AckStatus, cred_issuance::ack::AckCredentialContent},
msg_types::cred_issuance::CredentialIssuanceTypeV1_0,
};

#[test]
fn test_minimal_ack_cred() {
let content: AckCredentialContent = AckContent::builder().status(AckStatus::Ok).build();

let decorators = AckDecorators::builder().thread(make_extended_thread()).build();

let expected = json!({
"status": content.inner.status,
"~thread": decorators.thread
});

test_utils::test_msg(content, decorators, CredentialIssuanceTypeV1_0::Ack, expected);
}

#[test]
fn test_extended_ack_cred() {
let content: AckCredentialContent = AckContent::builder().status(AckStatus::Ok).build();

let decorators = AckDecorators::builder()
.thread(make_extended_thread())
.timing(make_extended_timing())
.build();

let expected = json!({
"status": content.inner.status,
"~thread": decorators.thread,
"~timing": decorators.timing
});

test_utils::test_msg(content, decorators, CredentialIssuanceTypeV1_0::Ack, expected);
}
}
135 changes: 135 additions & 0 deletions messages/src/msg_fields/protocols/cred_issuance/v2/issue_credential.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use serde::{Deserialize, Serialize};
use typed_builder::TypedBuilder;

use crate::{
decorators::{attachment::Attachment, please_ack::PleaseAck, thread::Thread, timing::Timing},
msg_parts::MsgParts,
};

use super::AttachmentFormatSpecifier;

pub type IssueCredentialV2 = MsgParts<IssueCredentialV2Content, IssueCredentialV2Decorators>;

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)]
pub struct IssueCredentialV2Content {
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pub goal_code: Option<String>,
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pub replacement_id: Option<String>,
#[builder(default, setter(strip_option))]
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub formats: Vec<AttachmentFormatSpecifier<IssueCredentialAttachmentFormatType>>,
#[serde(rename = "credentials~attach")]
pub credentials_attach: Vec<Attachment>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, TypedBuilder)]
pub struct IssueCredentialV2Decorators {
#[serde(rename = "~thread")]
pub thread: Thread,
#[builder(default, setter(strip_option))]
#[serde(rename = "~please_ack")]
#[serde(skip_serializing_if = "Option::is_none")]
pub please_ack: Option<PleaseAck>,
#[builder(default, setter(strip_option))]
#[serde(rename = "~timing")]
#[serde(skip_serializing_if = "Option::is_none")]
pub timing: Option<Timing>,
}

#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum IssueCredentialAttachmentFormatType {
#[serde(rename = "aries/[email protected]")]
AriesLdProofVcDetail1_0,
gmulhearn-anonyome marked this conversation as resolved.
Show resolved Hide resolved
#[serde(rename = "hlindy/[email protected]")]
HyperledgerIndyCredential2_0,
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use serde_json::json;
use shared_vcx::maybe_known::MaybeKnown;

use super::*;
use crate::{
decorators::{
attachment::tests::make_extended_attachment, please_ack::tests::make_minimal_please_ack,
thread::tests::make_extended_thread, timing::tests::make_extended_timing,
},
misc::test_utils,
msg_types::cred_issuance::CredentialIssuanceTypeV2_0,
};

#[test]
fn test_minimal_issue_cred() {
let content = IssueCredentialV2Content::builder()
.formats(vec![AttachmentFormatSpecifier {
attach_id: "1".to_owned(),
format: MaybeKnown::Known(IssueCredentialAttachmentFormatType::HyperledgerIndyCredential2_0),
}])
.credentials_attach(vec![make_extended_attachment()])
.build();

let decorators = IssueCredentialV2Decorators::builder()
.thread(make_extended_thread())
.build();

let expected = json!({
"formats": content.formats,
"credentials~attach": content.credentials_attach,
"~thread": decorators.thread
});

test_utils::test_msg(
content,
decorators,
CredentialIssuanceTypeV2_0::IssueCredential,
expected,
);
}

#[test]
fn test_extended_issue_cred() {
let content = IssueCredentialV2Content::builder()
.formats(vec![AttachmentFormatSpecifier {
attach_id: "1".to_owned(),
format: shared_vcx::maybe_known::MaybeKnown::Known(
IssueCredentialAttachmentFormatType::HyperledgerIndyCredential2_0,
),
}])
.credentials_attach(vec![make_extended_attachment()])
.goal_code("goal.goal".to_owned())
.replacement_id("replacement-123".to_owned())
.comment("test_comment".to_owned())
.build();

let decorators = IssueCredentialV2Decorators::builder()
.thread(make_extended_thread())
.timing(make_extended_timing())
.please_ack(make_minimal_please_ack())
.build();

let expected = json!({
"formats": content.formats,
"credentials~attach": content.credentials_attach,
"goal_code": content.goal_code,
"replacement_id": content.replacement_id,
"comment": content.comment,
"~thread": decorators.thread,
"~timing": decorators.timing,
"~please_ack": decorators.please_ack
});

test_utils::test_msg(
content,
decorators,
CredentialIssuanceTypeV2_0::IssueCredential,
expected,
);
}
}
Loading
Loading