blob: c5f803fe40fdb75af8f27aa05cc57c302a827f65 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file contains the respresentation for the configuration of a cobalt
// project (See projectConfig) and a way to parse that configuration information
// from a yaml string.
package config_parser
import (
"config"
"fmt"
"github.com/golang/glog"
"yamlpb"
)
// Represents the configuration of a single project.
type projectConfig struct {
customerName string
customerId uint32
projectName string
projectId uint32
contact string
projectConfig config.CobaltConfig
}
// Parse the configuration for one project from the yaml string provided into
// the config field in projectConfig.
func parseProjectConfig(y string, c *projectConfig) (err error) {
if err := yamlpb.UnmarshalString(y, &c.projectConfig); err != nil {
return fmt.Errorf("Error while parsing yaml: %v", err)
}
// Maps metric ids to the metric's index in c.projectConfig.MetricConfigs.
metrics := map[uint32]uint32{}
// Set of encoding ids. Used to detect duplicates.
encodingIds := map[uint32]bool{}
// Set of report ids. Used to detect duplicates.
reportIds := map[uint32]bool{}
for i, e := range c.projectConfig.EncodingConfigs {
if encodingIds[e.Id] {
return fmt.Errorf("Encoding id '%v' is repeated in encoding config entry number %v. Encoding ids must be unique.", e.Id, i)
}
encodingIds[e.Id] = true
e.CustomerId = c.customerId
e.ProjectId = c.projectId
}
for i, e := range c.projectConfig.MetricConfigs {
if _, ok := metrics[e.Id]; ok {
return fmt.Errorf("Metric id '%v' is repeated in metric config entry number %v. Metric ids must be unique.", e.Id, i)
}
metrics[e.Id] = uint32(i)
if err := validateMetric(*e); err != nil {
return fmt.Errorf("Error validating metric %v (%v): %v", e.Name, e.Id, err)
}
e.CustomerId = c.customerId
e.ProjectId = c.projectId
}
for i, e := range c.projectConfig.ReportConfigs {
if reportIds[e.Id] {
return fmt.Errorf("Report id '%v' is repeated in report config entry number %v. Report ids must be unique.", e.Id, i)
}
reportIds[e.Id] = true
e.CustomerId = c.customerId
e.ProjectId = c.projectId
if _, ok := metrics[e.MetricId]; !ok {
return fmt.Errorf("Error validating report %v (%v): There is no metric id %v.", e.Name, e.Id, e.MetricId)
}
m := c.projectConfig.MetricConfigs[metrics[e.MetricId]]
if err := validateReportVariables(*e, *m); err != nil {
return fmt.Errorf("Error validating report %v (%v): %v", e.Name, e.Id, err)
}
}
return nil
}
func validateMetric(m config.Metric) (err error) {
for k, v := range m.Parts {
if v == nil {
return fmt.Errorf("Metric part '%v' is null. This is not allowed.", k)
}
}
return nil
}
// Checks that the report variables are compatible with the specific metric.
func validateReportVariables(c config.ReportConfig, m config.Metric) (err error) {
if len(c.Variable) == 0 {
glog.Warningf("Report '%v' (Customer %v, Project %v Id %v) does not have any report variables.", c.Name, c.CustomerId, c.ProjectId, c.Id)
return nil
}
for i, v := range c.Variable {
if v == nil {
return fmt.Errorf("Report Variable in position %v is null. This is not allowed.", i)
}
// Check that the metric part being referenced can be found.
p, ok := m.Parts[v.MetricPart]
if !ok {
return fmt.Errorf("Metric part '%v' is not present in metric '%v'.", v.MetricPart, m.Name)
}
// Checks that if index labels are found, the metric part referred to is an index.
if v.IndexLabels != nil && len(v.IndexLabels.Labels) > 0 && p.DataType != config.MetricPart_INDEX {
return fmt.Errorf("Report variable %v has index labels specified "+
"which implies referring to an index metric part. But metric part '%v'"+
"of metric '%v' (%v) is of type %v.",
i, v.MetricPart, m.Name, m.Id, config.MetricPart_DataType_name[int32(p.DataType)])
}
// Checks that if RAPPOR candidates are found, the metric part referred to is a string.
if v.RapporCandidates != nil && len(v.RapporCandidates.Candidates) > 0 && p.DataType != config.MetricPart_STRING {
return fmt.Errorf("Report variable %v has RAPPOR candidates specified "+
"which implies referring to a string metric part. But metric part '%v'"+
"of metric '%v' (%v) is of type %v.",
i, v.MetricPart, m.Name, m.Id, config.MetricPart_DataType_name[int32(p.DataType)])
}
}
return nil
}