tap_msg/message/
presentation.rs

1//! Presentation and RequestPresentation message types for the Transaction Authorization Protocol.
2//!
3//! This module defines the Presentation and RequestPresentation message types, which
4//! are used for requesting and submitting verifiable credentials in the TAP protocol.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use crate::error::Result;
10use crate::impl_tap_message;
11use crate::message::tap_message_trait::TapMessageBody;
12
13/// Request Presentation message body (TAIP-10).
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct RequestPresentation {
16    /// Transfer ID that this request is related to.
17    pub transaction_id: String,
18
19    /// Presentation definition identifier or URI.
20    pub presentation_definition: String,
21
22    /// Description of the request.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub description: Option<String>,
25
26    /// Challenge to be included in the response.
27    pub challenge: String,
28
29    /// Whether the request is for the originator's information.
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub for_originator: Option<bool>,
32
33    /// Whether the request is for the beneficiary's information.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub for_beneficiary: Option<bool>,
36
37    /// Note for the request.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub note: Option<String>,
40
41    /// Additional metadata.
42    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
43    pub metadata: HashMap<String, serde_json::Value>,
44}
45
46impl TapMessageBody for RequestPresentation {
47    fn message_type() -> &'static str {
48        "https://tap.rsvp/schema/1.0#request-presentation"
49    }
50
51    fn validate(&self) -> Result<()> {
52        if self.transaction_id.is_empty() {
53            return Err(crate::error::Error::Validation(
54                "Transaction ID is required in RequestPresentation".to_string(),
55            ));
56        }
57
58        if self.presentation_definition.is_empty() {
59            return Err(crate::error::Error::Validation(
60                "Presentation definition is required in RequestPresentation".to_string(),
61            ));
62        }
63
64        if self.challenge.is_empty() {
65            return Err(crate::error::Error::Validation(
66                "Challenge is required in RequestPresentation".to_string(),
67            ));
68        }
69
70        Ok(())
71    }
72}
73
74impl_tap_message!(RequestPresentation);
75
76/// Presentation message body (TAIP-8, TAIP-10).
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct Presentation {
79    /// Challenge from the request.
80    pub challenge: String,
81
82    /// Credential data.
83    pub credentials: Vec<serde_json::Value>,
84
85    /// Transfer ID that this presentation is related to (optional).
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub transaction_id: Option<String>,
88
89    /// Identifier for this presentation (used for message_id)
90    pub id: String,
91
92    /// Additional metadata.
93    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
94    pub metadata: HashMap<String, serde_json::Value>,
95}
96
97impl Presentation {
98    /// Create a new Presentation
99    pub fn new(
100        challenge: String,
101        credentials: Vec<serde_json::Value>,
102        transaction_id: Option<String>,
103    ) -> Self {
104        Self {
105            challenge,
106            credentials,
107            transaction_id,
108            id: uuid::Uuid::new_v4().to_string(),
109            metadata: HashMap::new(),
110        }
111    }
112
113    /// Add metadata to the presentation
114    pub fn with_metadata(mut self, key: &str, value: serde_json::Value) -> Self {
115        self.metadata.insert(key.to_string(), value);
116        self
117    }
118}
119
120impl TapMessageBody for Presentation {
121    fn message_type() -> &'static str {
122        "https://didcomm.org/present-proof/3.0/presentation"
123    }
124
125    fn validate(&self) -> Result<()> {
126        if self.challenge.is_empty() {
127            return Err(crate::error::Error::Validation(
128                "Challenge is required in Presentation".to_string(),
129            ));
130        }
131
132        if self.credentials.is_empty() {
133            return Err(crate::error::Error::Validation(
134                "Credentials are required in Presentation".to_string(),
135            ));
136        }
137
138        Ok(())
139    }
140}
141
142impl_tap_message!(Presentation, optional_transaction_id);