11package coordinator
22
33import (
4+ "fmt"
5+ "net/url"
46 "os"
7+ "strconv"
8+ "strings"
59
610 "github.com/ethpandaops/assertoor/pkg/coordinator/clients"
711 "github.com/ethpandaops/assertoor/pkg/coordinator/db"
812 "github.com/ethpandaops/assertoor/pkg/coordinator/helper"
913 "github.com/ethpandaops/assertoor/pkg/coordinator/names"
14+ "github.com/ethpandaops/assertoor/pkg/coordinator/test"
1015 "github.com/ethpandaops/assertoor/pkg/coordinator/types"
1116 web_types "github.com/ethpandaops/assertoor/pkg/coordinator/web/types"
1217 "gopkg.in/yaml.v3"
@@ -26,7 +31,7 @@ type Config struct {
2631 ValidatorNames * names.Config `yaml:"validatorNames" json:"validatorNames"`
2732
2833 // Global variables
29- GlobalVars map [string ]interface {} `yaml:"globalVars" json:"globalVars"`
34+ GlobalVars map [string ]any `yaml:"globalVars" json:"globalVars"`
3035
3136 // Coordinator config
3237 Coordinator * CoordinatorConfig `yaml:"coordinator" json:"coordinator"`
@@ -57,7 +62,7 @@ func DefaultConfig() *Config {
5762 ConsensusURL : "http://localhost:5052" ,
5863 },
5964 },
60- GlobalVars : make (map [string ]interface {} ),
65+ GlobalVars : make (map [string ]any ),
6166 Coordinator : & CoordinatorConfig {},
6267 Tests : []* types.TestConfig {},
6368 ExternalTests : []* types.ExternalTestConfig {},
@@ -82,3 +87,113 @@ func NewConfig(path string) (*Config, error) {
8287
8388 return config , nil
8489}
90+
91+ func (c * Config ) Validate () error {
92+ var errs []error
93+
94+ // Validate database config
95+ if c .Database != nil {
96+ if c .Database .Engine != "" && c .Database .Engine != "sqlite" && c .Database .Engine != "postgres" {
97+ errs = append (errs , fmt .Errorf ("invalid database engine: %s" , c .Database .Engine ))
98+ }
99+ }
100+
101+ // Validate endpoints
102+ for i , endpoint := range c .Endpoints {
103+ if endpoint .Name == "" {
104+ errs = append (errs , fmt .Errorf ("endpoint[%d]: name cannot be empty" , i ))
105+ }
106+
107+ if endpoint .ConsensusURL == "" && endpoint .ExecutionURL == "" {
108+ errs = append (errs , fmt .Errorf ("endpoint[%d] '%s': must have at least one URL" , i , endpoint .Name ))
109+ }
110+ // Validate URLs are parseable
111+ if endpoint .ConsensusURL != "" {
112+ if _ , err := url .Parse (endpoint .ConsensusURL ); err != nil {
113+ errs = append (errs , fmt .Errorf ("endpoint[%d] '%s': invalid consensus URL: %v" , i , endpoint .Name , err ))
114+ }
115+ }
116+
117+ if endpoint .ExecutionURL != "" {
118+ if _ , err := url .Parse (endpoint .ExecutionURL ); err != nil {
119+ errs = append (errs , fmt .Errorf ("endpoint[%d] '%s': invalid execution URL: %v" , i , endpoint .Name , err ))
120+ }
121+ }
122+ }
123+
124+ // Validate web config
125+ if c .Web != nil {
126+ if c .Web .Frontend != nil && c .Web .Frontend .Enabled {
127+ // Validate port is in valid range
128+ if c .Web .Server .Port != "" {
129+ if port , err := strconv .Atoi (c .Web .Server .Port ); err != nil {
130+ errs = append (errs , fmt .Errorf ("invalid web server port: %s (must be a number)" , c .Web .Server .Port ))
131+ } else if port < 1 || port > 65535 {
132+ errs = append (errs , fmt .Errorf ("invalid web server port: %d (must be between 1 and 65535)" , port ))
133+ }
134+ }
135+ }
136+ }
137+
138+ // Validate coordinator config
139+ if c .Coordinator != nil {
140+ if err := c .Coordinator .Validate (); err != nil {
141+ errs = append (errs , fmt .Errorf ("coordinator config: %v" , err ))
142+ }
143+ }
144+
145+ // Validate tests
146+ for i , testCfg := range c .Tests {
147+ if testCfg .ID == "" {
148+ errs = append (errs , fmt .Errorf ("test[%d]: ID cannot be empty" , i ))
149+ }
150+
151+ if testCfg .Name == "" {
152+ errs = append (errs , fmt .Errorf ("test[%d] '%s': name cannot be empty" , i , testCfg .ID ))
153+ }
154+
155+ // Validate task configurations
156+ if err := test .ValidateTestConfig (testCfg ); err != nil {
157+ errs = append (errs , fmt .Errorf ("test[%d] '%s': %v" , i , testCfg .ID , err ))
158+ }
159+ }
160+
161+ // Validate external tests
162+ for i , extTest := range c .ExternalTests {
163+ if extTest .File == "" {
164+ errs = append (errs , fmt .Errorf ("external test[%d]: file cannot be empty" , i ))
165+ }
166+
167+ if extTest .ID == "" {
168+ errs = append (errs , fmt .Errorf ("external test[%d]: ID cannot be empty" , i ))
169+ }
170+ }
171+
172+ if len (errs ) > 0 {
173+ return fmt .Errorf ("configuration validation failed:\n %s" , formatErrors (errs ))
174+ }
175+
176+ return nil
177+ }
178+
179+ func formatErrors (errs []error ) string {
180+ var buf strings.Builder
181+ for _ , err := range errs {
182+ buf .WriteString (" - " )
183+ buf .WriteString (err .Error ())
184+ buf .WriteString ("\n " )
185+ }
186+
187+ return strings .TrimSuffix (buf .String (), "\n " )
188+ }
189+
190+ func (c * CoordinatorConfig ) Validate () error {
191+ if c .TestRetentionTime .Duration != 0 {
192+ // Duration is valid if it parsed successfully
193+ if c .TestRetentionTime .Duration < 0 {
194+ return fmt .Errorf ("testRetentionTime cannot be negative" )
195+ }
196+ }
197+
198+ return nil
199+ }
0 commit comments