use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[non_exhaustive]
#[repr(i32)]
pub enum ConfirmationType {
Generic = 1,
Trade = 2,
MarketSell = 3,
#[default]
Unknown = 0,
}
impl From<i32> for ConfirmationType {
fn from(value: i32) -> Self {
match value {
1 => Self::Generic,
2 => Self::Trade,
3 => Self::MarketSell,
_ => Self::Unknown,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Confirmation {
pub id: String,
#[serde(alias = "type", default)]
pub conf_type: ConfirmationType,
#[serde(alias = "creator_id", default)]
pub creator: String,
#[serde(alias = "nonce", default)]
pub key: String,
#[serde(alias = "headline", default)]
pub title: String,
#[serde(default)]
pub receiving: String,
#[serde(default)]
pub sending: String,
#[serde(default)]
pub summary: Vec<String>,
#[serde(default)]
pub time: String,
#[serde(alias = "creation_time", default)]
pub timestamp: u64,
#[serde(default)]
pub icon: String,
#[serde(default)]
pub type_name: Option<String>,
}
impl Confirmation {
pub fn from_api(data: &serde_json::Value) -> Result<Self, crate::error::SteamUserError> {
use crate::error::SteamUserError;
let id = data.get("id").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'id'".into()))?.to_string().trim_matches('"').to_string();
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()))?;
let type_i32 = i32::try_from(type_i64).map_err(|_| SteamUserError::MalformedResponse(format!("Confirmation: 'type' {} out of i32 range", type_i64)))?;
let conf_type = ConfirmationType::from(type_i32);
let creator = data.get("creator_id").ok_or_else(|| SteamUserError::MalformedResponse("Confirmation: missing 'creator_id'".into()))?.to_string().trim_matches('"').to_string();
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();
let type_name = data.get("type_name").and_then(|v| v.as_str()).unwrap_or("Confirm");
let headline = data.get("headline").and_then(|v| v.as_str()).unwrap_or("");
let title = format!("{} - {}", type_name, headline);
let summary = data.get("summary").and_then(|v| v.as_array());
let sending = summary.and_then(|arr| arr.first()).and_then(|v| v.as_str()).unwrap_or("").to_string();
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() };
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()))?;
let time = chrono_timestamp_to_iso(creation_time);
let icon = data.get("icon").and_then(|v| v.as_str()).unwrap_or("").to_string();
let type_name_str = Some(type_name.to_string());
let summary_arr = summary.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect()).unwrap_or_default();
Ok(Self { id, conf_type, creator, key, title, receiving, sending, summary: summary_arr, time, timestamp: creation_time, icon, type_name: type_name_str })
}
pub fn offer_id(&self) -> Option<&str> {
if self.conf_type == ConfirmationType::Trade {
Some(&self.creator)
} else {
None
}
}
}
fn chrono_timestamp_to_iso(timestamp: u64) -> String {
use std::time::{Duration, UNIX_EPOCH};
let datetime = UNIX_EPOCH + Duration::from_secs(timestamp);
format!("{:?}", datetime)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_confirmation_type_from() {
assert_eq!(ConfirmationType::from(1), ConfirmationType::Generic);
assert_eq!(ConfirmationType::from(2), ConfirmationType::Trade);
assert_eq!(ConfirmationType::from(3), ConfirmationType::MarketSell);
assert_eq!(ConfirmationType::from(99), ConfirmationType::Unknown);
}
#[test]
fn test_confirmation_from_api() {
let json = serde_json::json!({
"id": "12345",
"type": 2,
"creator_id": "67890",
"nonce": "abc123",
"type_name": "Trade",
"headline": "Test Trade",
"summary": ["1 Key", "2 Ref"],
"creation_time": 1609459200,
"icon": "https://example.com/icon.png"
});
let conf = Confirmation::from_api(&json).unwrap();
assert_eq!(conf.id, "12345");
assert_eq!(conf.conf_type, ConfirmationType::Trade);
assert_eq!(conf.creator, "67890");
assert_eq!(conf.sending, "1 Key");
assert_eq!(conf.receiving, "2 Ref");
assert_eq!(conf.offer_id(), Some("67890"));
}
}