Skip to main content

prosaic_project/
fixture.rs

1//! `fixtures/*.json` loader.
2//!
3//! A fixture is a single-event context map serialized as JSON.
4
5use prosaic_core::{Context, Value};
6use serde_json::Value as JsonValue;
7
8use crate::error::ProjectError;
9
10/// Parse a fixture JSON string into a `Context`.
11pub fn parse_fixture(name: &str, json: &str) -> Result<Context, ProjectError> {
12    let v: JsonValue = serde_json::from_str(json).map_err(|e| ProjectError::JsonParse {
13        file: name.to_string(),
14        cause: e.to_string(),
15    })?;
16    let obj = v
17        .as_object()
18        .ok_or_else(|| ProjectError::FixtureValidation {
19            name: name.to_string(),
20            reason: "fixture must be a JSON object".to_string(),
21        })?;
22    let mut ctx = Context::new();
23    for (k, v) in obj {
24        ctx.insert(k.clone(), json_to_value(v));
25    }
26    Ok(ctx)
27}
28
29fn json_to_value(v: &JsonValue) -> Value {
30    match v {
31        JsonValue::Null => Value::String(String::new()),
32        JsonValue::Bool(b) => Value::Number(if *b { 1 } else { 0 }),
33        JsonValue::Number(n) => {
34            if let Some(i) = n.as_i64() {
35                Value::Number(i)
36            } else if let Some(f) = n.as_f64() {
37                Value::Number(f as i64)
38            } else {
39                Value::Number(0)
40            }
41        }
42        JsonValue::String(s) => Value::String(s.clone()),
43        JsonValue::Array(items) => Value::List(
44            items
45                .iter()
46                .map(|i| match i {
47                    JsonValue::String(s) => s.clone(),
48                    other => other.to_string(),
49                })
50                .collect(),
51        ),
52        JsonValue::Object(_) => Value::String(v.to_string()),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn parse_simple_fixture() {
62        let json = r#"{"name": "UserService", "entity_type": "class", "consumer_count": 6}"#;
63        let ctx = parse_fixture("test", json).unwrap();
64        assert_eq!(ctx.get("name").unwrap().as_display(), "UserService");
65        assert_eq!(ctx.get("consumer_count").unwrap().as_number().unwrap(), 6);
66    }
67
68    #[test]
69    fn parse_fixture_with_array() {
70        let json = r#"{"consumers": ["A", "B", "C"]}"#;
71        let ctx = parse_fixture("test", json).unwrap();
72        let list = ctx.get("consumers").unwrap().as_list().unwrap();
73        assert_eq!(
74            list,
75            vec!["A".to_string(), "B".to_string(), "C".to_string()]
76        );
77    }
78
79    #[test]
80    fn invalid_json_errors() {
81        let res = parse_fixture("bad", "{not json");
82        assert!(matches!(res, Err(ProjectError::JsonParse { .. })));
83    }
84
85    #[test]
86    fn non_object_errors() {
87        let res = parse_fixture("bad", "[1, 2, 3]");
88        assert!(matches!(res, Err(ProjectError::FixtureValidation { .. })));
89    }
90}