| // 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 |
| } |