package main import ( "encoding/json" "flag" "fmt" "log" "os" "path/filepath" "strings" "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" ) func main() { var ( schemaPath string failFast bool ) flag.StringVar(&schemaPath, "schema", "configs/schema.yaml", "Path to JSON schema in YAML format") flag.BoolVar(&failFast, "fail-fast", false, "Stop on first error") flag.Parse() if flag.NArg() == 0 { log.Fatalf("usage: configlint [--schema path] [--fail-fast] ") } schemaLoader, err := loadSchema(schemaPath) if err != nil { log.Fatalf("failed to load schema: %v", err) } var hadError bool for _, configPath := range flag.Args() { if err := validateConfig(schemaLoader, configPath); err != nil { hadError = true fmt.Fprintf(os.Stderr, "configlint: %s: %v\n", configPath, err) if failFast { os.Exit(1) } } } if hadError { os.Exit(1) } fmt.Println("All configuration files are valid.") } func loadSchema(schemaPath string) (gojsonschema.JSONLoader, error) { data, err := os.ReadFile(schemaPath) if err != nil { return nil, err } var schemaYAML interface{} if err := yaml.Unmarshal(data, &schemaYAML); err != nil { return nil, err } schemaJSON, err := json.Marshal(schemaYAML) if err != nil { return nil, err } tmpFile, err := os.CreateTemp("", "fetchml-schema-*.json") if err != nil { return nil, err } defer tmpFile.Close() if _, err := tmpFile.Write(schemaJSON); err != nil { return nil, err } return gojsonschema.NewReferenceLoader("file://" + filepath.ToSlash(tmpFile.Name())), nil } func validateConfig(schemaLoader gojsonschema.JSONLoader, configPath string) error { data, err := os.ReadFile(configPath) if err != nil { return err } var configYAML interface{} if err := yaml.Unmarshal(data, &configYAML); err != nil { return fmt.Errorf("failed to parse YAML: %w", err) } configJSON, err := json.Marshal(configYAML) if err != nil { return err } result, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewBytesLoader(configJSON)) if err != nil { return err } if result.Valid() { fmt.Printf("%s: valid\n", configPath) return nil } var builder strings.Builder for _, issue := range result.Errors() { builder.WriteString("- ") builder.WriteString(issue.String()) builder.WriteByte('\n') } return fmt.Errorf("validation failed:\n%s", builder.String()) }