blob: ad2bf6982951f3dd1e101bb5a1a47471f80d9a73 [file] [log] [blame]
// Copyright 2020 The LUCI 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.
package sink
import (
"mime"
"time"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/resultdb/pbutil"
sinkpb "go.chromium.org/luci/resultdb/sink/proto/v1"
)
// validateTestResult returns a non-nil error if msg is invalid.
func validateTestResult(now time.Time, msg *sinkpb.TestResult) (err error) {
ec := checker{&err}
switch {
case msg == nil:
return unspecified()
case ec.isErr(pbutil.ValidateTestID(msg.TestId), "test_id"):
case ec.isErr(pbutil.ValidateResultID(msg.ResultId), "result_id"):
// skip `Expected`
case ec.isErr(pbutil.ValidateTestResultStatus(msg.Status), "status"):
case ec.isErr(pbutil.ValidateSummaryHTML(msg.SummaryHtml), "summary_html"):
case ec.isErr(pbutil.ValidateStartTimeWithDuration(now, msg.StartTime, msg.Duration), ""):
case ec.isErr(pbutil.ValidateStringPairs(msg.Tags), "tags"):
case ec.isErr(validateArtifacts(msg.Artifacts), "artifacts"):
case msg.TestMetadata != nil && ec.isErr(pbutil.ValidateTestMetadata(msg.TestMetadata), "test_metadata"):
case msg.FailureReason != nil && ec.isErr(pbutil.ValidateFailureReason(msg.FailureReason), "failure_reason"):
}
return err
}
// validateArtifact returns a non-nil error if art is invalid.
func validateArtifact(art *sinkpb.Artifact) error {
if art.GetFilePath() == "" && art.GetContents() == nil {
return errors.Reason("body: either file_path or contents must be provided").Err()
}
if art.GetContentType() != "" {
_, _, err := mime.ParseMediaType(art.ContentType)
if err != nil {
return err
}
}
return nil
}
// validateArtifacts returns a non-nil error if any element of arts is invalid.
func validateArtifacts(arts map[string]*sinkpb.Artifact) error {
for id, art := range arts {
if art == nil {
return errors.Reason("%s: %s", id, unspecified()).Err()
}
if err := pbutil.ValidateArtifactID(id); err != nil {
return errors.Annotate(err, "%s", id).Err()
}
if err := validateArtifact(art); err != nil {
return errors.Annotate(err, "%s", id).Err()
}
}
return nil
}
type checker struct {
lastCheckedErr *error
}
// isErr returns true if err is nil. False, otherwise.
//
// It also stores err into lastCheckedErr. If err was not nil, it wraps err with
// errors.Annotate before storing it in lastErr.
func (c *checker) isErr(err error, format string, args ...interface{}) bool {
if err == nil {
return false
}
*c.lastCheckedErr = errors.Annotate(err, format, args...).Err()
return true
}
func unspecified() error {
return errors.Reason("unspecified").Err()
}