bms_table/
de.rs

1//! 反序列化实现模块
2//!
3//! 将所有 `Deserialize` 实现与辅助原始类型集中于此,保持 `lib.rs` 仅包含类型定义。
4
5use anyhow::Result;
6use serde::{Deserialize, Deserializer};
7use serde_json::Value;
8
9use crate::{BmsTableData, BmsTableHeader, ChartItem, CourseInfo, Trophy};
10
11/// 内部辅助类型:用于更简洁地反序列化表头,并保留未知字段。
12#[derive(Deserialize)]
13struct BmsTableHeaderRaw {
14    name: String,
15    symbol: String,
16    data_url: String,
17    #[serde(default)]
18    course: Option<Value>,
19    #[serde(default)]
20    level_order: Option<Vec<Value>>,
21    #[serde(flatten)]
22    extra: serde_json::Map<String, Value>,
23}
24
25/// 支持两种 `course` 输入:`Vec<CourseInfo>` 或 `Vec<Vec<CourseInfo>>`。
26/// 保留原有行为:当 `course` 是空数组时,视为扁平形式并包一层为空组。
27impl TryFrom<BmsTableHeaderRaw> for BmsTableHeader {
28    type Error = String;
29
30    fn try_from(raw: BmsTableHeaderRaw) -> Result<Self, Self::Error> {
31        let course = match raw.course {
32            Some(Value::Array(arr)) => {
33                if arr.is_empty() {
34                    // 空数组按扁平形式处理,包成一个空组
35                    vec![Vec::new()]
36                } else if matches!(arr.first(), Some(Value::Array(_))) {
37                    // Vec<Vec<CourseInfo>>
38                    serde_json::from_value::<Vec<Vec<CourseInfo>>>(Value::Array(arr))
39                        .map_err(|e| e.to_string())?
40                } else {
41                    // Vec<CourseInfo> -> 包装为单个组
42                    let inner: Vec<CourseInfo> =
43                        serde_json::from_value(Value::Array(arr)).map_err(|e| e.to_string())?;
44                    vec![inner]
45                }
46            }
47            _ => Vec::new(),
48        };
49
50        let level_order = raw
51            .level_order
52            .unwrap_or_default()
53            .into_iter()
54            .map(|v| match v {
55                Value::Number(n) => n.to_string(),
56                Value::String(s) => s,
57                other => other.to_string(),
58            })
59            .collect::<Vec<String>>();
60
61        Ok(Self {
62            name: raw.name,
63            symbol: raw.symbol,
64            data_url: raw.data_url,
65            course,
66            level_order,
67            extra: Value::Object(raw.extra),
68        })
69    }
70}
71
72impl<'de> Deserialize<'de> for BmsTableHeader {
73    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
74    where
75        D: Deserializer<'de>,
76    {
77        let raw = BmsTableHeaderRaw::deserialize(deserializer)?;
78        Self::try_from(raw).map_err(serde::de::Error::custom)
79    }
80}
81
82/// 内部辅助类型:兼容数组或包裹对象的两种 `charts` 形式。
83#[derive(Deserialize)]
84#[serde(untagged)]
85enum ChartsWrapper {
86    Array(Vec<ChartItem>),
87    Object { charts: Vec<ChartItem> },
88}
89
90impl TryFrom<ChartsWrapper> for BmsTableData {
91    type Error = String;
92
93    fn try_from(w: ChartsWrapper) -> Result<Self, Self::Error> {
94        let charts = match w {
95            ChartsWrapper::Array(v) => v,
96            ChartsWrapper::Object { charts } => charts,
97        };
98        Ok(Self { charts })
99    }
100}
101
102impl<'de> Deserialize<'de> for BmsTableData {
103    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
104    where
105        D: Deserializer<'de>,
106    {
107        let w = ChartsWrapper::deserialize(deserializer)?;
108        Self::try_from(w).map_err(serde::de::Error::custom)
109    }
110}
111
112/// 内部辅助类型:用于更简洁地构造 `CourseInfo`,并处理 md5/sha256 列表。
113#[derive(Deserialize)]
114struct CourseInfoRaw {
115    name: String,
116    #[serde(default)]
117    constraint: Vec<String>,
118    #[serde(default)]
119    trophy: Vec<Trophy>,
120    #[serde(default, rename = "md5")]
121    md5list: Vec<String>,
122    #[serde(default, rename = "sha256")]
123    sha256list: Vec<String>,
124    #[serde(default)]
125    charts: Vec<Value>,
126}
127
128impl TryFrom<CourseInfoRaw> for CourseInfo {
129    type Error = String;
130
131    fn try_from(raw: CourseInfoRaw) -> Result<Self, Self::Error> {
132        let mut charts: Vec<ChartItem> =
133            Vec::with_capacity(raw.charts.len() + raw.md5list.len() + raw.sha256list.len());
134
135        // 处理 charts,缺失 level 时补 "0"
136        for mut chart_value in raw.charts {
137            if chart_value.get("level").is_none() {
138                let obj = chart_value
139                    .as_object()
140                    .ok_or_else(|| "chart_value is not an object".to_string())?
141                    .clone();
142                let mut obj = obj;
143                obj.insert("level".to_string(), Value::String("0".to_string()));
144                chart_value = Value::Object(obj);
145            }
146            let item: ChartItem = serde_json::from_value(chart_value).map_err(|e| e.to_string())?;
147            charts.push(item);
148        }
149
150        // md5list -> charts
151        charts.extend(raw.md5list.into_iter().map(|md5| ChartItem {
152            level: "0".to_string(),
153            md5: Some(md5),
154            sha256: None,
155            title: None,
156            subtitle: None,
157            artist: None,
158            subartist: None,
159            url: None,
160            url_diff: None,
161            extra: Value::Object(serde_json::Map::new()),
162        }));
163
164        // sha256list -> charts
165        charts.extend(raw.sha256list.into_iter().map(|sha256| ChartItem {
166            level: "0".to_string(),
167            md5: None,
168            sha256: Some(sha256),
169            title: None,
170            subtitle: None,
171            artist: None,
172            subartist: None,
173            url: None,
174            url_diff: None,
175            extra: Value::Object(serde_json::Map::new()),
176        }));
177
178        Ok(Self {
179            name: raw.name,
180            constraint: raw.constraint,
181            trophy: raw.trophy,
182            charts,
183        })
184    }
185}
186
187impl<'de> Deserialize<'de> for CourseInfo {
188    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189    where
190        D: Deserializer<'de>,
191    {
192        let raw = CourseInfoRaw::deserialize(deserializer)?;
193        Self::try_from(raw).map_err(serde::de::Error::custom)
194    }
195}
196
197/// 将空字符串反序列化为 `None` 的通用辅助函数。
198fn empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
199where
200    D: Deserializer<'de>,
201{
202    let opt = Option::<String>::deserialize(deserializer)?;
203    Ok(opt.filter(|s| !s.is_empty()))
204}
205
206/// 内部辅助类型:用于更简洁地反序列化 `ChartItem` 并保留未知字段。
207#[derive(Deserialize)]
208struct ChartItemRaw {
209    level: String,
210    #[serde(default, deserialize_with = "empty_string_as_none")]
211    md5: Option<String>,
212    #[serde(default, deserialize_with = "empty_string_as_none")]
213    sha256: Option<String>,
214    #[serde(default, deserialize_with = "empty_string_as_none")]
215    title: Option<String>,
216    #[serde(default, deserialize_with = "empty_string_as_none")]
217    subtitle: Option<String>,
218    #[serde(default, deserialize_with = "empty_string_as_none")]
219    artist: Option<String>,
220    #[serde(default, deserialize_with = "empty_string_as_none")]
221    subartist: Option<String>,
222    #[serde(default, deserialize_with = "empty_string_as_none")]
223    url: Option<String>,
224    #[serde(default, deserialize_with = "empty_string_as_none")]
225    url_diff: Option<String>,
226    #[serde(flatten)]
227    extra: serde_json::Map<String, Value>,
228}
229
230impl TryFrom<ChartItemRaw> for ChartItem {
231    type Error = String;
232
233    fn try_from(raw: ChartItemRaw) -> Result<Self, Self::Error> {
234        Ok(Self {
235            level: raw.level,
236            md5: raw.md5,
237            sha256: raw.sha256,
238            title: raw.title,
239            subtitle: raw.subtitle,
240            artist: raw.artist,
241            subartist: raw.subartist,
242            url: raw.url,
243            url_diff: raw.url_diff,
244            extra: Value::Object(raw.extra),
245        })
246    }
247}
248
249impl<'de> Deserialize<'de> for ChartItem {
250    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
251    where
252        D: Deserializer<'de>,
253    {
254        let raw = ChartItemRaw::deserialize(deserializer)?;
255        Self::try_from(raw).map_err(serde::de::Error::custom)
256    }
257}