Skip to main content

nika_core/ast/
context.rs

1//! Context configuration for workflow
2//!
3//! The `context:` block in a workflow allows loading files at workflow start.
4//! Files are loaded into the RunContext and accessible via `{{context.files.alias}}` bindings.
5//!
6//! # Example
7//!
8//! ```yaml
9//! context:
10//!   files:
11//!     brand: ./context/brand.md        # Markdown → string
12//!     persona: ./context/persona.json  # JSON → parsed object
13//!     examples: ./context/*.md         # Glob → array of strings
14//!   session: .nika/sessions/prev.json  # Session restore
15//! ```
16
17use rustc_hash::FxHashMap;
18use serde::Deserialize;
19
20/// Context configuration for workflow
21///
22/// Defines files to load at workflow start and optional session restoration.
23#[derive(Debug, Clone, Deserialize, Default)]
24pub struct ContextConfig {
25    /// Files to load at workflow start
26    ///
27    /// Key is the alias, value is the file path (supports glob patterns).
28    /// - Single files: loaded as string (markdown, txt) or parsed (json, yaml)
29    /// - Glob patterns: loaded as array of strings
30    #[serde(default)]
31    pub files: FxHashMap<String, String>,
32
33    /// Session file to restore
34    ///
35    /// Path to a JSON file containing previous session data.
36    /// Accessible via `{{context.session.key}}` bindings.
37    pub session: Option<String>,
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43    use crate::serde_yaml;
44
45    #[test]
46    fn test_context_config_default() {
47        let config = ContextConfig::default();
48        assert!(config.files.is_empty());
49        assert!(config.session.is_none());
50    }
51
52    #[test]
53    fn test_context_config_deserialize_empty() {
54        let yaml = "";
55        let config: ContextConfig = serde_yaml::from_str(yaml).unwrap_or_default();
56        assert!(config.files.is_empty());
57    }
58
59    #[test]
60    fn test_context_config_deserialize_files() {
61        let yaml = r#"
62files:
63  brand: ./context/brand.md
64  persona: ./context/persona.json
65"#;
66        let config: ContextConfig = serde_yaml::from_str(yaml).unwrap();
67        assert_eq!(config.files.len(), 2);
68        assert_eq!(
69            config.files.get("brand"),
70            Some(&"./context/brand.md".to_string())
71        );
72        assert_eq!(
73            config.files.get("persona"),
74            Some(&"./context/persona.json".to_string())
75        );
76    }
77
78    #[test]
79    fn test_context_config_deserialize_session() {
80        let yaml = r#"
81session: .nika/sessions/prev.json
82"#;
83        let config: ContextConfig = serde_yaml::from_str(yaml).unwrap();
84        assert_eq!(config.session, Some(".nika/sessions/prev.json".to_string()));
85    }
86
87    #[test]
88    fn test_context_config_deserialize_full() {
89        let yaml = r#"
90files:
91  brand: ./context/brand.md
92  examples: ./context/*.md
93session: .nika/sessions/prev.json
94"#;
95        let config: ContextConfig = serde_yaml::from_str(yaml).unwrap();
96        assert_eq!(config.files.len(), 2);
97        assert!(config.files.contains_key("brand"));
98        assert!(config.files.contains_key("examples"));
99        assert!(config.session.is_some());
100    }
101
102    #[test]
103    fn test_context_config_glob_pattern() {
104        let yaml = r#"
105files:
106  examples: ./context/*.md
107"#;
108        let config: ContextConfig = serde_yaml::from_str(yaml).unwrap();
109        assert!(config.files.get("examples").unwrap().contains('*'));
110    }
111}