bms_table/
de.rs

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