go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/resultdb/sink/validation.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sink 16 17 import ( 18 "mime" 19 "time" 20 21 "go.chromium.org/luci/common/errors" 22 23 "go.chromium.org/luci/resultdb/pbutil" 24 sinkpb "go.chromium.org/luci/resultdb/sink/proto/v1" 25 ) 26 27 // validateTestResult returns a non-nil error if msg is invalid. 28 func validateTestResult(now time.Time, msg *sinkpb.TestResult) (err error) { 29 ec := checker{&err} 30 switch { 31 case msg == nil: 32 return unspecified() 33 case ec.isErr(pbutil.ValidateTestID(msg.TestId), "test_id"): 34 case ec.isErr(pbutil.ValidateResultID(msg.ResultId), "result_id"): 35 // skip `Expected` 36 case ec.isErr(pbutil.ValidateTestResultStatus(msg.Status), "status"): 37 case ec.isErr(pbutil.ValidateSummaryHTML(msg.SummaryHtml), "summary_html"): 38 case ec.isErr(pbutil.ValidateStartTimeWithDuration(now, msg.StartTime, msg.Duration), ""): 39 case ec.isErr(pbutil.ValidateStringPairs(msg.Tags), "tags"): 40 case ec.isErr(validateArtifacts(msg.Artifacts), "artifacts"): 41 case msg.TestMetadata != nil && ec.isErr(pbutil.ValidateTestMetadata(msg.TestMetadata), "test_metadata"): 42 case msg.FailureReason != nil && ec.isErr(pbutil.ValidateFailureReason(msg.FailureReason), "failure_reason"): 43 case msg.Properties != nil && ec.isErr(pbutil.ValidateProperties(msg.Properties), "properties"): 44 } 45 return err 46 } 47 48 // validateArtifact returns a non-nil error if art is invalid. 49 func validateArtifact(art *sinkpb.Artifact) error { 50 if art.GetFilePath() == "" && art.GetContents() == nil && art.GetGcsUri() == "" { 51 return errors.Reason("body: one of file_path or contents or gcs_uri must be provided").Err() 52 } 53 if art.GetContentType() != "" { 54 _, _, err := mime.ParseMediaType(art.ContentType) 55 if err != nil { 56 return err 57 } 58 } 59 return nil 60 } 61 62 // validateArtifacts returns a non-nil error if any element of arts is invalid. 63 func validateArtifacts(arts map[string]*sinkpb.Artifact) error { 64 for id, art := range arts { 65 if art == nil { 66 return errors.Reason("%s: %s", id, unspecified()).Err() 67 } 68 if err := pbutil.ValidateArtifactID(id); err != nil { 69 return errors.Annotate(err, "%s", id).Err() 70 } 71 if err := validateArtifact(art); err != nil { 72 return errors.Annotate(err, "%s", id).Err() 73 } 74 } 75 return nil 76 } 77 78 type checker struct { 79 lastCheckedErr *error 80 } 81 82 // isErr returns true if err is nil. False, otherwise. 83 // 84 // It also stores err into lastCheckedErr. If err was not nil, it wraps err with 85 // errors.Annotate before storing it in lastErr. 86 func (c *checker) isErr(err error, format string, args ...any) bool { 87 if err == nil { 88 return false 89 } 90 *c.lastCheckedErr = errors.Annotate(err, format, args...).Err() 91 return true 92 } 93 94 func unspecified() error { 95 return errors.Reason("unspecified").Err() 96 }