1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ExecutionSpec {
6 pub code: String,
8 pub ctx: serde_json::Value,
10 pub namespace: String,
12}
13
14impl ExecutionSpec {
15 pub fn new(code: String, ctx: serde_json::Value) -> Self {
18 let namespace = ctx
19 .as_object()
20 .and_then(|o| o.get("_ns"))
21 .and_then(|v| v.as_str())
22 .unwrap_or("default")
23 .to_string();
24 Self {
25 code,
26 ctx,
27 namespace,
28 }
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35 use serde_json::json;
36
37 #[test]
38 fn namespace_from_ctx() {
39 let spec = ExecutionSpec::new("code".into(), json!({"_ns": "custom"}));
40 assert_eq!(spec.namespace, "custom");
41 }
42
43 #[test]
44 fn namespace_default_when_missing() {
45 let spec = ExecutionSpec::new("code".into(), json!({"key": "value"}));
46 assert_eq!(spec.namespace, "default");
47 }
48
49 #[test]
50 fn namespace_default_when_null_ctx() {
51 let spec = ExecutionSpec::new("code".into(), serde_json::Value::Null);
52 assert_eq!(spec.namespace, "default");
53 }
54
55 #[test]
56 fn namespace_default_when_ns_is_non_string() {
57 let spec = ExecutionSpec::new("code".into(), json!({"_ns": 42}));
58 assert_eq!(spec.namespace, "default");
59 }
60
61 #[test]
62 fn roundtrip_json() {
63 let spec = ExecutionSpec::new("return 1".into(), json!({"_ns": "test", "data": [1,2]}));
64 let json = serde_json::to_value(&spec).unwrap();
65 let restored: ExecutionSpec = serde_json::from_value(json).unwrap();
66 assert_eq!(restored.code, spec.code);
67 assert_eq!(restored.ctx, spec.ctx);
68 assert_eq!(restored.namespace, spec.namespace);
69 }
70}
71
72#[cfg(test)]
73mod proptests {
74 use super::*;
75 use proptest::prelude::*;
76
77 fn arb_json_value() -> impl Strategy<Value = serde_json::Value> {
78 prop_oneof![
79 Just(serde_json::Value::Null),
80 any::<bool>().prop_map(serde_json::Value::Bool),
81 any::<i64>().prop_map(|n| serde_json::json!(n)),
82 "\\PC{0,50}".prop_map(|s| serde_json::Value::String(s)),
83 Just(serde_json::json!([])),
84 Just(serde_json::json!({})),
85 Just(serde_json::json!({"key": "value"})),
86 "\\PC{1,30}".prop_map(|ns| serde_json::json!({"_ns": ns})),
87 ]
88 }
89
90 proptest! {
91 #[test]
92 fn new_never_panics(code in "\\PC{0,200}", ctx in arb_json_value()) {
93 let spec = ExecutionSpec::new(code, ctx);
94 prop_assert!(!spec.namespace.is_empty());
96 }
97
98 #[test]
99 fn namespace_is_ctx_ns_or_default(ns in "\\PC{1,30}") {
100 let ctx = serde_json::json!({"_ns": ns});
101 let spec = ExecutionSpec::new("code".into(), ctx);
102 prop_assert_eq!(&spec.namespace, &ns);
103 }
104
105 #[test]
106 fn serde_roundtrip(code in "\\PC{0,100}", ctx in arb_json_value()) {
107 let spec = ExecutionSpec::new(code, ctx);
108 let json = serde_json::to_value(&spec).unwrap();
109 let restored: ExecutionSpec = serde_json::from_value(json).unwrap();
110 prop_assert_eq!(&restored.code, &spec.code);
111 prop_assert_eq!(&restored.ctx, &spec.ctx);
112 prop_assert_eq!(&restored.namespace, &spec.namespace);
113 }
114 }
115}