/ src / battlenet / data / starcraft2 / achievement.rs
achievement.rs
  1  use std::collections::HashMap;
  2  use chrono::{DateTime, Utc};
  3  use serde::{Deserialize, Serialize};
  4  use crate::battlenet::platform::data::starcraft2::profile::metadata::AchievementMetadata;
  5  use crate::battlenet::platform::data::starcraft2::profile::profile::EarnedAchievement;
  6  
  7  #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
  8  pub struct Sc2Achievement
  9  {
 10  	pub description: String,
 11  	pub displayOrder: u64,
 12  	pub id: u64,
 13  	pub name: String,
 14  	pub points: u64,
 15  	pub unlocked: bool,
 16  	pub unlockedTimestamp: Option<DateTime<Utc>>,
 17  }
 18  
 19  impl From<AchievementMetadata> for Sc2Achievement
 20  {
 21  	fn from(value: AchievementMetadata) -> Self
 22  	{
 23  		return Self
 24  		{
 25  			description: value.description.clone(),
 26  			displayOrder: value.uiOrderHint,
 27  			
 28  			id: value.id.parse::<u64>()
 29  				.unwrap_or_default(),
 30  			
 31  			name: value.title.clone(),
 32  			points: value.points,
 33  			..Default::default()
 34  		};
 35  	}
 36  }
 37  
 38  impl Sc2Achievement
 39  {
 40  	/**
 41  	Process the achievements metadata returned by the Profile/Static endpoint.
 42  	
 43  	Returns a map pairing each `Sc2Achievement` with its associated category id.
 44  	*/
 45  	pub fn processMetadata(metadata: Vec<AchievementMetadata>) -> HashMap<String, Self>
 46  	{
 47  		let mut map = HashMap::new();
 48  		
 49  		for metaChievo in metadata
 50  		{
 51  			map.insert(
 52  				metaChievo.categoryId.clone(),
 53  				Self::from(metaChievo),
 54  			);
 55  		}
 56  		
 57  		return map;
 58  	}
 59  	
 60  	pub fn update(&mut self, value: EarnedAchievement)
 61  	{
 62  		self.unlocked = value.isComplete;
 63  		self.unlockedTimestamp = DateTime::from_timestamp(value.completionDate as i64, 0);
 64  	}
 65  }
 66  
 67  #[cfg(test)]
 68  mod tests
 69  {
 70  	use std::str::FromStr;
 71  
 72  use super::*;
 73  	
 74  	const JsonPayload: &str = r#"
 75  [
 76  	{
 77  		"categoryId":"1",
 78  		"chainAchievementIds":["2"],
 79  		"chainRewardSize":1,
 80  		"criteriaIds":["3"],
 81  		"description":"Achievement id 4 description",
 82  		"flags":2,
 83  		"id":"4",
 84  		
 85  		"imageUrl":"someurl",
 86  		
 87  		"isChained":false,
 88  		"points":0,
 89  		"title":"Achievement id 4",
 90  		"uiOrderHint":8
 91  	},
 92  	
 93  	{
 94  		"categoryId":"5",
 95  		"chainAchievementIds":["6"],
 96  		"chainRewardSize":0,
 97  		"criteriaIds":["7"],
 98  		"description":"Achievement id 8 description",
 99  		"flags":2,
100  		"id":"8",
101  		"imageUrl":"someurl",
102  		"isChained":false,
103  		"points":0,
104  		"title":"Achievement id 8",
105  		"uiOrderHint":12
106  	},
107  	
108  	{
109  		"categoryId":"9",
110  		"chainAchievementIds":["10"],
111  		"chainRewardSize":0,
112  		"criteriaIds":[
113  			"11",
114  			"12",
115  			"13"
116  		],
117  		"description":"Achievement id 14 description",
118  		"flags":0,
119  		"id":"14",
120  		"imageUrl":"someurl",
121  		"isChained":false,
122  		"points":10,
123  		"title":"Achievement id 14",
124  		"uiOrderHint":27
125  	}
126  ]
127  "#;
128  	
129  	const EarnedJson: &str = r#"
130  {
131  	"achievementId":"9",
132  	"completionDate":1765083932,
133  	"numCompletedAchievementsInSeries":10,
134  	"totalAchievementsInSeries":10,
135  	"isComplete":true,
136  	"inProgress":false,
137  	"criteria":[
138  		{"criterionId":"25","earned":{"quantity":1,"startTime":1}}
139  	]
140  }
141  "#;
142  	
143  	#[test]
144  	fn processMetadata()
145  	{
146  		let metadata = serde_json::from_str(JsonPayload);
147  		
148  		assert!(metadata.is_ok());
149  		let metadata = metadata.unwrap();
150  		
151  		let achievements = Sc2Achievement::processMetadata(metadata);
152  		assert!(!achievements.is_empty());
153  		assert_eq!(achievements.len(), 3);
154  		assert!(achievements.keys().all(|k| !k.is_empty()));
155  		
156  		let achievement = achievements.get("9");
157  		assert!(achievement.is_some());
158  		let achievement = achievement.unwrap();
159  		assert_eq!(achievement.id, 14);
160  		assert_eq!(&achievement.description, "Achievement id 14 description");
161  		assert_eq!(achievement.displayOrder, 27);
162  		assert_eq!(&achievement.name, "Achievement id 14");
163  		assert_eq!(achievement.points, 10);
164  		assert!(!achievement.unlocked);
165  	}
166  	
167  	#[test]
168  	fn update()
169  	{
170  		let dateTime = DateTime::parse_from_str(
171  			"20251207T050532+0000",
172  			"%Y%m%dT%H%M%S%z"
173  		)
174  			.unwrap()
175  			.naive_utc();
176  		
177  		let metadata = serde_json::from_str(EarnedJson);
178  		assert!(metadata.is_ok());
179  		
180  		let mut achievement = Sc2Achievement::default();
181  		assert!(!achievement.unlocked);
182  		assert_eq!(achievement.unlockedTimestamp, None);
183  		
184  		achievement.update(metadata.unwrap());
185  		assert!(achievement.unlocked);
186  		assert_eq!(achievement.unlockedTimestamp.unwrap().naive_utc(), dateTime);
187  	}
188  }