pforge_config/
validator.rs

1use crate::{ConfigError, ForgeConfig, Result, ToolDef};
2use rustc_hash::FxHashSet;
3
4pub fn validate_config(config: &ForgeConfig) -> Result<()> {
5    // Check for duplicate tool names
6    let mut tool_names = FxHashSet::default();
7    for tool in &config.tools {
8        let name = tool.name();
9        if !tool_names.insert(name) {
10            return Err(ConfigError::DuplicateToolName(name.to_string()));
11        }
12    }
13
14    // Validate handler paths for native tools
15    for tool in &config.tools {
16        if let ToolDef::Native { handler, .. } = tool {
17            validate_handler_path(&handler.path)?;
18        }
19    }
20
21    Ok(())
22}
23
24fn validate_handler_path(path: &str) -> Result<()> {
25    if path.is_empty() {
26        return Err(ConfigError::InvalidHandlerPath("empty path".to_string()));
27    }
28
29    // Basic validation: should contain ::
30    if !path.contains("::") {
31        return Err(ConfigError::InvalidHandlerPath(format!(
32            "invalid format: {path} (expected format: module::function)"
33        )));
34    }
35
36    Ok(())
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::*;
43
44    #[test]
45    fn test_validate_config_success() {
46        let config = ForgeConfig {
47            forge: ForgeMetadata {
48                name: "test".to_string(),
49                version: "1.0.0".to_string(),
50                transport: TransportType::Stdio,
51                optimization: OptimizationLevel::Debug,
52            },
53            tools: vec![ToolDef::Native {
54                name: "tool1".to_string(),
55                description: "Tool 1".to_string(),
56                handler: HandlerRef {
57                    path: "module::handler".to_string(),
58                    inline: None,
59                },
60                params: ParamSchema {
61                    fields: rustc_hash::FxHashMap::default(),
62                },
63                timeout_ms: None,
64            }],
65            resources: vec![],
66            prompts: vec![],
67            state: None,
68        };
69
70        assert!(validate_config(&config).is_ok());
71    }
72
73    #[test]
74    fn test_validate_config_duplicate_tools() {
75        let config = ForgeConfig {
76            forge: ForgeMetadata {
77                name: "test".to_string(),
78                version: "1.0.0".to_string(),
79                transport: TransportType::Stdio,
80                optimization: OptimizationLevel::Debug,
81            },
82            tools: vec![
83                ToolDef::Native {
84                    name: "duplicate".to_string(),
85                    description: "Tool 1".to_string(),
86                    handler: HandlerRef {
87                        path: "module::handler1".to_string(),
88                        inline: None,
89                    },
90                    params: ParamSchema {
91                        fields: rustc_hash::FxHashMap::default(),
92                    },
93                    timeout_ms: None,
94                },
95                ToolDef::Native {
96                    name: "duplicate".to_string(),
97                    description: "Tool 2".to_string(),
98                    handler: HandlerRef {
99                        path: "module::handler2".to_string(),
100                        inline: None,
101                    },
102                    params: ParamSchema {
103                        fields: rustc_hash::FxHashMap::default(),
104                    },
105                    timeout_ms: None,
106                },
107            ],
108            resources: vec![],
109            prompts: vec![],
110            state: None,
111        };
112
113        let result = validate_config(&config);
114        assert!(result.is_err());
115        assert!(matches!(
116            result.unwrap_err(),
117            ConfigError::DuplicateToolName(_)
118        ));
119    }
120
121    #[test]
122    fn test_validate_handler_path_empty() {
123        let result = validate_handler_path("");
124        assert!(result.is_err());
125        assert!(matches!(
126            result.unwrap_err(),
127            ConfigError::InvalidHandlerPath(_)
128        ));
129    }
130
131    #[test]
132    fn test_validate_handler_path_invalid_format() {
133        let result = validate_handler_path("invalid_path");
134        assert!(result.is_err());
135        assert!(matches!(
136            result.unwrap_err(),
137            ConfigError::InvalidHandlerPath(_)
138        ));
139    }
140
141    #[test]
142    fn test_validate_handler_path_valid() {
143        let result = validate_handler_path("module::handler");
144        assert!(result.is_ok());
145    }
146}