package commandkit import ( "bytes" "fmt" "strings" "go.yaml.in/yaml/v4" ) func configOperationDeclared[C any](scopes []ConfigScope[C], operation ConfigOperation) bool { for _, scope := range scopes { if containsOperation(scope.Operations, operation) { return true } } return false } func newConfigValidateCommand[C any](suite ConfigSuite[C]) (Command, error) { spec := CommandSpec[configInput[C]]{ Name: string(ConfigValidate), Summary: "Validate selected configuration scopes.", Flags: configInputFlags(suite, "text"), CheckInput: func(input configInput[C]) error { _, err := preflightConfig(suite, input, ConfigValidate) return err }, Run: func(invocation Invocation, input configInput[C]) Outcome { return runConfigValidation(invocation, suite, input) }, } return NewCommand(spec) } func runConfigValidation[C any](invocation Invocation, suite ConfigSuite[C], input configInput[C]) Outcome { if err := invocation.Context.Err(); err != nil { return Failure(err) } scopeNames, err := selectConfigScopes(suite, input, ConfigValidate) if err != nil { return Failure(err) } contexts := contextsForScopes(suite, input, scopeNames) diagnostics := make([]Diagnostic, 0) writtenRecord := false for _, name := range scopeNames { scope, ok := findConfigScope(suite.Scopes, name) if !ok || scope.Acquire == nil { return validationFailure(fmt.Errorf("configuration scope %q has no acquisition callback", name), diagnostics, writtenRecord) } if err := invocation.Context.Err(); err != nil { return validationFailure(err, diagnostics, writtenRecord) } acquired, acquireErr := scope.Acquire(invocation.Context, ConfigRequest{Operation: ConfigValidate, Selection: input.Selection}, contexts[name]) diagnostics = append(diagnostics, acquired.Diagnostics...) if acquireErr != nil { return validationFailure(acquireErr, diagnostics, writtenRecord) } if err := invocation.Context.Err(); err != nil { return validationFailure(err, diagnostics, writtenRecord) } record, err := validationSuccessRecord(name, input.Format) if err != nil { return validationFailure(err, diagnostics, writtenRecord) } if input.Format == "yaml" && !writtenRecord { if err := writeReport(invocation.Streams.Out, []byte("scopes:\n")); err != nil { outcome := validationFailure(err, diagnostics, true) outcome.DeliveryError = err return outcome } } if err := writeReport(invocation.Streams.Out, record); err != nil { outcome := validationFailure(err, diagnostics, true) outcome.DeliveryError = err return outcome } writtenRecord = true } outcome := Success() outcome.Diagnostics = diagnostics return outcome } func validationFailure(cause error, diagnostics []Diagnostic, reportStarted bool) Outcome { outcome := Failure(cause) outcome.Diagnostics = diagnostics outcome.ReportIncomplete = reportStarted return outcome } func validationSuccessRecord(scope, format string) ([]byte, error) { if format == "text" { return []byte(scope + ": valid\n"), nil } encoded, err := yaml.Marshal(struct { Name string `yaml:"name"` Valid bool `yaml:"valid"` }{Name: scope, Valid: true}) if err != nil { return nil, err } lines := strings.Split(strings.TrimSuffix(string(encoded), "\n"), "\n") var record bytes.Buffer for index, line := range lines { if index == 0 { record.WriteString(" - ") } else { record.WriteString(" ") } record.WriteString(line) record.WriteByte('\n') } return record.Bytes(), nil }