tap_msg/message/
party.rs

1//! Party types for TAP messages (TAIP-6).
2//!
3//! This module defines the structure of party information used in TAP messages.
4//! Parties are the real-world entities involved with a transaction - legal or natural persons.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9use crate::message::agent::TapParticipant;
10use crate::utils::NameHashable;
11
12/// Party in a transaction (TAIP-6).
13///
14/// Parties are identified using an IRI as the @id attribute in a JSON-LD object.
15/// They represent real-world entities (legal or natural persons) that are parties to a transaction.
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub struct Party {
18    /// IRI of the party (DID, email, phone number, etc).
19    #[serde(rename = "@id")]
20    pub id: String,
21
22    /// Additional JSON-LD metadata for the party.
23    /// This allows for extensible metadata like country codes, LEI codes, MCC codes, etc.
24    /// Example: {"https://schema.org/addressCountry": "de", "lei": "..."}
25    #[serde(flatten)]
26    pub metadata: HashMap<String, serde_json::Value>,
27}
28
29impl TapParticipant for Party {
30    fn id(&self) -> &str {
31        &self.id
32    }
33}
34
35impl Party {
36    /// Create a new party with the given IRI.
37    pub fn new(id: &str) -> Self {
38        Self {
39            id: id.to_string(),
40            metadata: HashMap::new(),
41        }
42    }
43
44    /// Create a new party with metadata.
45    pub fn with_metadata(id: &str, metadata: HashMap<String, serde_json::Value>) -> Self {
46        Self {
47            id: id.to_string(),
48            metadata,
49        }
50    }
51
52    /// Add a metadata field to the party.
53    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
54        self.metadata.insert(key, value);
55    }
56
57    /// Add country code metadata.
58    pub fn with_country(mut self, country_code: &str) -> Self {
59        self.metadata.insert(
60            "https://schema.org/addressCountry".to_string(),
61            serde_json::Value::String(country_code.to_string()),
62        );
63        self
64    }
65
66    /// Add LEI code metadata.
67    pub fn with_lei(mut self, lei_code: &str) -> Self {
68        self.metadata.insert(
69            "https://schema.org/leiCode".to_string(),
70            serde_json::Value::String(lei_code.to_string()),
71        );
72        self
73    }
74
75    /// Add merchant category code (MCC) metadata.
76    pub fn with_mcc(mut self, mcc: &str) -> Self {
77        self.metadata.insert(
78            "mcc".to_string(),
79            serde_json::Value::String(mcc.to_string()),
80        );
81        self
82    }
83
84    /// Get a metadata value by key.
85    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
86        self.metadata.get(key)
87    }
88
89    /// Get country code if present.
90    pub fn country(&self) -> Option<String> {
91        self.get_metadata("https://schema.org/addressCountry")
92            .and_then(|v| v.as_str())
93            .map(|s| s.to_string())
94    }
95
96    /// Get LEI code if present.
97    pub fn lei_code(&self) -> Option<String> {
98        self.get_metadata("https://schema.org/leiCode")
99            .and_then(|v| v.as_str())
100            .map(|s| s.to_string())
101    }
102
103    /// Get MCC code if present.
104    pub fn mcc(&self) -> Option<String> {
105        self.get_metadata("mcc")
106            .and_then(|v| v.as_str())
107            .map(|s| s.to_string())
108    }
109
110    /// Add name hash metadata according to TAIP-12.
111    pub fn with_name_hash(mut self, name: &str) -> Self {
112        let hash = Self::hash_name(name);
113        self.metadata
114            .insert("nameHash".to_string(), serde_json::Value::String(hash));
115        self
116    }
117
118    /// Get name hash if present.
119    pub fn name_hash(&self) -> Option<String> {
120        self.get_metadata("nameHash")
121            .and_then(|v| v.as_str())
122            .map(|s| s.to_string())
123    }
124
125    /// Set name hash directly.
126    pub fn set_name_hash(&mut self, hash: String) {
127        self.metadata
128            .insert("nameHash".to_string(), serde_json::Value::String(hash));
129    }
130}
131
132// Implement NameHashable for Party
133impl NameHashable for Party {}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138    use serde_json;
139
140    #[test]
141    fn test_party_creation() {
142        let party = Party::new("did:example:alice");
143        assert_eq!(party.id, "did:example:alice");
144        assert!(party.metadata.is_empty());
145    }
146
147    #[test]
148    fn test_party_with_metadata() {
149        let mut metadata = HashMap::new();
150        metadata.insert(
151            "name".to_string(),
152            serde_json::Value::String("Alice".to_string()),
153        );
154
155        let party = Party::with_metadata("did:example:alice", metadata);
156        assert_eq!(party.id, "did:example:alice");
157        assert_eq!(
158            party.get_metadata("name").unwrap().as_str().unwrap(),
159            "Alice"
160        );
161    }
162
163    #[test]
164    fn test_party_with_country() {
165        let party = Party::new("did:example:alice").with_country("de");
166        assert_eq!(party.country().unwrap(), "de");
167    }
168
169    #[test]
170    fn test_party_with_lei() {
171        let party = Party::new("did:web:example.com").with_lei("LEI123456789");
172        assert_eq!(party.lei_code().unwrap(), "LEI123456789");
173    }
174
175    #[test]
176    fn test_party_with_mcc() {
177        let party = Party::new("did:web:merchant.com").with_mcc("5812");
178        assert_eq!(party.mcc().unwrap(), "5812");
179    }
180
181    #[test]
182    fn test_party_serialization() {
183        let party = Party::new("did:example:alice")
184            .with_country("de")
185            .with_lei("LEI123");
186
187        let json = serde_json::to_string(&party).unwrap();
188        let deserialized: Party = serde_json::from_str(&json).unwrap();
189
190        assert_eq!(party, deserialized);
191        assert_eq!(deserialized.country().unwrap(), "de");
192        assert_eq!(deserialized.lei_code().unwrap(), "LEI123");
193    }
194
195    #[test]
196    fn test_party_json_ld_format() {
197        let party = Party::new("did:example:alice").with_country("de");
198        let json = serde_json::to_value(&party).unwrap();
199
200        assert_eq!(json["@id"], "did:example:alice");
201        assert_eq!(json["https://schema.org/addressCountry"], "de");
202    }
203
204    #[test]
205    fn test_party_with_name_hash() {
206        let party = Party::new("did:example:alice").with_name_hash("Alice Lee");
207        assert_eq!(
208            party.name_hash().unwrap(),
209            "b117f44426c9670da91b563db728cd0bc8bafa7d1a6bb5e764d1aad2ca25032e"
210        );
211    }
212
213    #[test]
214    fn test_party_name_hash_serialization() {
215        let party = Party::new("did:example:alice")
216            .with_name_hash("Alice Lee")
217            .with_country("US");
218
219        let json = serde_json::to_value(&party).unwrap();
220        assert_eq!(json["@id"], "did:example:alice");
221        assert_eq!(
222            json["nameHash"],
223            "b117f44426c9670da91b563db728cd0bc8bafa7d1a6bb5e764d1aad2ca25032e"
224        );
225        assert_eq!(json["https://schema.org/addressCountry"], "US");
226    }
227}