1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[allow(clippy::derive_partial_eq_without_eq)]
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13#[serde(deny_unknown_fields)]
14pub struct GgenConfig {
15 pub project: ProjectConfig,
17
18 #[serde(skip_serializing_if = "Option::is_none")]
20 pub ai: Option<AiConfig>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub templates: Option<TemplatesConfig>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub rdf: Option<RdfConfig>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub sparql: Option<SparqlConfig>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub lifecycle: Option<LifecycleConfig>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub security: Option<SecurityConfig>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub performance: Option<PerformanceConfig>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub logging: Option<LoggingConfig>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub telemetry: Option<TelemetryConfig>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub features: Option<HashMap<String, bool>>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub env: Option<HashMap<String, serde_json::Value>>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub build: Option<BuildConfig>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub test: Option<TestConfig>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub package: Option<PackageMetadata>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub inference: Option<InferenceConfig>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub generation: Option<GenerationConfig>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub mcp: Option<McpConfig>,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub a2a: Option<A2AConfig>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
93pub struct ProjectConfig {
94 pub name: String,
96
97 pub version: String,
99
100 #[serde(skip_serializing_if = "Option::is_none")]
102 pub description: Option<String>,
103
104 #[serde(skip_serializing_if = "Option::is_none")]
106 pub authors: Option<Vec<String>>,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub license: Option<String>,
111
112 #[serde(skip_serializing_if = "Option::is_none")]
114 pub repository: Option<String>,
115}
116
117#[allow(clippy::derive_partial_eq_without_eq)]
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
121pub struct AiConfig {
122 pub provider: String,
124
125 pub model: String,
127
128 #[serde(default = "default_temperature")]
130 pub temperature: f32,
131
132 #[serde(default = "default_max_tokens")]
134 pub max_tokens: u32,
135
136 #[serde(default = "default_timeout")]
138 pub timeout: u32,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub prompts: Option<AiPrompts>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub validation: Option<AiValidation>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
151pub struct AiPrompts {
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub system: Option<String>,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub user_prefix: Option<String>,
159}
160
161#[allow(clippy::derive_partial_eq_without_eq)]
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
165pub struct AiValidation {
166 #[serde(default)]
168 pub enabled: bool,
169
170 #[serde(default = "default_quality_threshold")]
172 pub quality_threshold: f32,
173
174 #[serde(default = "default_max_iterations")]
176 pub max_iterations: u32,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
181pub struct TemplatesConfig {
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub directory: Option<String>,
185
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub output_directory: Option<String>,
189
190 #[serde(default)]
192 pub backup_enabled: bool,
193
194 #[serde(default)]
196 pub idempotent: bool,
197}
198
199#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
201pub struct RdfConfig {
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub base_uri: Option<String>,
205
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub base_iri: Option<String>,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub prefixes: Option<HashMap<String, String>>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub default_format: Option<String>,
217
218 #[serde(default)]
220 pub cache_queries: bool,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
225pub struct SparqlConfig {
226 #[serde(default = "default_sparql_timeout")]
228 pub timeout: u32,
229
230 #[serde(default = "default_max_results")]
232 pub max_results: u32,
233
234 #[serde(default)]
236 pub cache_enabled: bool,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
241pub struct LifecycleConfig {
242 #[serde(default)]
244 pub enabled: bool,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub config_file: Option<String>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub cache_directory: Option<String>,
253
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub state_file: Option<String>,
257
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub phases: Option<HashMap<String, Vec<String>>>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
265pub struct SecurityConfig {
266 #[serde(default = "default_true")]
268 pub path_traversal_protection: bool,
269
270 #[serde(default = "default_true")]
272 pub shell_injection_protection: bool,
273
274 #[serde(default = "default_true")]
276 pub template_sandboxing: bool,
277
278 #[serde(default = "default_true")]
280 pub validate_paths: bool,
281
282 #[serde(default)]
284 pub require_confirmation: bool,
285
286 #[serde(default)]
288 pub audit_operations: bool,
289
290 #[serde(default)]
292 pub backup_before_write: bool,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
297pub struct PerformanceConfig {
298 #[serde(default)]
300 pub parallel_execution: bool,
301
302 #[serde(default = "default_max_workers")]
304 pub max_workers: u32,
305
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub cache_size: Option<String>,
309
310 #[serde(default)]
312 pub enable_profiling: bool,
313
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub memory_limit_mb: Option<u32>,
317}
318
319#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
321pub struct LoggingConfig {
322 #[serde(default = "default_log_level")]
324 pub level: String,
325
326 #[serde(default = "default_log_format")]
328 pub format: String,
329
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub file: Option<String>,
333
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub rotation: Option<String>,
337}
338
339#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
341pub struct TelemetryConfig {
342 #[serde(default = "default_telemetry_endpoint")]
344 pub endpoint: String,
345
346 #[serde(default = "default_telemetry_service_name")]
348 pub service_name: String,
349
350 #[serde(default = "default_telemetry_console_output")]
352 pub console_output: bool,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
357pub struct BuildConfig {
358 #[serde(skip_serializing_if = "Option::is_none")]
360 pub target: Option<String>,
361
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub features: Option<Vec<String>>,
365
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub profile: Option<String>,
369
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub parallel_jobs: Option<u32>,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
377pub struct TestConfig {
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub framework: Option<String>,
381
382 #[serde(default)]
384 pub parallel: bool,
385
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub timeout_seconds: Option<u32>,
389
390 #[serde(default)]
392 pub coverage_enabled: bool,
393
394 #[serde(skip_serializing_if = "Option::is_none")]
396 pub coverage_threshold: Option<u32>,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
401pub struct PackageMetadata {
402 pub name: String,
404
405 pub version: String,
407
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub description: Option<String>,
411
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub authors: Option<Vec<String>>,
415
416 #[serde(skip_serializing_if = "Option::is_none")]
418 pub license: Option<String>,
419
420 #[serde(skip_serializing_if = "Option::is_none")]
422 pub repository: Option<String>,
423
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub keywords: Option<Vec<String>>,
427
428 #[serde(skip_serializing_if = "Option::is_none")]
430 pub categories: Option<Vec<String>>,
431
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub metadata: Option<HashMap<String, serde_json::Value>>,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
443pub struct McpConfig {
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub name: Option<String>,
447
448 #[serde(skip_serializing_if = "Option::is_none")]
450 pub version: Option<String>,
451
452 #[serde(default = "default_mcp_tool_timeout")]
454 pub tool_timeout_ms: u64,
455
456 #[serde(default = "default_mcp_max_concurrent")]
458 pub max_concurrent_requests: usize,
459
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub transport: Option<McpTransportConfig>,
463
464 #[serde(skip_serializing_if = "Option::is_none")]
466 pub tools: Option<McpToolsConfig>,
467
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub zai: Option<McpZaiConfig>,
471
472 #[serde(default = "default_mcp_enabled")]
474 pub enabled: bool,
475
476 #[serde(skip_serializing_if = "Option::is_none")]
478 pub discovery: Option<McpDiscoveryConfig>,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
483pub struct McpTransportConfig {
484 #[serde(default = "default_mcp_transport_type")]
486 pub transport_type: String,
487
488 #[serde(skip_serializing_if = "Option::is_none")]
490 pub port: Option<u16>,
491
492 #[serde(default = "default_mcp_host")]
494 pub host: String,
495
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub tls: Option<McpTlsConfig>,
499
500 #[serde(default = "default_mcp_request_timeout")]
502 pub request_timeout_seconds: u64,
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
507pub struct McpTlsConfig {
508 #[serde(default)]
510 pub enabled: bool,
511
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub cert_path: Option<String>,
515
516 #[serde(skip_serializing_if = "Option::is_none")]
518 pub key_path: Option<String>,
519
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub ca_path: Option<String>,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
527pub struct McpToolsConfig {
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub discovery_path: Option<String>,
531
532 #[serde(default)]
534 pub require_registration: bool,
535
536 #[serde(default = "default_true")]
538 pub validate_signatures: bool,
539
540 #[serde(skip_serializing_if = "Option::is_none")]
542 pub allowed_prefixes: Option<Vec<String>>,
543}
544
545#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
547pub struct McpZaiConfig {
548 #[serde(default)]
550 pub enabled: bool,
551
552 #[serde(skip_serializing_if = "Option::is_none")]
554 pub provider_url: Option<String>,
555
556 #[serde(skip_serializing_if = "Option::is_none")]
558 pub model: Option<String>,
559
560 #[serde(default = "default_mcp_zai_cache")]
562 pub cache_enabled: bool,
563
564 #[serde(default = "default_mcp_zai_cache_ttl")]
566 pub cache_ttl_seconds: u64,
567}
568
569#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
571pub struct McpDiscoveryConfig {
572 #[serde(default)]
574 pub enabled: bool,
575
576 #[serde(default = "default_mcp_discovery_method")]
578 pub method: String,
579
580 #[serde(skip_serializing_if = "Option::is_none")]
582 pub registry_url: Option<String>,
583
584 #[serde(default = "default_mcp_discovery_cache_ttl")]
586 pub cache_ttl_seconds: u64,
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
594pub struct A2AConfig {
595 #[serde(skip_serializing_if = "Option::is_none")]
597 pub agent_id: Option<String>,
598
599 #[serde(skip_serializing_if = "Option::is_none")]
601 pub agent_name: Option<String>,
602
603 #[serde(skip_serializing_if = "Option::is_none")]
605 pub agent_type: Option<String>,
606
607 #[serde(skip_serializing_if = "Option::is_none")]
609 pub transport: Option<A2ATransportConfig>,
610
611 #[serde(skip_serializing_if = "Option::is_none")]
613 pub messaging: Option<A2AMessagingConfig>,
614
615 #[serde(skip_serializing_if = "Option::is_none")]
617 pub orchestration: Option<A2AOrchestrationConfig>,
618
619 #[serde(skip_serializing_if = "Option::is_none")]
621 pub capabilities: Option<Vec<String>>,
622
623 #[serde(default = "default_a2a_enabled")]
625 pub enabled: bool,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
630pub struct A2ATransportConfig {
631 #[serde(default = "default_a2a_transport_type")]
633 pub transport_type: String,
634
635 #[serde(skip_serializing_if = "Option::is_none")]
637 pub bind_address: Option<String>,
638
639 #[serde(skip_serializing_if = "Option::is_none")]
641 pub port: Option<u16>,
642
643 #[serde(default = "default_a2a_timeout")]
645 pub timeout_ms: u64,
646
647 #[serde(skip_serializing_if = "Option::is_none")]
649 pub max_connections: Option<usize>,
650
651 #[serde(skip_serializing_if = "Option::is_none")]
653 pub retry: Option<A2ARetryConfig>,
654}
655
656#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
658pub struct A2ARetryConfig {
659 #[serde(default = "default_a2a_max_retries")]
661 pub max_attempts: u32,
662
663 #[serde(default = "default_a2a_retry_delay")]
665 pub initial_delay_ms: u64,
666
667 #[serde(default = "default_a2a_max_retry_delay")]
669 pub max_delay_ms: u64,
670
671 #[serde(default)]
673 pub exponential_backoff: bool,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
678pub struct A2AMessagingConfig {
679 #[serde(default = "default_a2a_queue_size")]
681 pub queue_size: usize,
682
683 #[serde(default = "default_a2a_message_ttl")]
685 pub message_ttl_seconds: u64,
686
687 #[serde(default)]
689 pub persistence_enabled: bool,
690
691 #[serde(skip_serializing_if = "Option::is_none")]
693 pub persistence_path: Option<String>,
694
695 #[serde(default)]
697 pub signing_enabled: bool,
698
699 #[serde(skip_serializing_if = "Option::is_none")]
701 pub signature_algorithm: Option<String>,
702}
703
704#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
706pub struct A2AOrchestrationConfig {
707 #[serde(default = "default_a2a_orchestration_mode")]
709 pub mode: String,
710
711 #[serde(skip_serializing_if = "Option::is_none")]
713 pub coordinator_address: Option<String>,
714
715 #[serde(default = "default_a2a_heartbeat_interval")]
717 pub heartbeat_interval_seconds: u64,
718
719 #[serde(default = "default_a2a_agent_timeout")]
721 pub agent_timeout_seconds: u64,
722
723 #[serde(default)]
725 pub consensus_enabled: bool,
726
727 #[serde(skip_serializing_if = "Option::is_none")]
729 pub consensus_algorithm: Option<String>,
730}
731
732#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
734pub struct InferenceConfig {
735 #[serde(default)]
737 pub rules: Vec<InferenceRule>,
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
742pub struct InferenceRule {
743 pub name: String,
745 pub construct: String,
747 #[serde(default)]
749 pub order: i32,
750 #[serde(skip_serializing_if = "Option::is_none")]
752 pub when: Option<String>,
753}
754
755#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
757pub struct GenerationConfig {
758 pub output_dir: String,
760 #[serde(default)]
762 pub rules: Vec<serde_json::Value>,
763}
764
765const fn default_temperature() -> f32 {
767 0.7
768}
769
770const fn default_max_tokens() -> u32 {
771 2000
772}
773
774const fn default_timeout() -> u32 {
775 30
776}
777
778const fn default_quality_threshold() -> f32 {
779 0.8
780}
781
782const fn default_max_iterations() -> u32 {
783 3
784}
785
786const fn default_sparql_timeout() -> u32 {
787 10
788}
789
790const fn default_max_results() -> u32 {
791 1000
792}
793
794fn default_max_workers() -> u32 {
795 num_cpus()
796}
797
798fn default_log_level() -> String {
799 "info".to_string()
800}
801
802fn default_log_format() -> String {
803 "text".to_string()
804}
805
806const fn default_true() -> bool {
807 true
808}
809
810fn num_cpus() -> u32 {
811 std::thread::available_parallelism().map_or(4, |n| n.get() as u32)
812}
813
814impl Default for GgenConfig {
815 fn default() -> Self {
816 Self {
817 project: ProjectConfig {
818 name: "unnamed".to_string(),
819 version: "0.1.0".to_string(),
820 description: None,
821 authors: None,
822 license: None,
823 repository: None,
824 },
825 ai: None,
826 templates: None,
827 rdf: None,
828 sparql: None,
829 lifecycle: None,
830 security: None,
831 performance: None,
832 logging: None,
833 telemetry: None,
834 features: None,
835 env: None,
836 build: None,
837 test: None,
838 package: None,
839 inference: None,
840 generation: None,
841 mcp: None,
842 a2a: None,
843 }
844 }
845}
846
847const fn default_mcp_tool_timeout() -> u64 {
849 30000
850}
851
852const fn default_mcp_max_concurrent() -> usize {
853 100
854}
855
856fn default_mcp_transport_type() -> String {
857 "stdio".to_string()
858}
859
860fn default_mcp_host() -> String {
861 "127.0.0.1".to_string()
862}
863
864const fn default_mcp_request_timeout() -> u64 {
865 30
866}
867
868fn default_mcp_enabled() -> bool {
869 false
870}
871
872const fn default_mcp_zai_cache() -> bool {
873 true
874}
875
876const fn default_mcp_zai_cache_ttl() -> u64 {
877 3600
878}
879
880fn default_mcp_discovery_method() -> String {
881 "local".to_string()
882}
883
884const fn default_mcp_discovery_cache_ttl() -> u64 {
885 7200
886}
887
888fn default_a2a_enabled() -> bool {
889 false
890}
891
892fn default_a2a_transport_type() -> String {
893 "memory".to_string()
894}
895
896const fn default_a2a_timeout() -> u64 {
897 5000
898}
899
900const fn default_a2a_max_retries() -> u32 {
901 3
902}
903
904const fn default_a2a_retry_delay() -> u64 {
905 1000
906}
907
908const fn default_a2a_max_retry_delay() -> u64 {
909 30000
910}
911
912const fn default_a2a_queue_size() -> usize {
913 1000
914}
915
916const fn default_a2a_message_ttl() -> u64 {
917 3600
918}
919
920fn default_a2a_orchestration_mode() -> String {
921 "decentralized".to_string()
922}
923
924const fn default_a2a_heartbeat_interval() -> u64 {
925 30
926}
927
928const fn default_a2a_agent_timeout() -> u64 {
929 300
930}
931
932fn default_telemetry_endpoint() -> String {
933 "http://localhost:4317".to_string()
934}
935
936fn default_telemetry_service_name() -> String {
937 "ggen".to_string()
938}
939
940fn default_telemetry_console_output() -> bool {
941 false
942}
943
944use star_toml::{Validate, Validator};
949
950impl Validate for GgenConfig {
951 fn validate(&self, v: &mut Validator) {
952 v.field("project", |v| self.project.validate(v));
953 if let Some(ai) = &self.ai {
954 v.field("ai", |v| ai.validate(v));
955 }
956 if let Some(templates) = &self.templates {
957 v.field("templates", |v| templates.validate(v));
958 }
959 if let Some(rdf) = &self.rdf {
960 v.field("rdf", |v| rdf.validate(v));
961 }
962 if let Some(sparql) = &self.sparql {
963 v.field("sparql", |v| sparql.validate(v));
964 }
965 if let Some(lifecycle) = &self.lifecycle {
966 v.field("lifecycle", |v| lifecycle.validate(v));
967 }
968 if let Some(security) = &self.security {
969 v.field("security", |v| security.validate(v));
970 }
971 if let Some(performance) = &self.performance {
972 v.field("performance", |v| performance.validate(v));
973 }
974 if let Some(logging) = &self.logging {
975 v.field("logging", |v| logging.validate(v));
976 }
977 if let Some(telemetry) = &self.telemetry {
978 v.field("telemetry", |v| telemetry.validate(v));
979 }
980 if let Some(build) = &self.build {
981 v.field("build", |v| build.validate(v));
982 }
983 if let Some(test) = &self.test {
984 v.field("test", |v| test.validate(v));
985 }
986 if let Some(package) = &self.package {
987 v.field("package", |v| package.validate(v));
988 }
989 if let Some(mcp) = &self.mcp {
990 v.field("mcp", |v| mcp.validate(v));
991 }
992 if let Some(a2a) = &self.a2a {
993 v.field("a2a", |v| a2a.validate(v));
994 }
995 }
996}
997
998impl Validate for ProjectConfig {
999 fn validate(&self, v: &mut Validator) {
1000 v.check_non_empty("name", &self.name);
1001 v.check_semver("version", &self.version);
1002 }
1003}
1004
1005impl Validate for AiConfig {
1006 fn validate(&self, v: &mut Validator) {
1007 let providers = ["openai", "ollama", "anthropic", "cohere", "huggingface"];
1008 v.check_one_of("provider", &self.provider, &providers);
1009 v.check_range("temperature", self.temperature, 0.0..=1.0);
1010 v.check_predicate(
1011 "max_tokens",
1012 self.max_tokens > 0,
1013 "out_of_range",
1014 "AI max_tokens must be greater than 0",
1015 );
1016 v.check_predicate(
1017 "timeout",
1018 self.timeout > 0,
1019 "out_of_range",
1020 "AI timeout must be greater than 0",
1021 );
1022 if let Some(prompts) = &self.prompts {
1023 v.field("prompts", |v| prompts.validate(v));
1024 }
1025 if let Some(validation) = &self.validation {
1026 v.field("validation", |v| validation.validate(v));
1027 }
1028 }
1029}
1030
1031impl Validate for AiPrompts {
1032 fn validate(&self, _v: &mut Validator) {}
1033}
1034
1035impl Validate for AiValidation {
1036 fn validate(&self, v: &mut Validator) {
1037 v.check_range("quality_threshold", self.quality_threshold, 0.0..=1.0);
1038 }
1039}
1040
1041impl Validate for TemplatesConfig {
1042 fn validate(&self, v: &mut Validator) {
1043 if let Some(dir) = &self.directory {
1044 v.check_path("directory", dir, None);
1045 }
1046 if let Some(out_dir) = &self.output_directory {
1047 v.check_path("output_directory", out_dir, None);
1048 }
1049 }
1050}
1051
1052impl Validate for RdfConfig {
1053 fn validate(&self, _v: &mut Validator) {}
1054}
1055
1056impl Validate for SparqlConfig {
1057 fn validate(&self, _v: &mut Validator) {}
1058}
1059
1060impl Validate for LifecycleConfig {
1061 fn validate(&self, _v: &mut Validator) {}
1062}
1063
1064impl Validate for SecurityConfig {
1065 fn validate(&self, _v: &mut Validator) {}
1066}
1067
1068impl Validate for PerformanceConfig {
1069 fn validate(&self, v: &mut Validator) {
1070 v.check_consistent(
1071 "max_workers",
1072 &["parallel_execution"],
1073 !self.parallel_execution || self.max_workers > 0,
1074 "out_of_range",
1075 "Performance max_workers must be greater than 0 when parallel_execution is enabled",
1076 );
1077 if let Some(cache_size) = &self.cache_size {
1078 v.check_size_format("cache_size", cache_size);
1079 }
1080 }
1081}
1082
1083impl Validate for LoggingConfig {
1084 fn validate(&self, v: &mut Validator) {
1085 let level = self.level.to_lowercase();
1086 v.check_one_of(
1087 "level",
1088 &level,
1089 &["trace", "debug", "info", "warn", "error"],
1090 );
1091 let format = self.format.to_lowercase();
1092 v.check_one_of("format", &format, &["json", "text", "pretty"]);
1093 if let Some(file) = &self.file {
1094 v.check_path("file", file, None);
1095 }
1096 }
1097}
1098
1099impl Validate for TelemetryConfig {
1100 fn validate(&self, _v: &mut Validator) {}
1101}
1102
1103impl Validate for BuildConfig {
1104 fn validate(&self, _v: &mut Validator) {}
1105}
1106
1107impl Validate for TestConfig {
1108 fn validate(&self, _v: &mut Validator) {}
1109}
1110
1111impl Validate for PackageMetadata {
1112 fn validate(&self, _v: &mut Validator) {}
1113}
1114
1115impl Validate for McpConfig {
1116 fn validate(&self, v: &mut Validator) {
1117 v.check_predicate(
1118 "tool_timeout_ms",
1119 self.tool_timeout_ms > 0,
1120 "out_of_range",
1121 "MCP tool_timeout_ms must be greater than 0",
1122 );
1123 v.check_predicate(
1124 "max_concurrent_requests",
1125 self.max_concurrent_requests > 0,
1126 "out_of_range",
1127 "MCP max_concurrent_requests must be greater than 0",
1128 );
1129 if let Some(transport) = &self.transport {
1130 v.field("transport", |v| transport.validate(v));
1131 }
1132 if let Some(tools) = &self.tools {
1133 v.field("tools", |v| tools.validate(v));
1134 }
1135 if let Some(zai) = &self.zai {
1136 v.field("zai", |v| zai.validate(v));
1137 }
1138 if let Some(discovery) = &self.discovery {
1139 v.field("discovery", |v| discovery.validate(v));
1140 }
1141 }
1142}
1143
1144impl Validate for McpTransportConfig {
1145 fn validate(&self, v: &mut Validator) {
1146 let valid_transports = ["stdio", "http", "websocket"];
1147 v.check_one_of("transport_type", &self.transport_type, &valid_transports);
1148 if let Some(port) = self.port {
1149 v.check_range("port", port, 1..=65535);
1150 }
1151 if let Some(tls) = &self.tls {
1152 v.field("tls", |v| tls.validate(v));
1153 }
1154 }
1155}
1156
1157impl Validate for McpTlsConfig {
1158 fn validate(&self, v: &mut Validator) {
1159 if let Some(cert_path) = &self.cert_path {
1160 v.check_path("cert_path", cert_path, None);
1161 }
1162 if let Some(key_path) = &self.key_path {
1163 v.check_path("key_path", key_path, None);
1164 }
1165 if let Some(ca_path) = &self.ca_path {
1166 v.check_path("ca_path", ca_path, None);
1167 }
1168 }
1169}
1170
1171impl Validate for McpToolsConfig {
1172 fn validate(&self, v: &mut Validator) {
1173 if let Some(discovery_path) = &self.discovery_path {
1174 v.check_path("discovery_path", discovery_path, None);
1175 }
1176 }
1177}
1178
1179impl Validate for McpZaiConfig {
1180 fn validate(&self, _v: &mut Validator) {}
1181}
1182
1183impl Validate for McpDiscoveryConfig {
1184 fn validate(&self, _v: &mut Validator) {}
1185}
1186
1187impl Validate for A2AConfig {
1188 fn validate(&self, v: &mut Validator) {
1189 if let Some(transport) = &self.transport {
1190 v.field("transport", |v| transport.validate(v));
1191 }
1192 if let Some(messaging) = &self.messaging {
1193 v.field("messaging", |v| messaging.validate(v));
1194 }
1195 if let Some(orchestration) = &self.orchestration {
1196 v.field("orchestration", |v| orchestration.validate(v));
1197 }
1198 }
1199}
1200
1201impl Validate for A2ATransportConfig {
1202 fn validate(&self, v: &mut Validator) {
1203 let valid_transports = ["memory", "http", "websocket", "amqp"];
1204 v.check_one_of("transport_type", &self.transport_type, &valid_transports);
1205 if let Some(port) = self.port {
1206 v.check_range("port", port, 1..=65535);
1207 }
1208 if let Some(retry) = &self.retry {
1209 v.field("retry", |v| retry.validate(v));
1210 }
1211 }
1212}
1213
1214impl Validate for A2ARetryConfig {
1215 fn validate(&self, _v: &mut Validator) {}
1216}
1217
1218impl Validate for A2AMessagingConfig {
1219 fn validate(&self, v: &mut Validator) {
1220 if let Some(persistence_path) = &self.persistence_path {
1221 v.check_path("persistence_path", persistence_path, None);
1222 }
1223 }
1224}
1225
1226impl Validate for A2AOrchestrationConfig {
1227 fn validate(&self, v: &mut Validator) {
1228 let valid_modes = ["centralized", "decentralized", "hierarchical"];
1229 v.check_one_of("mode", &self.mode, &valid_modes);
1230 if self.consensus_enabled {
1231 if let Some(algo) = &self.consensus_algorithm {
1232 let valid_algos = ["raft", "pbft", "naive"];
1233 v.check_one_of("consensus_algorithm", algo, &valid_algos);
1234 } else {
1235 v.check_predicate(
1236 "consensus_algorithm",
1237 false,
1238 "missing",
1239 "A2A consensus algorithm must be specified when consensus is enabled",
1240 );
1241 }
1242 }
1243 }
1244}
1245
1246#[cfg(test)]
1247#[allow(clippy::unwrap_used, clippy::expect_used)]
1248mod tests {
1249 use super::*;
1250 use star_toml::Validate;
1251
1252 #[test]
1253 fn test_ggen_config_validate_trait() {
1254 let mut config = GgenConfig::default();
1255 config.project.name = String::new(); config.project.version = "1.0".to_string(); let errs = config.check().unwrap_err();
1259 let error_msgs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1260 assert!(error_msgs.contains(&"project.name".to_string()));
1261 assert!(error_msgs.contains(&"project.version".to_string()));
1262
1263 config.project.name = "my-project".to_string();
1264 config.project.version = "1.0.0".to_string();
1265 assert!(config.check().is_ok());
1266
1267 config.ai = Some(AiConfig {
1269 provider: "unknown".to_string(),
1270 model: "gpt-4".to_string(),
1271 temperature: 1.5, max_tokens: 0, timeout: 0, prompts: None,
1275 validation: Some(AiValidation {
1276 enabled: true,
1277 quality_threshold: -0.5, max_iterations: 3,
1279 }),
1280 });
1281
1282 let errs = config.check().unwrap_err();
1283 let error_locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1284 assert!(error_locs.contains(&"ai.provider".to_string()));
1285 assert!(error_locs.contains(&"ai.temperature".to_string()));
1286 assert!(error_locs.contains(&"ai.max_tokens".to_string()));
1287 assert!(error_locs.contains(&"ai.timeout".to_string()));
1288 assert!(error_locs.contains(&"ai.validation.quality_threshold".to_string()));
1289 }
1290
1291 #[test]
1292 fn test_ggen_config_optional_subconfigs_none() {
1293 let mut config = GgenConfig::default();
1294 config.project.name = "my-project".to_string();
1295 config.project.version = "1.0.0".to_string();
1296 config.ai = None;
1298 config.templates = None;
1299 config.rdf = None;
1300 config.sparql = None;
1301 config.lifecycle = None;
1302 config.security = None;
1303 config.performance = None;
1304 config.logging = None;
1305 config.telemetry = None;
1306 config.build = None;
1307 config.test = None;
1308 config.package = None;
1309 config.mcp = None;
1310 config.a2a = None;
1311
1312 assert!(config.check().is_ok());
1313 }
1314
1315 #[test]
1316 fn test_ggen_config_extreme_values() {
1317 let mut config = GgenConfig::default();
1318 config.project.name = "my-project".to_string();
1319 config.project.version = "1.0.0".to_string();
1320
1321 config.performance = Some(PerformanceConfig {
1323 parallel_execution: true,
1324 max_workers: 0, cache_size: Some("invalid_size".to_string()), enable_profiling: false,
1327 memory_limit_mb: None,
1328 });
1329
1330 let errs = config.check().unwrap_err();
1331 let error_locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1332 assert!(error_locs.contains(&"performance.max_workers".to_string()));
1333 assert!(error_locs.contains(&"performance.cache_size".to_string()));
1334
1335 config.performance = None;
1337 config.logging = Some(LoggingConfig {
1338 level: "SUPER_FATAL".to_string(), format: "xml".to_string(), file: None,
1341 rotation: None,
1342 });
1343
1344 let errs = config.check().unwrap_err();
1345 let error_locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1346 assert!(error_locs.contains(&"logging.level".to_string()));
1347 assert!(error_locs.contains(&"logging.format".to_string()));
1348
1349 config.logging = None;
1351 config.mcp = Some(McpConfig {
1352 name: Some("test-mcp".to_string()),
1353 version: Some("1.0.0".to_string()),
1354 enabled: true,
1355 tool_timeout_ms: 0, max_concurrent_requests: 0, transport: Some(McpTransportConfig {
1358 transport_type: "ftp".to_string(), port: Some(0), host: "localhost".to_string(),
1361 tls: None,
1362 request_timeout_seconds: 30,
1363 }),
1364 tools: None,
1365 zai: None,
1366 discovery: None,
1367 });
1368
1369 let errs = config.check().unwrap_err();
1370 let error_locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1371 assert!(error_locs.contains(&"mcp.tool_timeout_ms".to_string()));
1372 assert!(error_locs.contains(&"mcp.max_concurrent_requests".to_string()));
1373 assert!(error_locs.contains(&"mcp.transport.transport_type".to_string()));
1374 assert!(error_locs.contains(&"mcp.transport.port".to_string()));
1375
1376 config.mcp = None;
1378 config.a2a = Some(A2AConfig {
1379 agent_id: Some("agent-1".to_string()),
1380 agent_name: Some("test-agent".to_string()),
1381 agent_type: Some("worker".to_string()),
1382 capabilities: None,
1383 enabled: true,
1384 transport: Some(A2ATransportConfig {
1385 transport_type: "gRPC".to_string(), bind_address: Some("127.0.0.1".to_string()),
1387 port: Some(9999), timeout_ms: 5000,
1389 max_connections: Some(10),
1390 retry: None,
1391 }),
1392 messaging: None,
1393 orchestration: Some(A2AOrchestrationConfig {
1394 mode: "dynamic".to_string(), coordinator_address: Some("127.0.0.1".to_string()),
1396 heartbeat_interval_seconds: 5,
1397 agent_timeout_seconds: 30,
1398 consensus_enabled: true,
1399 consensus_algorithm: Some("paxos".to_string()), }),
1401 });
1402
1403 config
1405 .a2a
1406 .as_mut()
1407 .unwrap()
1408 .transport
1409 .as_mut()
1410 .unwrap()
1411 .port = Some(0);
1412
1413 let errs = config.check().unwrap_err();
1414 let error_locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1415 assert!(error_locs.contains(&"a2a.transport.transport_type".to_string()));
1416 assert!(error_locs.contains(&"a2a.transport.port".to_string()));
1417 assert!(error_locs.contains(&"a2a.orchestration.mode".to_string()));
1418 assert!(error_locs.contains(&"a2a.orchestration.consensus_algorithm".to_string()));
1419 }
1420
1421 #[test]
1422 fn test_ggen_config_path_validation_gaps() {
1423 let mut config = GgenConfig::default();
1424 config.project.name = "my-project".to_string();
1425 config.project.version = "1.0.0".to_string();
1426
1427 let malicious_path = "path/../../to/malicious/file".to_string();
1429 let backslash_path = "path\\..\\to\\malicious\\file".to_string();
1430 let null_byte_path = "path/with\0null/byte".to_string();
1431
1432 config.templates = Some(TemplatesConfig {
1434 directory: Some(malicious_path.clone()),
1435 output_directory: Some(backslash_path.clone()),
1436 backup_enabled: false,
1437 idempotent: false,
1438 });
1439
1440 let errs = config.check().unwrap_err();
1441 let locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1442 assert!(locs.contains(&"templates.directory".to_string()));
1443 assert!(locs.contains(&"templates.output_directory".to_string()));
1444
1445 config.templates = None;
1447 config.logging = Some(LoggingConfig {
1448 level: "info".to_string(),
1449 format: "json".to_string(),
1450 file: Some(null_byte_path.clone()),
1451 rotation: None,
1452 });
1453
1454 let errs = config.check().unwrap_err();
1455 assert!(errs
1456 .errors()
1457 .iter()
1458 .any(|e| e.loc.to_string() == "logging.file"));
1459
1460 config.logging = None;
1462 config.mcp = Some(McpConfig {
1463 name: Some("test-mcp".to_string()),
1464 version: Some("1.0.0".to_string()),
1465 enabled: true,
1466 tool_timeout_ms: 1000,
1467 max_concurrent_requests: 10,
1468 transport: Some(McpTransportConfig {
1469 transport_type: "stdio".to_string(),
1470 port: None,
1471 host: "localhost".to_string(),
1472 tls: Some(McpTlsConfig {
1473 enabled: false,
1474 cert_path: Some(malicious_path.clone()),
1475 key_path: Some(backslash_path.clone()),
1476 ca_path: Some(null_byte_path.clone()),
1477 }),
1478 request_timeout_seconds: 30,
1479 }),
1480 tools: Some(McpToolsConfig {
1481 discovery_path: Some(malicious_path.clone()),
1482 require_registration: false,
1483 validate_signatures: false,
1484 allowed_prefixes: None,
1485 }),
1486 zai: None,
1487 discovery: None,
1488 });
1489
1490 let errs = config.check().unwrap_err();
1491 let locs: Vec<String> = errs.errors().iter().map(|e| e.loc.to_string()).collect();
1492 assert!(locs.contains(&"mcp.transport.tls.cert_path".to_string()));
1493 assert!(locs.contains(&"mcp.transport.tls.key_path".to_string()));
1494 assert!(locs.contains(&"mcp.transport.tls.ca_path".to_string()));
1495 assert!(locs.contains(&"mcp.tools.discovery_path".to_string()));
1496
1497 config.mcp = None;
1499 config.a2a = Some(A2AConfig {
1500 agent_id: Some("agent-1".to_string()),
1501 agent_name: Some("test-agent".to_string()),
1502 agent_type: Some("worker".to_string()),
1503 capabilities: None,
1504 enabled: true,
1505 transport: None,
1506 messaging: Some(A2AMessagingConfig {
1507 queue_size: 100,
1508 message_ttl_seconds: 60,
1509 persistence_enabled: true,
1510 persistence_path: Some(malicious_path.clone()),
1511 signing_enabled: false,
1512 signature_algorithm: None,
1513 }),
1514 orchestration: None,
1515 });
1516
1517 let errs = config.check().unwrap_err();
1518 assert!(errs
1519 .errors()
1520 .iter()
1521 .any(|e| e.loc.to_string() == "a2a.messaging.persistence_path"));
1522 }
1523}