steam_user/types/
confirmation.rs1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
7#[non_exhaustive]
8#[repr(i32)]
9pub enum ConfirmationType {
10 Generic = 1,
12 Trade = 2,
14 MarketSell = 3,
16 #[default]
18 Unknown = 0,
19}
20
21impl From<i32> for ConfirmationType {
22 fn from(value: i32) -> Self {
23 match value {
24 1 => Self::Generic,
25 2 => Self::Trade,
26 3 => Self::MarketSell,
27 _ => Self::Unknown,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34pub struct Confirmation {
35 pub id: String,
37 #[serde(alias = "type", default)]
39 pub conf_type: ConfirmationType,
40 #[serde(alias = "creator_id", default)]
42 pub creator: String,
43 #[serde(alias = "nonce", default)]
45 pub key: String,
46 #[serde(alias = "headline", default)]
48 pub title: String,
49 #[serde(default)]
51 pub receiving: String,
52 #[serde(default)]
54 pub sending: String,
55 #[serde(default)]
57 pub summary: Vec<String>,
58 #[serde(default)]
60 pub time: String,
61 #[serde(alias = "creation_time", default)]
63 pub timestamp: u64,
64 #[serde(default)]
66 pub icon: String,
67 #[serde(default)]
69 pub type_name: Option<String>,
70}
71
72impl Confirmation {
73 pub fn from_api(data: &serde_json::Value) -> Result<Self, crate::error::SteamUserError> {
80 use crate::error::SteamUserError;
81 let id = data.get("id").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'id'".into()))?.to_string().trim_matches('"').to_string();
82 let type_i64 = data.get("type").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'type'".into()))?.as_i64().ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: 'type' is not an integer".into()))?;
83 let type_i32 = i32::try_from(type_i64).map_err(|_| SteamUserError::MalformedResponse(format!("Confirmation: 'type' {} out of i32 range", type_i64)))?;
84 let conf_type = ConfirmationType::from(type_i32);
85 let creator = data.get("creator_id").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'creator_id'".into()))?.to_string().trim_matches('"').to_string();
86 let key = data.get("nonce").and_then(|v| v.as_str()).ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing or non-string 'nonce'".into()))?.to_string();
87
88 let type_name = data.get("type_name").and_then(|v| v.as_str()).unwrap_or("Confirm");
89 let headline = data.get("headline").and_then(|v| v.as_str()).unwrap_or("");
90 let title = format!("{} - {}", type_name, headline);
91
92 let summary = data.get("summary").and_then(|v| v.as_array());
93 let sending = summary.and_then(|arr| arr.first()).and_then(|v| v.as_str()).unwrap_or("").to_string();
94 let receiving = if conf_type == ConfirmationType::Trade { summary.and_then(|arr| arr.get(1)).and_then(|v| v.as_str()).unwrap_or("").to_string() } else { String::new() };
95
96 let creation_time = data.get("creation_time").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'creation_time'".into()))?.as_u64().ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: 'creation_time' is not a u64".into()))?;
97 let time = chrono_timestamp_to_iso(creation_time);
98
99 let icon = data.get("icon").and_then(|v| v.as_str()).unwrap_or("").to_string();
100
101 let type_name_str = Some(type_name.to_string());
102 let summary_arr = summary.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()).unwrap_or_default();
103
104 Ok(Self { id, conf_type, creator, key, title, receiving, sending, summary: summary_arr, time, timestamp: creation_time, icon, type_name: type_name_str })
105 }
106
107 pub fn offer_id(&self) -> Option<&str> {
109 if self.conf_type == ConfirmationType::Trade {
110 Some(&self.creator)
111 } else {
112 None
113 }
114 }
115}
116
117fn chrono_timestamp_to_iso(timestamp: u64) -> String {
119 use std::time::{Duration, UNIX_EPOCH};
120 let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
121 format!("{:?}", datetime)
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_confirmation_type_from() {
131 assert_eq!(ConfirmationType::from(1), ConfirmationType::Generic);
132 assert_eq!(ConfirmationType::from(2), ConfirmationType::Trade);
133 assert_eq!(ConfirmationType::from(3), ConfirmationType::MarketSell);
134 assert_eq!(ConfirmationType::from(99), ConfirmationType::Unknown);
135 }
136
137 #[test]
138 fn test_confirmation_from_api() {
139 let json = serde_json::json!({
140 "id": "12345",
141 "type": 2,
142 "creator_id": "67890",
143 "nonce": "abc123",
144 "type_name": "Trade",
145 "headline": "Test Trade",
146 "summary": ["1 Key", "2 Ref"],
147 "creation_time": 1609459200,
148 "icon": "https://example.com/icon.png"
149 });
150
151 let conf = Confirmation::from_api(&json).unwrap();
152 assert_eq!(conf.id, "12345");
153 assert_eq!(conf.conf_type, ConfirmationType::Trade);
154 assert_eq!(conf.creator, "67890");
155 assert_eq!(conf.sending, "1 Key");
156 assert_eq!(conf.receiving, "2 Ref");
157 assert_eq!(conf.offer_id(), Some("67890"));
158 }
159}