htb_cli/models/
sherlock.rs1use serde::{Deserialize, Serialize};
2
3use crate::output::Tabular;
4
5#[derive(Debug, Deserialize, Serialize)]
6pub struct Sherlock {
7 pub id: u64,
8 pub name: String,
9 #[serde(default)]
10 pub difficulty: Option<String>,
11 #[serde(default)]
12 pub state: Option<String>,
13 #[serde(default)]
14 pub category_id: Option<u64>,
15 #[serde(default)]
16 pub category_name: Option<String>,
17 #[serde(default)]
18 pub solves: u32,
19 #[serde(default)]
20 pub is_owned: bool,
21 #[serde(default)]
22 pub rating: Option<f64>,
23 #[serde(default)]
24 pub rating_count: Option<u32>,
25 #[serde(default)]
26 pub progress: Option<u32>,
27 #[serde(default)]
28 pub release_date: Option<String>,
29 #[serde(default)]
30 pub play_methods: Vec<String>,
31 #[serde(default)]
32 pub labels: Vec<super::machine::Label>,
33}
34
35#[derive(Debug, Deserialize)]
36pub struct SherlockCategoriesResponse {
37 pub info: Vec<SherlockCategory>,
38}
39
40#[derive(Debug, Deserialize, Serialize)]
41pub struct SherlockCategory {
42 pub id: u64,
43 pub name: String,
44 #[serde(default)]
45 pub icon: Option<String>,
46}
47
48impl Tabular for Sherlock {
49 fn headers() -> Vec<&'static str> {
50 vec![
51 "ID",
52 "Name",
53 "Difficulty",
54 "Category",
55 "Solves",
56 "Rating",
57 "Progress",
58 "State",
59 ]
60 }
61
62 fn row(&self) -> Vec<String> {
63 let progress = self.progress.map(|p| format!("{p}%")).unwrap_or_default();
64 vec![
65 self.id.to_string(),
66 self.name.clone(),
67 self.difficulty.clone().unwrap_or_default(),
68 self.category_name.clone().unwrap_or_default(),
69 self.solves.to_string(),
70 self.rating.map(|r| format!("{r:.1}")).unwrap_or_default(),
71 progress,
72 self.state.clone().unwrap_or_default(),
73 ]
74 }
75}
76
77impl Tabular for SherlockCategory {
78 fn headers() -> Vec<&'static str> {
79 vec!["ID", "Name"]
80 }
81
82 fn row(&self) -> Vec<String> {
83 vec![self.id.to_string(), self.name.clone()]
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90 use crate::models::Paginated;
91
92 #[test]
93 fn deserialize_sherlocks_list() {
94 let json = include_str!("../../tests/fixtures/sherlocks-list.json");
95 let result: Paginated<Sherlock> = serde_json::from_str(json).unwrap();
96 assert!(!result.data.is_empty());
97 assert_eq!(result.data[0].name, "Brutus");
98 assert_eq!(result.data[0].category_name.as_deref(), Some("DFIR"));
99 }
100
101 #[test]
102 fn deserialize_sherlock_categories() {
103 let json = include_str!("../../tests/fixtures/sherlocks-categories.json");
104 let result: SherlockCategoriesResponse = serde_json::from_str(json).unwrap();
105 assert!(!result.info.is_empty());
106 assert!(result.info.iter().any(|c| c.name == "DFIR"));
107 }
108}