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;
10
11/// Party in a transaction (TAIP-6).
12///
13/// Parties are identified using an IRI as the @id attribute in a JSON-LD object.
14/// They represent real-world entities (legal or natural persons) that are parties to a transaction.
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
16pub struct Party {
17    /// IRI of the party (DID, email, phone number, etc).
18    #[serde(rename = "@id")]
19    pub id: String,
20
21    /// Additional JSON-LD metadata for the party.
22    /// This allows for extensible metadata like country codes, LEI codes, MCC codes, etc.
23    /// Example: {"https://schema.org/addressCountry": "de", "lei": "..."}
24    #[serde(flatten)]
25    pub metadata: HashMap<String, serde_json::Value>,
26}
27
28impl TapParticipant for Party {
29    fn id(&self) -> &str {
30        &self.id
31    }
32}
33
34impl Party {
35    /// Create a new party with the given IRI.
36    pub fn new(id: &str) -> Self {
37        Self {
38            id: id.to_string(),
39            metadata: HashMap::new(),
40        }
41    }
42
43    /// Create a new party with metadata.
44    pub fn with_metadata(id: &str, metadata: HashMap<String, serde_json::Value>) -> Self {
45        Self {
46            id: id.to_string(),
47            metadata,
48        }
49    }
50
51    /// Add a metadata field to the party.
52    pub fn add_metadata(&mut self, key: String, value: serde_json::Value) {
53        self.metadata.insert(key, value);
54    }
55
56    /// Add country code metadata.
57    pub fn with_country(mut self, country_code: &str) -> Self {
58        self.metadata.insert(
59            "https://schema.org/addressCountry".to_string(),
60            serde_json::Value::String(country_code.to_string()),
61        );
62        self
63    }
64
65    /// Add LEI code metadata.
66    pub fn with_lei(mut self, lei_code: &str) -> Self {
67        self.metadata.insert(
68            "https://schema.org/leiCode".to_string(),
69            serde_json::Value::String(lei_code.to_string()),
70        );
71        self
72    }
73
74    /// Add merchant category code (MCC) metadata.
75    pub fn with_mcc(mut self, mcc: &str) -> Self {
76        self.metadata.insert(
77            "mcc".to_string(),
78            serde_json::Value::String(mcc.to_string()),
79        );
80        self
81    }
82
83    /// Get a metadata value by key.
84    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
85        self.metadata.get(key)
86    }
87
88    /// Get country code if present.
89    pub fn country(&self) -> Option<String> {
90        self.get_metadata("https://schema.org/addressCountry")
91            .and_then(|v| v.as_str())
92            .map(|s| s.to_string())
93    }
94
95    /// Get LEI code if present.
96    pub fn lei_code(&self) -> Option<String> {
97        self.get_metadata("https://schema.org/leiCode")
98            .and_then(|v| v.as_str())
99            .map(|s| s.to_string())
100    }
101
102    /// Get MCC code if present.
103    pub fn mcc(&self) -> Option<String> {
104        self.get_metadata("mcc")
105            .and_then(|v| v.as_str())
106            .map(|s| s.to_string())
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113    use serde_json;
114
115    #[test]
116    fn test_party_creation() {
117        let party = Party::new("did:example:alice");
118        assert_eq!(party.id, "did:example:alice");
119        assert!(party.metadata.is_empty());
120    }
121
122    #[test]
123    fn test_party_with_metadata() {
124        let mut metadata = HashMap::new();
125        metadata.insert(
126            "name".to_string(),
127            serde_json::Value::String("Alice".to_string()),
128        );
129
130        let party = Party::with_metadata("did:example:alice", metadata);
131        assert_eq!(party.id, "did:example:alice");
132        assert_eq!(
133            party.get_metadata("name").unwrap().as_str().unwrap(),
134            "Alice"
135        );
136    }
137
138    #[test]
139    fn test_party_with_country() {
140        let party = Party::new("did:example:alice").with_country("de");
141        assert_eq!(party.country().unwrap(), "de");
142    }
143
144    #[test]
145    fn test_party_with_lei() {
146        let party = Party::new("did:web:example.com").with_lei("LEI123456789");
147        assert_eq!(party.lei_code().unwrap(), "LEI123456789");
148    }
149
150    #[test]
151    fn test_party_with_mcc() {
152        let party = Party::new("did:web:merchant.com").with_mcc("5812");
153        assert_eq!(party.mcc().unwrap(), "5812");
154    }
155
156    #[test]
157    fn test_party_serialization() {
158        let party = Party::new("did:example:alice")
159            .with_country("de")
160            .with_lei("LEI123");
161
162        let json = serde_json::to_string(&party).unwrap();
163        let deserialized: Party = serde_json::from_str(&json).unwrap();
164
165        assert_eq!(party, deserialized);
166        assert_eq!(deserialized.country().unwrap(), "de");
167        assert_eq!(deserialized.lei_code().unwrap(), "LEI123");
168    }
169
170    #[test]
171    fn test_party_json_ld_format() {
172        let party = Party::new("did:example:alice").with_country("de");
173        let json = serde_json::to_value(&party).unwrap();
174
175        assert_eq!(json["@id"], "did:example:alice");
176        assert_eq!(json["https://schema.org/addressCountry"], "de");
177    }
178}