rumdl_lib/code_block_tools/
config.rs1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
13#[serde(rename_all = "kebab-case")]
14pub struct CodeBlockToolsConfig {
15 #[serde(default)]
17 pub enabled: bool,
18
19 #[serde(default)]
21 pub normalize_language: NormalizeLanguage,
22
23 #[serde(default)]
25 pub on_error: OnError,
26
27 #[serde(default = "default_timeout")]
29 pub timeout: u64,
30
31 #[serde(default)]
33 pub languages: HashMap<String, LanguageToolConfig>,
34
35 #[serde(default)]
38 pub language_aliases: HashMap<String, String>,
39
40 #[serde(default)]
42 pub tools: HashMap<String, ToolDefinition>,
43}
44
45fn default_timeout() -> u64 {
46 30_000
47}
48
49impl Default for CodeBlockToolsConfig {
50 fn default() -> Self {
51 Self {
52 enabled: false,
53 normalize_language: NormalizeLanguage::default(),
54 on_error: OnError::default(),
55 timeout: default_timeout(),
56 languages: HashMap::new(),
57 language_aliases: HashMap::new(),
58 tools: HashMap::new(),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
65#[serde(rename_all = "kebab-case")]
66pub enum NormalizeLanguage {
67 #[default]
69 Linguist,
70 Exact,
72}
73
74#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
76#[serde(rename_all = "kebab-case")]
77pub enum OnError {
78 #[default]
80 Fail,
81 Skip,
83 Warn,
85}
86
87#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
89#[serde(rename_all = "kebab-case")]
90pub struct LanguageToolConfig {
91 #[serde(default)]
93 pub lint: Vec<String>,
94
95 #[serde(default)]
97 pub format: Vec<String>,
98
99 #[serde(default)]
101 pub on_error: Option<OnError>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, schemars::JsonSchema)]
108#[serde(rename_all = "kebab-case")]
109pub struct ToolDefinition {
110 pub command: Vec<String>,
112
113 #[serde(default = "default_true")]
115 pub stdin: bool,
116
117 #[serde(default = "default_true")]
119 pub stdout: bool,
120
121 #[serde(default)]
123 pub lint_args: Vec<String>,
124
125 #[serde(default)]
127 pub format_args: Vec<String>,
128}
129
130fn default_true() -> bool {
131 true
132}
133
134impl Default for ToolDefinition {
135 fn default() -> Self {
136 Self {
137 command: Vec::new(),
138 stdin: true,
139 stdout: true,
140 lint_args: Vec::new(),
141 format_args: Vec::new(),
142 }
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_default_config() {
152 let config = CodeBlockToolsConfig::default();
153 assert!(!config.enabled);
154 assert_eq!(config.normalize_language, NormalizeLanguage::Linguist);
155 assert_eq!(config.on_error, OnError::Fail);
156 assert_eq!(config.timeout, 30_000);
157 assert!(config.languages.is_empty());
158 assert!(config.language_aliases.is_empty());
159 assert!(config.tools.is_empty());
160 }
161
162 #[test]
163 fn test_deserialize_config() {
164 let toml = r#"
165enabled = true
166normalize-language = "exact"
167on-error = "skip"
168timeout = 60000
169
170[languages.python]
171lint = ["ruff:check"]
172format = ["ruff:format"]
173
174[languages.json]
175format = ["prettier"]
176on-error = "warn"
177
178[language-aliases]
179py = "python"
180bash = "shell"
181
182[tools.custom-tool]
183command = ["my-tool", "--format"]
184stdin = true
185stdout = true
186"#;
187
188 let config: CodeBlockToolsConfig = toml::from_str(toml).expect("Failed to parse TOML");
189
190 assert!(config.enabled);
191 assert_eq!(config.normalize_language, NormalizeLanguage::Exact);
192 assert_eq!(config.on_error, OnError::Skip);
193 assert_eq!(config.timeout, 60_000);
194
195 let python = config.languages.get("python").expect("Missing python config");
196 assert_eq!(python.lint, vec!["ruff:check"]);
197 assert_eq!(python.format, vec!["ruff:format"]);
198 assert_eq!(python.on_error, None);
199
200 let json = config.languages.get("json").expect("Missing json config");
201 assert!(json.lint.is_empty());
202 assert_eq!(json.format, vec!["prettier"]);
203 assert_eq!(json.on_error, Some(OnError::Warn));
204
205 assert_eq!(config.language_aliases.get("py").map(String::as_str), Some("python"));
206 assert_eq!(config.language_aliases.get("bash").map(String::as_str), Some("shell"));
207
208 let tool = config.tools.get("custom-tool").expect("Missing custom tool");
209 assert_eq!(tool.command, vec!["my-tool", "--format"]);
210 assert!(tool.stdin);
211 assert!(tool.stdout);
212 }
213
214 #[test]
215 fn test_serialize_config() {
216 let mut config = CodeBlockToolsConfig {
217 enabled: true,
218 ..Default::default()
219 };
220 config.languages.insert(
221 "rust".to_string(),
222 LanguageToolConfig {
223 lint: vec![],
224 format: vec!["rustfmt".to_string()],
225 on_error: None,
226 },
227 );
228
229 let toml = toml::to_string_pretty(&config).expect("Failed to serialize");
230 assert!(toml.contains("enabled = true"));
231 assert!(toml.contains("[languages.rust]"));
232 assert!(toml.contains("rustfmt"));
233 }
234}