rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use serde::Serialize;
use serde_json::Value;

pub struct RjangoJsonEncoder;

impl RjangoJsonEncoder {
    pub fn encode<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
        serde_json::to_string(value)
    }

    pub fn encode_pretty<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
        serde_json::to_string_pretty(value)
    }

    pub fn to_value<T: Serialize>(value: &T) -> Result<Value, serde_json::Error> {
        serde_json::to_value(value)
    }
}

#[cfg(test)]
mod tests {
    use super::RjangoJsonEncoder;
    use chrono::{NaiveDate, TimeZone, Utc};
    use serde::Serialize;
    use serde_json::json;
    use uuid::Uuid;

    #[derive(Debug, Serialize)]
    struct ExamplePayload {
        name: String,
        count: usize,
    }

    #[derive(Debug, Serialize)]
    struct TemporalPayload {
        date: NaiveDate,
        timestamp: chrono::DateTime<Utc>,
        id: Uuid,
    }

    #[test]
    fn encode_serializes_plain_structs() {
        let encoded = RjangoJsonEncoder::encode(&ExamplePayload {
            name: "Rjango".to_string(),
            count: 3,
        })
        .expect("plain structs should serialize");

        assert_eq!(encoded, "{\"name\":\"Rjango\",\"count\":3}");
    }

    #[test]
    fn encode_pretty_serializes_with_indentation() {
        let encoded = RjangoJsonEncoder::encode_pretty(&ExamplePayload {
            name: "Rjango".to_string(),
            count: 3,
        })
        .expect("plain structs should serialize with pretty formatting");

        assert!(encoded.contains('\n'));
        assert!(encoded.contains("  \"name\": \"Rjango\""));
    }

    #[test]
    fn encode_supports_dates_datetimes_and_uuids() {
        let payload = TemporalPayload {
            date: NaiveDate::from_ymd_opt(2024, 2, 29).expect("test date should be valid"),
            timestamp: Utc.with_ymd_and_hms(2024, 2, 29, 12, 30, 45).unwrap(),
            id: Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")
                .expect("test UUID should parse"),
        };

        let encoded =
            RjangoJsonEncoder::encode(&payload).expect("temporal payload should serialize");

        assert!(encoded.contains("\"date\":\"2024-02-29\""));
        assert!(encoded.contains("\"timestamp\":\"2024-02-29T12:30:45Z\""));
        assert!(encoded.contains("\"id\":\"550e8400-e29b-41d4-a716-446655440000\""));
    }

    #[test]
    fn to_value_converts_serializable_types_to_json_values() {
        let value = RjangoJsonEncoder::to_value(&ExamplePayload {
            name: "Rjango".to_string(),
            count: 3,
        })
        .expect("plain structs should convert to JSON values");

        assert_eq!(value, json!({"name": "Rjango", "count": 3}));
    }

    #[test]
    fn encode_handles_existing_json_values() {
        let encoded = RjangoJsonEncoder::encode(&json!({"ok": true, "items": [1, 2, 3]}))
            .expect("serde_json::Value should serialize");

        assert_eq!(encoded, "{\"items\":[1,2,3],\"ok\":true}");
    }

    #[test]
    fn encode_reports_serializer_errors_for_non_string_map_keys() {
        use std::collections::BTreeMap;

        let mut payload = BTreeMap::new();
        payload.insert((1, 2), "value");

        let error = RjangoJsonEncoder::encode(&payload)
            .expect_err("tuple map keys are not valid JSON object keys");

        assert!(error.to_string().contains("key must be a string"));
    }
}