Skip to main content

rhythm_open_exchange/codec/formats/
yrox.rs

1//! YAML-based ROX format (YROX).
2//!
3//! Useful for human-readable configuration-like charts.
4
5use crate::codec::{Decoder, Encoder, Format};
6use crate::error::{RoxError, RoxResult};
7use crate::model::RoxChart;
8
9/// YROX (YAML ROX) decoder.
10pub struct YroxDecoder;
11
12/// YROX (YAML ROX) encoder.
13pub struct YroxEncoder;
14
15impl Decoder for YroxDecoder {
16    fn decode(data: &[u8]) -> RoxResult<RoxChart> {
17        // Safety limit: 100MB
18        const MAX_FILE_SIZE: usize = 100 * 1024 * 1024;
19        if data.len() > MAX_FILE_SIZE {
20            return Err(RoxError::InvalidFormat(format!(
21                "File too large: {} bytes (max {}MB)",
22                data.len(),
23                MAX_FILE_SIZE / 1024 / 1024
24            )));
25        }
26
27        let chart: RoxChart = serde_yaml::from_slice(data)
28            .map_err(|e| RoxError::InvalidFormat(format!("YROX parse error: {e}")))?;
29
30        Ok(chart)
31    }
32}
33
34impl Encoder for YroxEncoder {
35    fn encode(chart: &RoxChart) -> RoxResult<Vec<u8>> {
36        let s = serde_yaml::to_string(chart).map_err(|e| RoxError::Serialize(e.to_string()))?;
37        Ok(s.into_bytes())
38    }
39}
40
41impl Format for YroxDecoder {
42    const EXTENSIONS: &'static [&'static str] = &["yrox"];
43}
44
45impl Format for YroxEncoder {
46    const EXTENSIONS: &'static [&'static str] = &["yrox"];
47}
48
49#[cfg(test)]
50mod tests {
51    use compact_str::ToCompactString;
52
53    use super::*;
54    use crate::codec::{Decoder, Encoder};
55    use crate::model::RoxChart;
56
57    #[test]
58    fn test_yrox_roundtrip() {
59        let mut chart = RoxChart::new(4);
60        chart.metadata.title = "Yrox Test".to_compact_string();
61
62        let encoded = YroxEncoder::encode(&chart).unwrap();
63        let decoded = YroxDecoder::decode(&encoded).unwrap();
64
65        assert_eq!(chart.key_count(), decoded.key_count());
66        assert_eq!(chart.metadata.title, decoded.metadata.title);
67    }
68
69    #[test]
70    fn test_file_too_large() {
71        // Safety limit: 100MB to prevent memory exhaustion
72        let max_file_size: usize = 100 * 1024 * 1024;
73        let big_data = vec![0; max_file_size + 1];
74        let result = YroxDecoder::decode(&big_data);
75        assert!(
76            matches!(result, Err(RoxError::InvalidFormat(msg)) if msg.contains("File too large"))
77        );
78    }
79}