1#![cfg(feature = "serde")]
5
6use serde::{Deserialize, Deserializer};
7use serde_json::Value;
8use std::collections::HashMap;
9
10use crate::{ChartItem, CourseInfo, Trophy};
11
12pub(crate) fn deserialize_course_groups<'de, D>(
15 deserializer: D,
16) -> Result<Vec<Vec<CourseInfo>>, D::Error>
17where
18 D: Deserializer<'de>,
19{
20 let Some(Value::Array(arr)) = Option::<Value>::deserialize(deserializer)? else {
21 return Ok(Vec::new());
22 };
23 if arr.is_empty() {
24 return Ok(vec![Vec::new()]);
25 }
26
27 if matches!(arr.first(), Some(Value::Array(_))) {
28 serde_json::from_value::<Vec<Vec<CourseInfo>>>(Value::Array(arr))
29 .map_err(serde::de::Error::custom)
30 } else {
31 let inner: Vec<CourseInfo> =
32 serde_json::from_value(Value::Array(arr)).map_err(serde::de::Error::custom)?;
33 Ok(vec![inner])
34 }
35}
36
37pub(crate) fn deserialize_level_order<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
40where
41 D: Deserializer<'de>,
42{
43 let values = Option::<Vec<Value>>::deserialize(deserializer)?.unwrap_or_default();
44 Ok(values
45 .into_iter()
46 .map(|v| match v {
47 Value::Number(n) => n.to_string(),
48 Value::String(s) => s,
49 other => other.to_string(),
50 })
51 .collect())
52}
53
54#[derive(Deserialize)]
56struct CourseInfoRaw {
57 name: String,
58 #[serde(default)]
59 constraint: Vec<String>,
60 #[serde(default)]
61 trophy: Vec<Trophy>,
62 #[serde(default, rename = "md5")]
63 md5list: Vec<String>,
64 #[serde(default, rename = "sha256")]
65 sha256list: Vec<String>,
66 #[serde(default)]
67 charts: Vec<Value>,
68}
69
70impl TryFrom<CourseInfoRaw> for CourseInfo {
71 type Error = String;
72
73 fn try_from(raw: CourseInfoRaw) -> Result<Self, Self::Error> {
74 let mut charts: Vec<ChartItem> =
75 Vec::with_capacity(raw.charts.len() + raw.md5list.len() + raw.sha256list.len());
76
77 for mut chart_value in raw.charts {
79 if chart_value.get("level").is_none() {
80 let obj = chart_value
81 .as_object()
82 .ok_or_else(|| "chart_value is not an object".to_string())?
83 .clone();
84 let mut obj = obj;
85 obj.insert("level".to_string(), Value::String("0".to_string()));
86 chart_value = Value::Object(obj);
87 }
88 let item: ChartItem = serde_json::from_value(chart_value).map_err(|e| e.to_string())?;
89 charts.push(item);
90 }
91
92 charts.extend(raw.md5list.into_iter().map(|md5| ChartItem {
94 level: "0".to_string(),
95 md5: Some(md5),
96 sha256: None,
97 title: None,
98 subtitle: None,
99 artist: None,
100 subartist: None,
101 url: None,
102 url_diff: None,
103 extra: HashMap::new(),
104 }));
105
106 charts.extend(raw.sha256list.into_iter().map(|sha256| ChartItem {
108 level: "0".to_string(),
109 md5: None,
110 sha256: Some(sha256),
111 title: None,
112 subtitle: None,
113 artist: None,
114 subartist: None,
115 url: None,
116 url_diff: None,
117 extra: HashMap::new(),
118 }));
119
120 Ok(Self {
121 name: raw.name,
122 constraint: raw.constraint,
123 trophy: raw.trophy,
124 charts,
125 })
126 }
127}
128
129impl<'de> Deserialize<'de> for CourseInfo {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: Deserializer<'de>,
133 {
134 let raw = CourseInfoRaw::deserialize(deserializer)?;
135 Self::try_from(raw).map_err(serde::de::Error::custom)
136 }
137}
138
139pub(crate) fn empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
141where
142 D: Deserializer<'de>,
143{
144 let opt = Option::<String>::deserialize(deserializer)?;
145 Ok(opt.filter(|s| !s.is_empty()))
146}