csfloat_rs/models/
schema.rs

1//! Schema models for CSFloat's undocumented `/schema` endpoint
2//! 
3//! This endpoint provides weapon pricing data including average prices for different
4//! wear conditions, volume data, and information about StatTrak and Souvenir variants.
5//! 
6//! **Note**: This is an undocumented endpoint and may change without notice.
7
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11/// Response from the /schema endpoint containing weapon pricing data
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SchemaResponse {
14    pub agents: Option<serde_json::Value>,
15    pub collectibles: Option<serde_json::Value>,
16    pub collections: Vec<Collection>,
17    pub containers: Option<serde_json::Value>,
18    pub custom_stickers: Option<serde_json::Value>,
19    pub highlight_reels: Option<serde_json::Value>,
20    pub keychains: Option<serde_json::Value>,
21    pub music_kits: Option<serde_json::Value>,
22    pub rarities: Option<serde_json::Value>,
23    pub stickers: Option<serde_json::Value>,
24    pub weapons: HashMap<String, Weapon>,
25}
26
27/// Collection information
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct Collection {
30    pub key: String,
31    pub name: String,
32    pub has_souvenir: Option<bool>,
33    pub has_crate: Option<bool>,
34}
35
36/// Weapon information with paints and pricing
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct Weapon {
39    pub name: String,
40    pub sticker_amount: u32,
41    #[serde(rename = "type")]
42    pub weapon_type: String,
43    pub faction: Option<String>,
44    pub paints: HashMap<String, Paint>,
45}
46
47/// Paint/skin information with pricing data
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Paint {
50    pub index: u32,
51    pub max: f64,
52    pub min: f64,
53    pub rarity: u32,
54    pub name: String,
55    pub collection: Option<String>,
56    pub image: String,
57    pub stattrak: Option<bool>,
58    pub souvenir: Option<bool>,
59    /// Normal prices by wear condition [Factory New, Minimal Wear, Field-Tested, Well-Worn, Battle-Scarred]
60    pub normal_prices: Vec<u64>,
61    /// Normal volumes by wear condition [Factory New, Minimal Wear, Field-Tested, Well-Worn, Battle-Scarred]
62    pub normal_volume: Vec<u64>,
63    /// StatTrak prices by wear condition (if applicable)
64    pub stattrak_prices: Option<Vec<u64>>,
65    /// StatTrak volumes by wear condition (if applicable)
66    pub stattrak_volume: Option<Vec<u64>>,
67}
68
69/// Wear condition index for accessing price arrays
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum WearCondition {
72    FactoryNew = 0,
73    MinimalWear = 1,
74    FieldTested = 2,
75    WellWorn = 3,
76    BattleScarred = 4,
77}
78
79impl WearCondition {
80    /// Get the index for this wear condition in price arrays
81    pub fn index(self) -> usize {
82        self as usize
83    }
84
85    /// Get the string representation of this wear condition
86    pub fn as_str(self) -> &'static str {
87        match self {
88            WearCondition::FactoryNew => "Factory New",
89            WearCondition::MinimalWear => "Minimal Wear",
90            WearCondition::FieldTested => "Field-Tested",
91            WearCondition::WellWorn => "Well-Worn",
92            WearCondition::BattleScarred => "Battle-Scarred",
93        }
94    }
95}
96
97impl Paint {
98    /// Get the price for a specific wear condition (normal skin)
99    pub fn price_for_wear(&self, wear: WearCondition) -> Option<u64> {
100        self.normal_prices.get(wear.index()).copied()
101    }
102
103    /// Get the volume for a specific wear condition (normal skin)
104    pub fn volume_for_wear(&self, wear: WearCondition) -> Option<u64> {
105        self.normal_volume.get(wear.index()).copied()
106    }
107
108    /// Get the StatTrak price for a specific wear condition
109    pub fn stattrak_price_for_wear(&self, wear: WearCondition) -> Option<u64> {
110        self.stattrak_prices
111            .as_ref()
112            .and_then(|prices| prices.get(wear.index()).copied())
113    }
114
115    /// Get the StatTrak volume for a specific wear condition
116    pub fn stattrak_volume_for_wear(&self, wear: WearCondition) -> Option<u64> {
117        self.stattrak_volume
118            .as_ref()
119            .and_then(|volumes| volumes.get(wear.index()).copied())
120    }
121
122    /// Check if this paint has StatTrak variants
123    pub fn has_stattrak(&self) -> bool {
124        self.stattrak.unwrap_or(false)
125    }
126
127    /// Check if this paint has Souvenir variants
128    pub fn has_souvenir(&self) -> bool {
129        self.souvenir.unwrap_or(false)
130    }
131
132    /// Get the float range for this paint
133    pub fn float_range(&self) -> (f64, f64) {
134        (self.min, self.max)
135    }
136}
137
138impl Weapon {
139    /// Get a paint by its index
140    pub fn get_paint_by_index(&self, index: u32) -> Option<&Paint> {
141        self.paints.values().find(|paint| paint.index == index)
142    }
143
144    /// Get a paint by its name
145    pub fn get_paint_by_name(&self, name: &str) -> Option<&Paint> {
146        self.paints
147            .values()
148            .find(|paint| paint.name.to_lowercase() == name.to_lowercase())
149    }
150
151    /// Get all paints for this weapon sorted by rarity (highest first)
152    pub fn paints_by_rarity(&self) -> Vec<&Paint> {
153        let mut paints: Vec<&Paint> = self.paints.values().collect();
154        paints.sort_by(|a, b| b.rarity.cmp(&a.rarity));
155        paints
156    }
157}
158
159impl SchemaResponse {
160    /// Get a weapon by its ID
161    pub fn get_weapon(&self, weapon_id: &str) -> Option<&Weapon> {
162        self.weapons.get(weapon_id)
163    }
164
165    /// Get a weapon by its name
166    pub fn get_weapon_by_name(&self, name: &str) -> Option<(String, &Weapon)> {
167        self.weapons
168            .iter()
169            .find(|(_, weapon)| weapon.name.to_lowercase() == name.to_lowercase())
170            .map(|(id, weapon)| (id.clone(), weapon))
171    }
172
173    /// Get all weapons sorted by name
174    pub fn weapons_by_name(&self) -> Vec<(String, &Weapon)> {
175        let mut weapons: Vec<(String, &Weapon)> = 
176            self.weapons.iter().map(|(id, weapon)| (id.clone(), weapon)).collect();
177        weapons.sort_by(|a, b| a.1.name.cmp(&b.1.name));
178        weapons
179    }
180
181    /// Find a specific paint across all weapons
182    pub fn find_paint(&self, weapon_name: &str, paint_name: &str) -> Option<(&Weapon, &Paint)> {
183        self.get_weapon_by_name(weapon_name)
184            .and_then(|(_, weapon)| {
185                weapon.get_paint_by_name(paint_name)
186                    .map(|paint| (weapon, paint))
187            })
188    }
189}