1#![cfg(feature = "serde")]
5
6use serde::{Deserialize, Deserializer};
7use serde_json::Value;
8use std::collections::BTreeMap;
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,
59 #[serde(default)]
61 constraint: Vec<String>,
62 #[serde(default)]
64 trophy: Vec<Trophy>,
65 #[serde(default, rename = "md5")]
67 md5list: Vec<String>,
68 #[serde(default, rename = "sha256")]
70 sha256list: Vec<String>,
71 #[serde(default)]
73 charts: Vec<Value>,
74}
75
76impl TryFrom<CourseInfoRaw> for CourseInfo {
77 type Error = String;
78
79 fn try_from(raw: CourseInfoRaw) -> Result<Self, Self::Error> {
80 let mut charts: Vec<ChartItem> =
81 Vec::with_capacity(raw.charts.len() + raw.md5list.len() + raw.sha256list.len());
82
83 for mut chart_value in raw.charts {
85 if chart_value.get("level").is_none() {
86 let obj = chart_value
87 .as_object()
88 .ok_or_else(|| "chart_value is not an object".to_string())?
89 .clone();
90 let mut obj = obj;
91 obj.insert("level".to_string(), Value::String("0".to_string()));
92 chart_value = Value::Object(obj);
93 }
94 let item: ChartItem = serde_json::from_value(chart_value).map_err(|e| e.to_string())?;
95 charts.push(item);
96 }
97
98 charts.extend(raw.md5list.into_iter().map(|md5| ChartItem {
100 level: "0".to_string(),
101 md5: Some(md5),
102 sha256: None,
103 title: None,
104 subtitle: None,
105 artist: None,
106 subartist: None,
107 url: None,
108 url_diff: None,
109 extra: BTreeMap::new(),
110 }));
111
112 charts.extend(raw.sha256list.into_iter().map(|sha256| ChartItem {
114 level: "0".to_string(),
115 md5: None,
116 sha256: Some(sha256),
117 title: None,
118 subtitle: None,
119 artist: None,
120 subartist: None,
121 url: None,
122 url_diff: None,
123 extra: BTreeMap::new(),
124 }));
125
126 Ok(Self {
127 name: raw.name,
128 constraint: raw.constraint,
129 trophy: raw.trophy,
130 charts,
131 })
132 }
133}
134
135impl<'de> Deserialize<'de> for CourseInfo {
136 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
137 where
138 D: Deserializer<'de>,
139 {
140 let raw = CourseInfoRaw::deserialize(deserializer)?;
141 Self::try_from(raw).map_err(serde::de::Error::custom)
142 }
143}
144
145pub(crate) fn de_numstring<'de, D>(deserializer: D) -> Result<String, D::Error>
147where
148 D: Deserializer<'de>,
149{
150 let opt = Option::<Value>::deserialize(deserializer)?;
151 let Some(value) = opt else {
152 return Err(serde::de::Error::custom(
153 "expected string or number, found None",
154 ));
155 };
156 match value {
157 Value::String(s) => Ok(s),
158 Value::Number(n) => Ok(n.to_string()),
159 other => Err(serde::de::Error::custom(format!(
160 "expected string or number, got {}",
161 other
162 ))),
163 }
164}