rskit-codec 0.2.0-alpha.1

Pluggable structured-text codecs (TOML/JSON) over a shared value tree, with value-tree merge
Documentation
use rskit_errors::{AppError, AppResult};
use serde_json::Value;

use crate::codec::Codec;

/// Built-in TOML codec.
///
/// Decodes TOML into the canonical [`Value`] tree and encodes a value tree back
/// to TOML. The top-level value must be a table (TOML has no top-level scalar or
/// array document), and JSON `null` has no TOML representation — both surface as
/// typed errors rather than panics.
#[derive(Debug, Clone, Copy, Default)]
pub struct TomlCodec;

impl Codec for TomlCodec {
    fn name(&self) -> &'static str {
        "toml"
    }

    fn encode_value(&self, value: &Value) -> AppResult<String> {
        toml::to_string(value).map_err(|err| {
            AppError::invalid_input("codec", "failed to serialize value as TOML").with_cause(err)
        })
    }

    fn decode_value(&self, contents: &str) -> AppResult<Value> {
        toml::from_str::<Value>(contents)
            .map_err(|err| AppError::invalid_input("codec", "failed to parse TOML").with_cause(err))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn round_trips_table() {
        let codec = TomlCodec;
        let value: Value = serde_json::json!({
            "name": "svc",
            "retries": 3,
            "nested": { "enabled": true }
        });

        let encoded = codec.encode_value(&value).unwrap();
        let decoded = codec.decode_value(&encoded).unwrap();

        assert_eq!(decoded, value);
        assert_eq!(codec.name(), "toml");
    }

    #[test]
    fn rejects_malformed_input() {
        let err = TomlCodec.decode_value("= not valid").unwrap_err();
        assert!(err.to_string().contains("parse"));
    }

    #[test]
    fn rejects_unrepresentable_values() {
        // JSON `null` has no TOML representation; this must surface as a typed
        // error rather than a panic.
        let err = TomlCodec.encode_value(&Value::Null).unwrap_err();
        assert!(err.to_string().contains("serialize"));
    }
}