rhythm_open_exchange/codec/
traits.rs1use std::path::Path;
4
5use crate::error::RoxResult;
6use crate::model::RoxChart;
7
8pub trait Decoder {
10 fn decode(data: &[u8]) -> RoxResult<RoxChart>;
16
17 fn decode_from_path(path: impl AsRef<Path>) -> RoxResult<RoxChart> {
23 let data = std::fs::read(path)?;
24 Self::decode(&data)
25 }
26}
27
28pub trait Encoder {
30 fn encode(chart: &RoxChart) -> RoxResult<Vec<u8>>;
36
37 fn encode_to_path(chart: &RoxChart, path: impl AsRef<Path>) -> RoxResult<()> {
43 let data = Self::encode(chart)?;
44 std::fs::write(path, data)?;
45 Ok(())
46 }
47
48 fn encode_to_string(chart: &RoxChart) -> RoxResult<String> {
54 let data = Self::encode(chart)?;
55 String::from_utf8(data)
56 .map_err(|e| crate::error::RoxError::InvalidFormat(format!("Invalid UTF-8: {e}")))
57 }
58}
59
60pub trait Format {
63 const EXTENSIONS: &'static [&'static str];
66
67 #[must_use]
69 fn supports_extension(ext: &str) -> bool {
70 let ext_lower = ext.to_lowercase();
71 Self::EXTENSIONS.iter().any(|&e| e == ext_lower)
72 }
73}
74
75pub fn convert<D: Decoder, E: Encoder>(data: &[u8]) -> RoxResult<Vec<u8>> {
89 let chart = D::decode(data)?;
90 E::encode(&chart)
91}
92
93pub fn convert_file<D: Decoder, E: Encoder>(
106 input: impl AsRef<Path>,
107 output: impl AsRef<Path>,
108) -> RoxResult<()> {
109 let chart = D::decode_from_path(input)?;
110 E::encode_to_path(&chart, output)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::codec::formats::{OsuDecoder, OsuEncoder};
117 use std::fs;
118 use tempfile::tempdir;
119
120 #[test]
121 fn test_convert() {
122 let data = crate::test_utils::get_test_asset("osu/mania_7k.osu");
123 let result = convert::<OsuDecoder, OsuEncoder>(&data).unwrap();
124 assert!(!result.is_empty());
125 assert!(String::from_utf8_lossy(&result).contains("osu file format v14"));
126 }
127
128 #[test]
129 fn test_convert_file() {
130 let dir = tempdir().unwrap();
131 let input_path = dir.path().join("input.osu");
132 let output_path = dir.path().join("output.osu");
133
134 let data = crate::test_utils::get_test_asset("osu/mania_7k.osu");
135 fs::write(&input_path, &data).unwrap();
136
137 convert_file::<OsuDecoder, OsuEncoder>(&input_path, &output_path).unwrap();
138
139 assert!(output_path.exists());
140 let result = fs::read(&output_path).unwrap();
141 assert!(String::from_utf8_lossy(&result).contains("osu file format v14"));
142 }
143
144 #[test]
145 fn test_decoder_from_path() {
146 let dir = tempdir().unwrap();
147 let path = dir.path().join("test.osu");
148 let data = crate::test_utils::get_test_asset("osu/mania_7k.osu");
149 fs::write(&path, &data).unwrap();
150
151 let chart = OsuDecoder::decode_from_path(&path).unwrap();
152 assert_eq!(chart.key_count(), 7);
153 }
154
155 #[test]
156 fn test_encoder_to_string() {
157 let data = crate::test_utils::get_test_asset("osu/mania_7k.osu");
158 let chart = OsuDecoder::decode(&data).unwrap();
159 let s = OsuEncoder::encode_to_string(&chart).unwrap();
160 assert!(s.contains("Artist:Iced Blade"));
161 }
162}