fraiseql_cli/config/
mod.rs1pub mod security;
7pub mod toml_schema;
8
9use std::path::Path;
10
11use anyhow::{Context, Result};
12pub use security::SecurityConfig;
13use serde::{Deserialize, Serialize};
14pub use toml_schema::TomlSchema;
15use tracing::info;
16
17#[derive(Debug, Clone, Default, Deserialize, Serialize)]
19#[serde(default)]
20pub struct FraiseQLConfig {
21 #[serde(rename = "project")]
23 pub project: ProjectConfig,
24
25 #[serde(rename = "fraiseql")]
27 pub fraiseql: FraiseQLSettings,
28}
29
30#[derive(Debug, Clone, Deserialize, Serialize)]
32#[serde(default)]
33pub struct ProjectConfig {
34 pub name: String,
36 pub version: String,
38 pub description: Option<String>,
40}
41
42impl Default for ProjectConfig {
43 fn default() -> Self {
44 Self {
45 name: "my-fraiseql-app".to_string(),
46 version: "1.0.0".to_string(),
47 description: None,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Deserialize, Serialize)]
54#[serde(default)]
55pub struct FraiseQLSettings {
56 pub schema_file: String,
58 pub output_file: String,
60 #[serde(rename = "security")]
62 pub security: SecurityConfig,
63}
64
65impl Default for FraiseQLSettings {
66 fn default() -> Self {
67 Self {
68 schema_file: "schema.json".to_string(),
69 output_file: "schema.compiled.json".to_string(),
70 security: SecurityConfig::default(),
71 }
72 }
73}
74
75impl FraiseQLConfig {
76 pub fn from_file(path: &str) -> Result<Self> {
78 info!("Loading configuration from {path}");
79
80 let path = Path::new(path);
81 if !path.exists() {
82 anyhow::bail!("Configuration file not found: {}", path.display());
83 }
84
85 let toml_content = std::fs::read_to_string(path).context("Failed to read fraiseql.toml")?;
86
87 let config: FraiseQLConfig = toml::from_str(&toml_content)
88 .map_err(|e| anyhow::anyhow!("Failed to parse fraiseql.toml: {e}"))?;
89
90 Ok(config)
91 }
92
93 pub fn validate(&self) -> Result<()> {
95 info!("Validating configuration");
96 self.fraiseql.security.validate()?;
97 Ok(())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_default_config() {
107 let config = FraiseQLConfig::default();
108 assert_eq!(config.project.name, "my-fraiseql-app");
109 assert_eq!(config.fraiseql.schema_file, "schema.json");
110 }
111
112 #[test]
113 fn test_default_security_config() {
114 let config = FraiseQLConfig::default();
115 assert!(config.fraiseql.security.audit_logging.enabled);
116 assert!(config.fraiseql.security.rate_limiting.enabled);
117 }
118
119 #[test]
120 fn test_validation() {
121 let config = FraiseQLConfig::default();
122 assert!(config.validate().is_ok());
123 }
124
125 #[test]
126 fn test_role_definitions_default() {
127 let config = FraiseQLConfig::default();
128 assert!(config.fraiseql.security.role_definitions.is_empty());
129 assert!(config.fraiseql.security.default_role.is_none());
130 }
131
132 #[test]
133 fn test_parse_role_definitions_from_toml() {
134 let toml_str = r#"
135[project]
136name = "test-app"
137
138[fraiseql]
139schema_file = "schema.json"
140
141[[fraiseql.security.role_definitions]]
142name = "viewer"
143description = "Read-only access"
144scopes = ["read:*"]
145
146[[fraiseql.security.role_definitions]]
147name = "admin"
148description = "Full access"
149scopes = ["admin:*"]
150
151[fraiseql.security]
152default_role = "viewer"
153"#;
154
155 let config: FraiseQLConfig = toml::from_str(toml_str).expect("Failed to parse TOML");
156
157 assert_eq!(config.fraiseql.security.role_definitions.len(), 2);
158 assert_eq!(config.fraiseql.security.role_definitions[0].name, "viewer");
159 assert_eq!(config.fraiseql.security.role_definitions[0].scopes[0], "read:*");
160 assert_eq!(config.fraiseql.security.role_definitions[1].name, "admin");
161 assert_eq!(config.fraiseql.security.default_role, Some("viewer".to_string()));
162 }
163
164 #[test]
165 fn test_security_config_validation_empty_role_name() {
166 let toml_str = r#"
167[[fraiseql.security.role_definitions]]
168name = ""
169scopes = ["read:*"]
170"#;
171
172 let config: FraiseQLConfig = toml::from_str(toml_str).expect("Failed to parse TOML");
173 assert!(config.validate().is_err(), "Should fail with empty role name");
174 }
175
176 #[test]
177 fn test_security_config_validation_empty_scopes() {
178 let toml_str = r#"
179[[fraiseql.security.role_definitions]]
180name = "viewer"
181scopes = []
182"#;
183
184 let config: FraiseQLConfig = toml::from_str(toml_str).expect("Failed to parse TOML");
185 assert!(config.validate().is_err(), "Should fail with empty scopes");
186 }
187}