Skip to main content

rhythm_open_exchange/codec/formats/
jrox.rs

1//! JSON-based ROX format (JROX).
2//!
3//! Useful for debugging and manual editing.
4
5use crate::codec::{Decoder, Encoder, Format};
6use crate::error::{RoxError, RoxResult};
7use crate::model::RoxChart;
8
9/// JROX (JSON ROX) decoder.
10pub struct JroxDecoder;
11
12/// JROX (JSON ROX) encoder.
13pub struct JroxEncoder;
14
15// Safety limit: 100MB to prevent memory exhaustion
16const MAX_FILE_SIZE: usize = 100 * 1024 * 1024;
17
18impl Decoder for JroxDecoder {
19    fn decode(data: &[u8]) -> RoxResult<RoxChart> {
20        if data.len() > MAX_FILE_SIZE {
21            return Err(RoxError::InvalidFormat(format!(
22                "File too large: {} bytes (max {}MB)",
23                data.len(),
24                MAX_FILE_SIZE / 1024 / 1024
25            )));
26        }
27        // JROX is essentially JSON-serialized ROX
28        // Since Metadata uses String in serde but ROX uses CompactString, we might need manual mapping
29        // But since CompactString implements Deserialize, it should handle string data seamlessly
30        // The error likely comes from where we construct ROX struct manually or vice versa
31
32        let chart: RoxChart = serde_json::from_slice(data)
33            .map_err(|e| RoxError::InvalidFormat(format!("JROX parse error: {e}")))?;
34
35        Ok(chart)
36    }
37}
38
39impl Encoder for JroxEncoder {
40    fn encode(chart: &RoxChart) -> RoxResult<Vec<u8>> {
41        serde_json::to_vec_pretty(chart).map_err(|e| RoxError::Serialize(e.to_string()))
42    }
43}
44
45impl Format for JroxDecoder {
46    const EXTENSIONS: &'static [&'static str] = &["jrox"];
47}
48
49impl Format for JroxEncoder {
50    const EXTENSIONS: &'static [&'static str] = &["jrox"];
51}
52
53#[cfg(test)]
54mod tests {
55    use compact_str::ToCompactString;
56
57    use super::*;
58    use crate::codec::{Decoder, Encoder};
59    use crate::model::RoxChart;
60
61    #[test]
62    fn test_jrox_roundtrip() {
63        let mut chart = RoxChart::new(4);
64        chart.metadata.title = "Jrox Test".to_compact_string();
65
66        let encoded = JroxEncoder::encode(&chart).unwrap();
67        let decoded = JroxDecoder::decode(&encoded).unwrap();
68
69        assert_eq!(chart.key_count(), decoded.key_count());
70        assert_eq!(chart.metadata.title, decoded.metadata.title);
71    }
72
73    #[test]
74    fn test_file_too_large() {
75        let big_data = vec![0; MAX_FILE_SIZE + 1];
76        let result = JroxDecoder::decode(&big_data);
77        assert!(
78            matches!(result, Err(RoxError::InvalidFormat(msg)) if msg.contains("File too large"))
79        );
80    }
81}