go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/analysis/pbutil/predicate.go (about) 1 // Copyright 2022 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 pbutil contains methods for manipulating LUCI Analysis protos. 16 package pbutil 17 18 import ( 19 "context" 20 "fmt" 21 "regexp" 22 "regexp/syntax" 23 "strings" 24 25 "go.chromium.org/luci/common/errors" 26 27 pb "go.chromium.org/luci/analysis/proto/v1" 28 ) 29 30 var ( 31 // Unspecified is the error to be used when something is unspecified when it's 32 // supposed to. 33 Unspecified = errors.Reason("unspecified").Err() 34 35 // DoesNotMatch is the error to be used when a string does not match a regex. 36 DoesNotMatch = errors.Reason("does not match").Err() 37 ) 38 39 // validateRegexp returns a non-nil error if re is an invalid regular 40 // expression. 41 func validateRegexp(re string) error { 42 // Note: regexp.Compile uses syntax.Perl. 43 if _, err := syntax.Parse(re, syntax.Perl); err != nil { 44 return err 45 } 46 47 // Do not allow ^ and $ in the regexp, because we need to be able to prepend 48 // a pattern to the user-supplied pattern. 49 if strings.HasPrefix(re, "^") { 50 return errors.Reason("must not start with ^; it is prepended automatically").Err() 51 } 52 if strings.HasSuffix(re, "$") { 53 return errors.Reason("must not end with $; it is appended automatically").Err() 54 } 55 56 return nil 57 } 58 59 // ValidateWithRe validates a value matches the given re. 60 func ValidateWithRe(re *regexp.Regexp, value string) error { 61 if value == "" { 62 return Unspecified 63 } 64 if !re.MatchString(value) { 65 return DoesNotMatch 66 } 67 return nil 68 } 69 70 // ValidateStringPair returns an error if p is invalid. 71 func ValidateStringPair(p *pb.StringPair) error { 72 if err := ValidateWithRe(stringPairKeyRe, p.Key); err != nil { 73 return errors.Annotate(err, "key").Err() 74 } 75 if len(p.Key) > maxStringPairKeyLength { 76 return errors.Reason("key length must be less or equal to %d", maxStringPairKeyLength).Err() 77 } 78 if len(p.Value) > maxStringPairValueLength { 79 return errors.Reason("value length must be less or equal to %d", maxStringPairValueLength).Err() 80 } 81 return nil 82 } 83 84 // ValidateVariant returns an error if vr is invalid. 85 func ValidateVariant(vr *pb.Variant) error { 86 for k, v := range vr.GetDef() { 87 p := pb.StringPair{Key: k, Value: v} 88 if err := ValidateStringPair(&p); err != nil { 89 return errors.Annotate(err, "%q:%q", k, v).Err() 90 } 91 } 92 return nil 93 } 94 95 // ValidateVariantPredicate returns a non-nil error if p is determined to be 96 // invalid. 97 func ValidateVariantPredicate(p *pb.VariantPredicate) error { 98 switch pr := p.Predicate.(type) { 99 case *pb.VariantPredicate_Equals: 100 return errors.Annotate(ValidateVariant(pr.Equals), "equals").Err() 101 case *pb.VariantPredicate_Contains: 102 return errors.Annotate(ValidateVariant(pr.Contains), "contains").Err() 103 case *pb.VariantPredicate_HashEquals: 104 return errors.Annotate(ValidateWithRe(variantHashRe, pr.HashEquals), "hash_equals").Err() 105 case nil: 106 return Unspecified 107 default: 108 panic("impossible") 109 } 110 } 111 112 // ValidateTestVerdictPredicate returns a non-nil error if p is determined to be 113 // invalid. 114 func ValidateTestVerdictPredicate(predicate *pb.TestVerdictPredicate) error { 115 if predicate == nil { 116 return Unspecified 117 } 118 119 if predicate.GetVariantPredicate() != nil { 120 if err := ValidateVariantPredicate(predicate.GetVariantPredicate()); err != nil { 121 return err 122 } 123 } 124 return ValidateEnum(int32(predicate.GetSubmittedFilter()), pb.SubmittedFilter_name) 125 } 126 127 // ValidateEnum returns a non-nil error if the value is not among valid values. 128 func ValidateEnum(value int32, validValues map[int32]string) error { 129 if _, ok := validValues[value]; !ok { 130 return errors.Reason("invalid value %d", value).Err() 131 } 132 return nil 133 } 134 135 // ValidateTimeRange returns a non-nil error if tr is determined to be invalid. 136 // To be valid, a TimeRange must have both Earliest and Latest fields specified, 137 // and the Earliest time must be chronologically before the Latest time. 138 func ValidateTimeRange(ctx context.Context, tr *pb.TimeRange) error { 139 if tr == nil { 140 return Unspecified 141 } 142 143 earliest, err := AsTime(tr.Earliest) 144 if err != nil { 145 return errors.Annotate(err, "earliest").Err() 146 } 147 148 latest, err := AsTime(tr.Latest) 149 if err != nil { 150 return errors.Annotate(err, "latest").Err() 151 } 152 153 if !earliest.Before(latest) { 154 return fmt.Errorf("earliest must be before latest") 155 } 156 157 return nil 158 }