1use crate::types::RiskLevel;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::collections::HashSet;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CodeModeConfig {
11 #[serde(default)]
13 pub enabled: bool,
14
15 #[serde(default)]
20 pub allow_mutations: bool,
21
22 #[serde(default)]
24 pub allowed_mutations: HashSet<String>,
25
26 #[serde(default)]
28 pub blocked_mutations: HashSet<String>,
29
30 #[serde(default)]
32 pub allow_introspection: bool,
33
34 #[serde(default)]
36 pub blocked_fields: HashSet<String>,
37
38 #[serde(default)]
40 pub allowed_queries: HashSet<String>,
41
42 #[serde(default)]
44 pub blocked_queries: HashSet<String>,
45
46 #[serde(default = "default_true")]
51 pub openapi_reads_enabled: bool,
52
53 #[serde(default)]
55 pub openapi_allow_writes: bool,
56
57 #[serde(default)]
59 pub openapi_allowed_writes: HashSet<String>,
60
61 #[serde(default)]
63 pub openapi_blocked_writes: HashSet<String>,
64
65 #[serde(default)]
67 pub openapi_allow_deletes: bool,
68
69 #[serde(default)]
71 pub openapi_allowed_deletes: HashSet<String>,
72
73 #[serde(default)]
75 pub openapi_blocked_paths: HashSet<String>,
76
77 #[serde(default)]
79 pub openapi_internal_blocked_fields: HashSet<String>,
80
81 #[serde(default)]
83 pub openapi_output_blocked_fields: HashSet<String>,
84
85 #[serde(default)]
87 pub openapi_require_output_declaration: bool,
88
89 #[serde(default)]
94 pub action_tags: HashMap<String, String>,
95
96 #[serde(default = "default_max_depth")]
98 pub max_depth: u32,
99
100 #[serde(default = "default_max_field_count")]
102 pub max_field_count: u32,
103
104 #[serde(default = "default_max_cost")]
106 pub max_cost: u32,
107
108 #[serde(default)]
110 pub allowed_sensitive_categories: HashSet<String>,
111
112 #[serde(default = "default_token_ttl")]
114 pub token_ttl_seconds: i64,
115
116 #[serde(default = "default_auto_approve_levels")]
118 pub auto_approve_levels: Vec<RiskLevel>,
119
120 #[serde(default = "default_max_query_length")]
122 pub max_query_length: usize,
123
124 #[serde(default = "default_max_result_rows")]
126 pub max_result_rows: usize,
127
128 #[serde(default = "default_query_timeout")]
130 pub query_timeout_seconds: u32,
131
132 #[serde(default)]
134 pub server_id: Option<String>,
135
136 #[serde(default)]
143 pub sdk_operations: HashSet<String>,
144}
145
146impl Default for CodeModeConfig {
147 fn default() -> Self {
148 Self {
149 enabled: false,
150 allow_mutations: false,
152 allowed_mutations: HashSet::new(),
153 blocked_mutations: HashSet::new(),
154 allow_introspection: false,
155 blocked_fields: HashSet::new(),
156 allowed_queries: HashSet::new(),
157 blocked_queries: HashSet::new(),
158 openapi_reads_enabled: true,
160 openapi_allow_writes: false,
161 openapi_allowed_writes: HashSet::new(),
162 openapi_blocked_writes: HashSet::new(),
163 openapi_allow_deletes: false,
164 openapi_allowed_deletes: HashSet::new(),
165 openapi_blocked_paths: HashSet::new(),
166 openapi_internal_blocked_fields: HashSet::new(),
167 openapi_output_blocked_fields: HashSet::new(),
168 openapi_require_output_declaration: false,
169 action_tags: HashMap::new(),
171 max_depth: default_max_depth(),
172 max_field_count: default_max_field_count(),
173 max_cost: default_max_cost(),
174 allowed_sensitive_categories: HashSet::new(),
175 token_ttl_seconds: default_token_ttl(),
176 auto_approve_levels: default_auto_approve_levels(),
177 max_query_length: default_max_query_length(),
178 max_result_rows: default_max_result_rows(),
179 query_timeout_seconds: default_query_timeout(),
180 server_id: None,
181 sdk_operations: HashSet::new(),
183 }
184 }
185}
186
187impl CodeModeConfig {
188 pub fn enabled() -> Self {
190 Self {
191 enabled: true,
192 ..Default::default()
193 }
194 }
195
196 pub fn is_sdk_mode(&self) -> bool {
198 !self.sdk_operations.is_empty()
199 }
200
201 pub fn should_auto_approve(&self, risk_level: RiskLevel) -> bool {
203 self.auto_approve_levels.contains(&risk_level)
204 }
205
206 pub fn server_id(&self) -> &str {
208 self.server_id.as_deref().unwrap_or("unknown")
209 }
210
211 pub fn to_server_config_entity(&self) -> crate::policy::ServerConfigEntity {
213 crate::policy::ServerConfigEntity {
214 server_id: self.server_id().to_string(),
215 server_type: "graphql".to_string(),
216 allow_write: self.allow_mutations,
217 allow_delete: self.allow_mutations,
218 allow_admin: self.allow_introspection,
219 allowed_operations: self.allowed_mutations.clone(),
220 blocked_operations: self.blocked_mutations.clone(),
221 max_depth: self.max_depth,
222 max_field_count: self.max_field_count,
223 max_cost: self.max_cost,
224 max_api_calls: 50,
225 blocked_fields: self.blocked_fields.clone(),
226 allowed_sensitive_categories: self.allowed_sensitive_categories.clone(),
227 }
228 }
229
230 #[cfg(feature = "openapi-code-mode")]
232 pub fn to_openapi_server_entity(&self) -> crate::policy::OpenAPIServerEntity {
233 let mut allowed_operations = self.openapi_allowed_writes.clone();
234 allowed_operations.extend(self.openapi_allowed_deletes.clone());
235
236 let write_mode = if !self.openapi_allow_writes {
237 "deny_all"
238 } else if !self.openapi_allowed_writes.is_empty() {
239 "allowlist"
240 } else if !self.openapi_blocked_writes.is_empty() {
241 "blocklist"
242 } else {
243 "allow_all"
244 };
245
246 crate::policy::OpenAPIServerEntity {
247 server_id: self.server_id().to_string(),
248 server_type: "openapi".to_string(),
249 allow_write: self.openapi_allow_writes,
250 allow_delete: self.openapi_allow_deletes,
251 allow_admin: false,
252 write_mode: write_mode.to_string(),
253 max_depth: self.max_depth,
254 max_cost: self.max_cost,
255 max_api_calls: 50,
256 max_loop_iterations: 100,
257 max_script_length: self.max_query_length as u32,
258 max_nesting_depth: self.max_depth,
259 execution_timeout_seconds: self.query_timeout_seconds,
260 allowed_operations,
261 blocked_operations: self.openapi_blocked_writes.clone(),
262 allowed_methods: HashSet::new(),
263 blocked_methods: HashSet::new(),
264 allowed_path_patterns: HashSet::new(),
265 blocked_path_patterns: self.openapi_blocked_paths.clone(),
266 sensitive_path_patterns: self.openapi_blocked_paths.clone(),
267 auto_approve_read_only: self.openapi_reads_enabled,
268 max_api_calls_for_auto_approve: 10,
269 internal_blocked_fields: self.openapi_internal_blocked_fields.clone(),
270 output_blocked_fields: self.openapi_output_blocked_fields.clone(),
271 require_output_declaration: self.openapi_require_output_declaration,
272 }
273 }
274}
275
276fn default_true() -> bool {
277 true
278}
279
280fn default_token_ttl() -> i64 {
281 300 }
283
284fn default_auto_approve_levels() -> Vec<RiskLevel> {
285 vec![RiskLevel::Low]
286}
287
288fn default_max_query_length() -> usize {
289 10000
290}
291
292fn default_max_result_rows() -> usize {
293 10000
294}
295
296fn default_query_timeout() -> u32 {
297 30
298}
299
300fn default_max_depth() -> u32 {
301 10
302}
303
304fn default_max_field_count() -> u32 {
305 100
306}
307
308fn default_max_cost() -> u32 {
309 1000
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_default_config() {
318 let config = CodeModeConfig::default();
319 assert!(!config.enabled);
320 assert!(!config.allow_mutations);
321 assert_eq!(config.token_ttl_seconds, 300);
322 assert_eq!(config.auto_approve_levels, vec![RiskLevel::Low]);
323 }
324
325 #[test]
326 fn test_enabled_config() {
327 let config = CodeModeConfig::enabled();
328 assert!(config.enabled);
329 }
330
331 #[test]
332 fn test_auto_approve() {
333 let config = CodeModeConfig::default();
334 assert!(config.should_auto_approve(RiskLevel::Low));
335 assert!(!config.should_auto_approve(RiskLevel::Medium));
336 assert!(!config.should_auto_approve(RiskLevel::High));
337 assert!(!config.should_auto_approve(RiskLevel::Critical));
338 }
339}