blob: c1b90acb9633c3f9de572bf69b214e48b0940d3d [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package config_validator
import (
"config"
"config_parser"
"errors"
"fmt"
"sort"
"strings"
)
// validationErrors is used to package up nested validation errors in a structured way
//
// The method validationErrors::err() converts this into an error type (or nil if no entries have been added to errors)
type validationErrors struct {
ty string
errors map[string][]error
}
func (ve validationErrors) addError(key string, value error) {
ve.errors[key] = append(ve.errors[key], value)
}
func (ve *validationErrors) Error() string {
if ve == nil {
return ""
}
projects := []string{}
for project := range ve.errors {
projects = append(projects, project)
}
sort.Strings(projects)
result := []string{}
for _, project := range projects {
errs := ve.errors[project]
if len(errs) == 1 {
var innerErrs *validationErrors
err := errs[0]
if errors.As(err, &innerErrs) {
// Indent the nested validationErrors
errStr := strings.Join(strings.Split(err.Error(), "\n"), "\n ")
result = append(result, fmt.Sprintf("%s `%s`:\n %v", ve.ty, project, errStr))
} else {
result = append(result, fmt.Sprintf("%s `%s`: %v", ve.ty, project, err.Error()))
}
} else {
errStrs := []string{}
for _, err := range errs {
errStrs = append(errStrs, err.Error())
}
errStr := strings.Join(errStrs, "\n")
errStr = strings.Join(strings.Split(errStr, "\n"), "\n ")
result = append(result, fmt.Sprintf("%s `%s`:\n %v", ve.ty, project, errStr))
}
}
return strings.Join(result, "\n")
}
func newValidationErrors(ty string) validationErrors {
return validationErrors{
ty: ty,
errors: map[string][]error{},
}
}
func (ve validationErrors) err() error {
if len(ve.errors) > 0 {
return &ve
}
return nil
}
func ValidateProjectConfigDatas(configs []config_parser.ProjectConfigData) error {
projectErrors := newValidationErrors("customer/project")
for _, c := range configs {
if err := validateProjectConfigData(&c); err != nil {
projectErrors.addError(fmt.Sprintf("%s/%s", c.CustomerName, c.ProjectName), err)
}
}
return projectErrors.err()
}
func validateProjectConfigData(c *config_parser.ProjectConfigData) (err error) {
if c.CobaltVersion == config_parser.CobaltVersion0 {
return fmt.Errorf("Cobalt version 0.1 configs are no longer supported")
}
if err = validateConfiguredMetricDefinitions(c.ProjectConfigFile.MetricDefinitions); err != nil {
return err
}
if err = validateReplacementMetricSaturation(c.ProjectConfigFile.MetricDefinitions); err != nil {
return err
}
return nil
}
func validateReplacementMetricSaturation(metrics []*config.MetricDefinition) (err error) {
hasAnyReplacementMetrics := false
metricsMissingReplacement := []string{}
unneededReplacements := []string{}
for _, m := range metrics {
if !config_parser.Cobalt11MetricTypesSet[m.MetricType] {
if m.ReplacementMetricId != 0 || m.NoReplacementMetric != "" {
hasAnyReplacementMetrics = true
} else {
metricsMissingReplacement = append(metricsMissingReplacement, m.MetricName)
}
} else {
if m.ReplacementMetricId != 0 || m.NoReplacementMetric != "" {
unneededReplacements = append(unneededReplacements, m.MetricName)
}
}
}
if hasAnyReplacementMetrics && len(metricsMissingReplacement) > 0 {
errs := newValidationErrors("metric")
for _, metric := range metricsMissingReplacement {
errs.addError(metric, fmt.Errorf("does not include replacement_metric_id or no_replacement_metric"))
}
return errs.err()
}
if len(unneededReplacements) > 0 {
errs := newValidationErrors("metric")
for _, metric := range unneededReplacements {
errs.addError(metric, fmt.Errorf("has replacement_metric_id or no_replacement_metric specified when it is not needed"))
}
return errs.err()
}
return nil
}