From d56a84b7c93f784f1bd6510dcdc2e89be978f249 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 5 Sep 2024 17:46:25 -0400 Subject: [PATCH 01/30] validate.Constraint -> shared.Constraint --- go.mod | 4 ++++ go.sum | 6 ++---- .../bufcheck/bufcheckserver/internal/buflintvalidate/cel.go | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index fc9cdb0d7d..98568d257b 100644 --- a/go.mod +++ b/go.mod @@ -120,3 +120,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/grpc v1.66.0 // indirect ) + +replace github.com/bufbuild/protovalidate-go => github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd + +replace buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go => ../pv-gen diff --git a/go.sum b/go.sum index 6fe0d21a81..5e83c0173c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.34.2-20240904181154-a0be11449112.2 h1:X9qBPcvWGOJs/CeRVLoxxLJwC/eKyWDS/G4nj+3KGMY= buf.build/gen/go/bufbuild/bufplugin/protocolbuffers/go v1.34.2-20240904181154-a0be11449112.2/go.mod h1:B+9TKHRYqoAUW57pLjhkLOnBCu0DQYMV+f7imQ9nXwI= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 h1:SZRVx928rbYZ6hEKUIN+vtGDkl7uotABRWGY4OAg5gM= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw= buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240821192916-45ba72cdd479.1 h1:QaJ6UkpvlGo4dBXR41vLRfPiKungbg7brjmbBC/k6Ig= buf.build/gen/go/bufbuild/registry/connectrpc/go v1.16.2-20240821192916-45ba72cdd479.1/go.mod h1:oQsMFNU3YzxxjRS6O68UkcF/A+pXdXqQNcUfQEBTWcw= buf.build/gen/go/bufbuild/registry/protocolbuffers/go v1.34.2-20240821192916-45ba72cdd479.2 h1:C3CTZTucEUm7i0O2tAM8GSlg23GnQYcljX1b1Jcpsro= @@ -30,8 +28,6 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/ github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee h1:E6ET8YUcYJ1lAe6ctR3as7yqzW2BNItDFnaB5zQq/8M= github.com/bufbuild/protoplugin v0.0.0-20240323223605-e2735f6c31ee/go.mod h1:HjGFxsck9RObrTJp2hXQZfWhPgZqnR6sR1U5fCA/Kus= -github.com/bufbuild/protovalidate-go v0.6.5 h1:WucDKXIbK22WjkO8A8J6Yyxxy0jl91Oe9LSMduq3YEE= -github.com/bufbuild/protovalidate-go v0.6.5/go.mod h1:LHDiGCWSM3GagZEnyEZ1sPtFwi6Ja4tVTi/DCc+iDFI= github.com/bufbuild/protoyaml-go v0.1.12 h1:tIJrwvGxumVpNwLsw/AevT1QnkPDBuAObBSuBAdmAWY= github.com/bufbuild/protoyaml-go v0.1.12/go.mod h1:Xmz3wct+08Va+g9gjIuLTAmxW2w6sre5Wrgw7K3gn0I= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -157,6 +153,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd h1:/JZgJiQWzuahn1u3D15Udv88eR+80HZmxumBvMgbsew= +github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd/go.mod h1:LHDiGCWSM3GagZEnyEZ1sPtFwi6Ja4tVTi/DCc+iDFI= github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ= github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8= github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go index dd86995238..dd6beae1fd 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go @@ -19,6 +19,7 @@ import ( "strings" "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" "github.com/bufbuild/protovalidate-go/celext" "github.com/google/cel-go/cel" @@ -111,7 +112,7 @@ func checkCELForField( func checkCEL( celEnv *cel.Env, - celConstraints []*validate.Constraint, + celConstraints []*shared.Constraint, parentName string, parentNameCapitalized string, celName string, From 5779a5e9950da4edb24e70ceb5654caf0fa86b08 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 5 Sep 2024 17:46:56 -0400 Subject: [PATCH 02/30] add comment todos --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index 98568d257b..bb8f1796d3 100644 --- a/go.mod +++ b/go.mod @@ -121,6 +121,8 @@ require ( google.golang.org/grpc v1.66.0 // indirect ) +// TODO: remove replace github.com/bufbuild/protovalidate-go => github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd +// TODO: remove replace buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go => ../pv-gen From c3d13e502a5124b670f1fb89bd9a69be15d8dda0 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Mon, 9 Sep 2024 21:33:38 -0400 Subject: [PATCH 03/30] wip --- .../internal/buflintvalidate/field.go | 161 +++++++++++++++++- 1 file changed, 154 insertions(+), 7 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 69ca1d7d97..2ff3996c16 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -15,6 +15,7 @@ package buflintvalidate import ( + "errors" "fmt" "regexp" "strings" @@ -22,10 +23,13 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/slicesext" + "github.com/bufbuild/protovalidate-go" "github.com/bufbuild/protovalidate-go/resolver" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/dynamicpb" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/timestamppb" @@ -220,23 +224,103 @@ func checkConstraintsForField( } if numberRulesCheckFunc, ok := fieldNumberToCheckNumberRulesFunc[typeRulesFieldNumber]; ok { numberRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() - return numberRulesCheckFunc(adder, typeRulesFieldNumber, numberRulesMessage) + if err := numberRulesCheckFunc(adder, typeRulesFieldNumber, numberRulesMessage); err != nil { + return nil + } } + var exampleValues []protoreflect.Value switch typeRulesFieldNumber { + case floatRulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetFloat().GetExample(), protoreflect.ValueOfFloat32) + case doubleRulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetDouble().GetExample(), protoreflect.ValueOfFloat64) + case int32RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetInt32().GetExample(), protoreflect.ValueOfInt32) + case sInt32RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetSint32().GetExample(), protoreflect.ValueOfInt32) + case sFixed32RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetSfixed32().GetExample(), protoreflect.ValueOfInt32) + case fixed32RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetFixed32().GetExample(), protoreflect.ValueOfUint32) + case uInt32RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetUint32().GetExample(), protoreflect.ValueOfUint32) + case int64RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetInt64().GetExample(), protoreflect.ValueOfInt64) + case sFixed64RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetSfixed64().GetExample(), protoreflect.ValueOfInt64) + case sInt64RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetSint64().GetExample(), protoreflect.ValueOfInt64) + case fixed64RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetFixed64().GetExample(), protoreflect.ValueOfUint64) + case uInt64RulesFieldNumber: + exampleValues = slicesext.Map(fieldConstraints.GetUint64().GetExample(), protoreflect.ValueOfUint64) case boolRulesFieldNumber: - // Bool rules only have `const` and does not need validating. + // Bool rules only have one constraint, `const`, which does not need validating. + // However, we still validate that example values satify the constraint. + exampleValues = slicesext.Map(fieldConstraints.GetBool().GetExample(), protoreflect.ValueOfBool) case stringRulesFieldNumber: - return checkStringRules(adder, fieldConstraints.GetString_()) + stringRules := fieldConstraints.GetString_() + if err := checkStringRules(adder, stringRules); err != nil { + return err + } + exampleValues = slicesext.Map(stringRules.GetExample(), protoreflect.ValueOfString) case bytesRulesFieldNumber: - return checkBytesRules(adder, fieldConstraints.GetBytes()) + bytesRules := fieldConstraints.GetBytes() + if err := checkBytesRules(adder, bytesRules); err != nil { + return err + } + exampleValues = slicesext.Map( + bytesRules.GetExample(), + protoreflect.ValueOfBytes, + ) case enumRulesFieldNumber: - checkEnumRules(adder, fieldConstraints.GetEnum()) + enumRules := fieldConstraints.GetEnum() + checkEnumRules(adder, enumRules) + exampleValues = slicesext.Map( + enumRules.GetExample(), + protoreflect.ValueOfInt32, + ) case anyRulesFieldNumber: + // Any does not have example values. checkAnyRules(adder, fieldConstraints.GetAny()) case durationRulesFieldNumber: - return checkDurationRules(adder, fieldConstraints.GetDuration()) + durationRules := fieldConstraints.GetDuration() + if err := checkDurationRules(adder, durationRules); err != nil { + return err + } + exampleValues = slicesext.Map( + durationRules.GetExample(), + func(duration *durationpb.Duration) protoreflect.Value { + return protoreflect.ValueOfMessage(duration.ProtoReflect()) + }, + ) case timestampRulesFieldNumber: - return checkTimestampRules(adder, fieldConstraints.GetTimestamp()) + timestampRules := fieldConstraints.GetTimestamp() + if err := checkTimestampRules(adder, timestampRules); err != nil { + return err + } + exampleValues = slicesext.Map( + timestampRules.GetExample(), + func(timestamp *timestamppb.Timestamp) protoreflect.Value { + return protoreflect.ValueOfMessage(timestamp.ProtoReflect()) + }, + ) + } + exampleValues = nil + typeRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() + typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { + if string(fd.Name()) == "example" { + // This assumed all *Rules.Example are repeated, otherwise it panics. + list := value.List() + for i := 0; i < list.Len(); i++ { + exampleValues = append(exampleValues, list.Get(i)) + } + return false + } + return true + }) + if len(exampleValues) > 0 { + return checkExampleValues(adder, typeRulesMessage, fieldDescriptor, exampleValues) } return nil } @@ -700,6 +784,69 @@ func checkTimestampRules(adder *adder, timestampRules *validate.TimestampRules) return nil } +func checkExampleValues( + adder *adder, + typeRulesMessage protoreflect.Message, + fieldDescriptor protoreflect.FieldDescriptor, + exampleValues []protoreflect.Value, +) error { + v, err := protovalidate.New() + if err != nil { + return err + } + var hasConstraints bool + typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + if string(fd.Name()) != "example" { + hasConstraints = true + return false + } + return true + }) + // TODO: this is incorrect, also needs to check for cel etc. + if !hasConstraints { + adder.addForPathf(nil, "example value is specified by there are no constraints defined") + return nil + } + parentMessageDescriptor := fieldDescriptor.ContainingMessage() + for _, exampleValue := range exampleValues { + messageToValidate := dynamicpb.NewMessage(parentMessageDescriptor) + if fieldDescriptor.Cardinality() == protoreflect.Repeated { + list := messageToValidate.NewField(fieldDescriptor).List() + list.Append(exampleValue) + messageToValidate.Set(fieldDescriptor, protoreflect.ValueOfList(list)) + } else { + messageToValidate.Set(fieldDescriptor, exampleValue) + } + err := v.Validate(messageToValidate) + if err == nil { + continue + } + if errors.Is(err, &protovalidate.CompilationError{}) { + // Expression failing to compile meaning some custom shared rules are invalid, + // which is checked in this rule (PROTOVALIDATE), but not in this code block. + // TODO: verify + break + } + validationErr := &protovalidate.ValidationError{} + if errors.As(err, &validationErr) { + for _, violation := range validationErr.Violations { + if violation.FieldPath == string(fieldDescriptor.Name()) { + adder.addForPathf(nil, `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.ConstraintId, violation.Message) + } + } + continue + } + runtimeError := &protovalidate.RuntimeError{} + if errors.As(err, &runtimeError) { + adder.addForPathf(nil, "example fail at runtime: %s", runtimeError.Error()) + continue + } + return fmt.Errorf("unexpected error from protovalidate: %s", err.Error()) + } + + return nil +} + func checkConst(adder *adder, rule proto.Message, ruleFieldNumber int32) { var ( fieldCount int From ce41891e492c5184ed809aa835f6514f06bda03e Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Tue, 10 Sep 2024 11:41:45 -0400 Subject: [PATCH 04/30] add comments and prints (to be removed); still need to get map to work --- .../internal/buflintvalidate/field.go | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 2ff3996c16..9fd7d5ad25 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -306,21 +306,25 @@ func checkConstraintsForField( }, ) } - exampleValues = nil typeRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() - typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { - if string(fd.Name()) == "example" { - // This assumed all *Rules.Example are repeated, otherwise it panics. - list := value.List() - for i := 0; i < list.Len(); i++ { - exampleValues = append(exampleValues, list.Get(i)) - } - return false - } - return true - }) + // // This also works. Using this would allow dropping most of the code setting examples values. + // // However, a lot of the methods called here may panic, even though in practice they won't, + // // except for the rare case where a typed rule in protovalidate has an example field that's + // // not repeated. + // exampleValues = nil + // typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { + // if string(fd.Name()) == "example" { + // // This assumed all *Rules.Example are repeated, otherwise it panics. + // list := value.List() + // for i := 0; i < list.Len(); i++ { + // exampleValues = append(exampleValues, list.Get(i)) + // } + // return false + // } + // return true + // }) if len(exampleValues) > 0 { - return checkExampleValues(adder, typeRulesMessage, fieldDescriptor, exampleValues) + return checkExampleValues(adder, fieldConstraints, typeRulesMessage, fieldDescriptor, exampleValues) } return nil } @@ -786,30 +790,54 @@ func checkTimestampRules(adder *adder, timestampRules *validate.TimestampRules) func checkExampleValues( adder *adder, + fieldConstraints *validate.FieldConstraints, typeRulesMessage protoreflect.Message, fieldDescriptor protoreflect.FieldDescriptor, exampleValues []protoreflect.Value, ) error { + // TODO: remove + fmt.Println("field:", fieldDescriptor.Name()) v, err := protovalidate.New() if err != nil { return err } - var hasConstraints bool - typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { - if string(fd.Name()) != "example" { - hasConstraints = true - return false - } - return true - }) - // TODO: this is incorrect, also needs to check for cel etc. + hasConstraints := len(fieldConstraints.GetCel()) > 0 + // TODO: check if this checks shared rules + // TODO: add a test where only shared rules and examples are specified + if !hasConstraints { + typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + if string(fd.Name()) != "example" { + hasConstraints = true + return false + } + return true + }) + } if !hasConstraints { adder.addForPathf(nil, "example value is specified by there are no constraints defined") + // No need to check if example values satifisy constraints, because there is none. return nil } parentMessageDescriptor := fieldDescriptor.ContainingMessage() + // TODO: remove + fmt.Println("parent:", parentMessageDescriptor.Name()) for _, exampleValue := range exampleValues { + // For each example value, instantiate a message of its containing message's type + // and set the field that we are linting to the example value: + // containingMessage { + // ... + // fieldToLint: exampleValue, + // ... + // } + // and validate this message instance with protovalidate and filter the structured + // errors by field name to determine whether this example value fails rules defined + // on the same field. + // + // Note: there might be cel expressions defined on the message level that the example + // value would cause to fail, but we have no way of knowing that the example value is + // the reason, therefore it's ok to only filter for field level failures. messageToValidate := dynamicpb.NewMessage(parentMessageDescriptor) + // TODO: what about maps? if fieldDescriptor.Cardinality() == protoreflect.Repeated { list := messageToValidate.NewField(fieldDescriptor).List() list.Append(exampleValue) @@ -822,6 +850,8 @@ func checkExampleValues( continue } if errors.Is(err, &protovalidate.CompilationError{}) { + // TODO: remove + fmt.Println("FAIL TO COMPILE", err.Error()) // Expression failing to compile meaning some custom shared rules are invalid, // which is checked in this rule (PROTOVALIDATE), but not in this code block. // TODO: verify @@ -830,6 +860,8 @@ func checkExampleValues( validationErr := &protovalidate.ValidationError{} if errors.As(err, &validationErr) { for _, violation := range validationErr.Violations { + // TODO: remove + fmt.Println(fieldDescriptor.Name(), violation.FieldPath) if violation.FieldPath == string(fieldDescriptor.Name()) { adder.addForPathf(nil, `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.ConstraintId, violation.Message) } From 89e7440235ebf19e31e52e6e7e831154a28bf643 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Wed, 11 Sep 2024 12:27:24 -0400 Subject: [PATCH 05/30] checkpoint; works --- .../internal/buflintvalidate/field.go | 204 ++++++++++-------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 9fd7d5ad25..47c2b737d2 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -23,7 +23,7 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" - "github.com/bufbuild/buf/private/pkg/slicesext" + "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/protovalidate-go" "github.com/bufbuild/protovalidate-go/resolver" "google.golang.org/protobuf/proto" @@ -167,6 +167,8 @@ func checkField( addFunc: add, }, constraints, + fieldDescriptor.ContainingMessage(), + nil, fieldDescriptor, fieldDescriptor.Cardinality() == protoreflect.Repeated, ) @@ -175,6 +177,16 @@ func checkField( func checkConstraintsForField( adder *adder, fieldConstraints *validate.FieldConstraints, + // TODO: fix wording + // This is needed because recursive calls of this function still need the same + // containing message. For example, checkConstraintsForField(.., fieldDescriptor, ...) + // may call checkConstraintsForField(..., fieldDescriptor.MapKey(), ...), but the map + // key should be associated with the same containing message as the field's. Since + // fieldDescriptor.MapKey().ContainingMessage() is not the same as fieldDescriptor.ContainingMessage(), + // this needs to be passed around. + containingMessageDescriptor protoreflect.MessageDescriptor, + // Only pass a non nil value when the field is a synthetic map key/value field + parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, expectRepeatedRule bool, ) error { @@ -213,10 +225,10 @@ func checkConstraintsForField( typeRulesFieldNumber := int32(typeRulesFieldDescriptor.Number()) // Map and repeated special cases that contain fieldConstraints. if typeRulesFieldNumber == mapRulesFieldNumber { - return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor) + return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor) } if typeRulesFieldNumber == repeatedRulesFieldNumber { - return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor) + return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor) } typesMatch := checkRulesTypeMatchFieldType(adder, fieldDescriptor, typeRulesFieldNumber, expectRepeatedRule) if !typesMatch { @@ -228,58 +240,22 @@ func checkConstraintsForField( return nil } } - var exampleValues []protoreflect.Value switch typeRulesFieldNumber { - case floatRulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetFloat().GetExample(), protoreflect.ValueOfFloat32) - case doubleRulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetDouble().GetExample(), protoreflect.ValueOfFloat64) - case int32RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetInt32().GetExample(), protoreflect.ValueOfInt32) - case sInt32RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetSint32().GetExample(), protoreflect.ValueOfInt32) - case sFixed32RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetSfixed32().GetExample(), protoreflect.ValueOfInt32) - case fixed32RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetFixed32().GetExample(), protoreflect.ValueOfUint32) - case uInt32RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetUint32().GetExample(), protoreflect.ValueOfUint32) - case int64RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetInt64().GetExample(), protoreflect.ValueOfInt64) - case sFixed64RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetSfixed64().GetExample(), protoreflect.ValueOfInt64) - case sInt64RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetSint64().GetExample(), protoreflect.ValueOfInt64) - case fixed64RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetFixed64().GetExample(), protoreflect.ValueOfUint64) - case uInt64RulesFieldNumber: - exampleValues = slicesext.Map(fieldConstraints.GetUint64().GetExample(), protoreflect.ValueOfUint64) case boolRulesFieldNumber: // Bool rules only have one constraint, `const`, which does not need validating. - // However, we still validate that example values satify the constraint. - exampleValues = slicesext.Map(fieldConstraints.GetBool().GetExample(), protoreflect.ValueOfBool) case stringRulesFieldNumber: stringRules := fieldConstraints.GetString_() if err := checkStringRules(adder, stringRules); err != nil { return err } - exampleValues = slicesext.Map(stringRules.GetExample(), protoreflect.ValueOfString) case bytesRulesFieldNumber: bytesRules := fieldConstraints.GetBytes() if err := checkBytesRules(adder, bytesRules); err != nil { return err } - exampleValues = slicesext.Map( - bytesRules.GetExample(), - protoreflect.ValueOfBytes, - ) case enumRulesFieldNumber: enumRules := fieldConstraints.GetEnum() checkEnumRules(adder, enumRules) - exampleValues = slicesext.Map( - enumRules.GetExample(), - protoreflect.ValueOfInt32, - ) case anyRulesFieldNumber: // Any does not have example values. checkAnyRules(adder, fieldConstraints.GetAny()) @@ -288,43 +264,39 @@ func checkConstraintsForField( if err := checkDurationRules(adder, durationRules); err != nil { return err } - exampleValues = slicesext.Map( - durationRules.GetExample(), - func(duration *durationpb.Duration) protoreflect.Value { - return protoreflect.ValueOfMessage(duration.ProtoReflect()) - }, - ) case timestampRulesFieldNumber: timestampRules := fieldConstraints.GetTimestamp() if err := checkTimestampRules(adder, timestampRules); err != nil { return err } - exampleValues = slicesext.Map( - timestampRules.GetExample(), - func(timestamp *timestamppb.Timestamp) protoreflect.Value { - return protoreflect.ValueOfMessage(timestamp.ProtoReflect()) - }, - ) } typeRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() - // // This also works. Using this would allow dropping most of the code setting examples values. - // // However, a lot of the methods called here may panic, even though in practice they won't, - // // except for the rare case where a typed rule in protovalidate has an example field that's - // // not repeated. - // exampleValues = nil - // typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { - // if string(fd.Name()) == "example" { - // // This assumed all *Rules.Example are repeated, otherwise it panics. - // list := value.List() - // for i := 0; i < list.Len(); i++ { - // exampleValues = append(exampleValues, list.Get(i)) - // } - // return false - // } - // return true - // }) + var exampleValues []protoreflect.Value + var exampleFieldNumber int32 + typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { + // TODO: make "exmaple" a const + if string(fd.Name()) == "example" { + exampleFieldNumber = int32(fd.Number()) + // This assumed all *Rules.Example are repeated, otherwise it panics. + list := value.List() + for i := 0; i < list.Len(); i++ { + exampleValues = append(exampleValues, list.Get(i)) + } + return false + } + return true + }) if len(exampleValues) > 0 { - return checkExampleValues(adder, fieldConstraints, typeRulesMessage, fieldDescriptor, exampleValues) + return checkExampleValues( + adder, + []int32{typeRulesFieldNumber, exampleFieldNumber}, + fieldConstraints, + typeRulesMessage, + containingMessageDescriptor, + parentMapFieldDescriptor, + fieldDescriptor, + exampleValues, + ) } return nil } @@ -455,6 +427,7 @@ func checkRepeatedRules( baseAdder *adder, repeatedRules *validate.RepeatedRules, fieldDescriptor protoreflect.FieldDescriptor, + containingMessageDescriptor protoreflect.MessageDescriptor, ) error { if !fieldDescriptor.IsList() { baseAdder.addForPathf( @@ -497,13 +470,14 @@ func checkRepeatedRules( ) } itemAdder := baseAdder.cloneWithNewBasePath(repeatedRulesFieldNumber, itemsFieldNumberInRepeatedRules) - return checkConstraintsForField(itemAdder, repeatedRules.Items, fieldDescriptor, false) + return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false) } func checkMapRules( baseAdder *adder, mapRules *validate.MapRules, fieldDescriptor protoreflect.FieldDescriptor, + containingMessageDescriptor protoreflect.MessageDescriptor, ) error { if !fieldDescriptor.IsMap() { baseAdder.addForPathf( @@ -535,12 +509,12 @@ func checkMapRules( ) } keyAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, keysFieldNumberInMapRules) - err := checkConstraintsForField(keyAdder, mapRules.Keys, fieldDescriptor.MapKey(), false) + err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false) if err != nil { return err } valueAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, valuesFieldNumberInMapRules) - return checkConstraintsForField(valueAdder, mapRules.Values, fieldDescriptor.MapValue(), false) + return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false) } func checkStringRules(adder *adder, stringRules *validate.StringRules) error { @@ -790,13 +764,15 @@ func checkTimestampRules(adder *adder, timestampRules *validate.TimestampRules) func checkExampleValues( adder *adder, + pathToExampleValues []int32, fieldConstraints *validate.FieldConstraints, typeRulesMessage protoreflect.Message, + containingMessageDescriptor protoreflect.MessageDescriptor, + // Not nil only if fieldDescriptor is a synthetic field for map key/value. + parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, exampleValues []protoreflect.Value, ) error { - // TODO: remove - fmt.Println("field:", fieldDescriptor.Name()) v, err := protovalidate.New() if err != nil { return err @@ -814,14 +790,11 @@ func checkExampleValues( }) } if !hasConstraints { - adder.addForPathf(nil, "example value is specified by there are no constraints defined") + adder.addForPathf(pathToExampleValues, "example value is specified by there are no constraints defined") // No need to check if example values satifisy constraints, because there is none. return nil } - parentMessageDescriptor := fieldDescriptor.ContainingMessage() - // TODO: remove - fmt.Println("parent:", parentMessageDescriptor.Name()) - for _, exampleValue := range exampleValues { + for exampleValueIndex, exampleValue := range exampleValues { // For each example value, instantiate a message of its containing message's type // and set the field that we are linting to the example value: // containingMessage { @@ -836,12 +809,65 @@ func checkExampleValues( // Note: there might be cel expressions defined on the message level that the example // value would cause to fail, but we have no way of knowing that the example value is // the reason, therefore it's ok to only filter for field level failures. - messageToValidate := dynamicpb.NewMessage(parentMessageDescriptor) - // TODO: what about maps? - if fieldDescriptor.Cardinality() == protoreflect.Repeated { + messageToValidate := dynamicpb.NewMessage(containingMessageDescriptor) + // TODO: test it for repeated min item + // This is needed because the shape of field path in a protovalidate.Violation depends on + // the type of the field descriptor. + matchViolationFunc := func(violation *validate.Violation) bool { + return violation.FieldPath == string(fieldDescriptor.Name()) + } + if fieldDescriptor.IsList() { // this is linting for items (they have the same descriptor) // TODO: update this comment so that it makes sense list := messageToValidate.NewField(fieldDescriptor).List() list.Append(exampleValue) messageToValidate.Set(fieldDescriptor, protoreflect.ValueOfList(list)) + matchViolationFunc = func(violation *validate.Violation) bool { + prefix := fieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.FieldPath + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) + } + } else if parentMapFieldDescriptor != nil { + mapEntryMessageDescriptor := fieldDescriptor.ContainingMessage() + if !mapEntryMessageDescriptor.IsMapEntry() { + return syserror.Newf("containing message %q is not a map", mapEntryMessageDescriptor.Name()) + } + if !parentMapFieldDescriptor.IsMap() { + return syserror.Newf("parent field descriptor %q is passed but is not a map field", parentMapFieldDescriptor.Name()) + } + if containingMessageDescriptor.Fields().ByName(parentMapFieldDescriptor.Name()) == nil { + return syserror.Newf("containing message %q does not have field named %q", containingMessageDescriptor.Name(), parentMapFieldDescriptor.Name()) + } + if parentMapFieldDescriptor.Message().Name() != mapEntryMessageDescriptor.Name() { + return syserror.Newf("field %q should have parent of type %q but has type %q", fieldDescriptor.Name(), parentMapFieldDescriptor.Message().Name(), containingMessageDescriptor.Name()) + } + mapEntry := messageToValidate.NewField(parentMapFieldDescriptor).Map() + switch fieldDescriptor.Name() { + case "key": + mapEntry.Set( + exampleValue.MapKey(), + dynamicpb.NewMessage(parentMapFieldDescriptor.Message()).NewField(parentMapFieldDescriptor.MapValue()), + ) + matchViolationFunc = func(violation *validate.Violation) bool { + prefix := parentMapFieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.FieldPath + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && violation.ForKey + } + case "value": + mapEntry.Set( + dynamicpb.NewMessage(parentMapFieldDescriptor.Message()).NewField(parentMapFieldDescriptor.MapKey()).MapKey(), + exampleValue, + ) + matchViolationFunc = func(violation *validate.Violation) bool { + prefix := parentMapFieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.FieldPath + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.ForKey + } + default: + return syserror.Newf("expected key or value as sythetic field name for map entry's field name, got %q", fieldDescriptor.Name()) + } + messageToValidate.Set(parentMapFieldDescriptor, protoreflect.ValueOfMap(mapEntry)) } else { messageToValidate.Set(fieldDescriptor, exampleValue) } @@ -849,28 +875,26 @@ func checkExampleValues( if err == nil { continue } - if errors.Is(err, &protovalidate.CompilationError{}) { - // TODO: remove - fmt.Println("FAIL TO COMPILE", err.Error()) + var compilationErr *protovalidate.CompilationError + if errors.As(err, &compilationErr) { // Expression failing to compile meaning some custom shared rules are invalid, // which is checked in this rule (PROTOVALIDATE), but not in this code block. - // TODO: verify break } validationErr := &protovalidate.ValidationError{} if errors.As(err, &validationErr) { for _, violation := range validationErr.Violations { - // TODO: remove - fmt.Println(fieldDescriptor.Name(), violation.FieldPath) - if violation.FieldPath == string(fieldDescriptor.Name()) { - adder.addForPathf(nil, `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.ConstraintId, violation.Message) + if matchViolationFunc(violation) { + adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.ConstraintId, violation.Message) } } continue } runtimeError := &protovalidate.RuntimeError{} if errors.As(err, &runtimeError) { - adder.addForPathf(nil, "example fail at runtime: %s", runtimeError.Error()) + // TODO: what to do here? + // return an error? + adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), "example fail at runtime: %s", runtimeError.Error()) continue } return fmt.Errorf("unexpected error from protovalidate: %s", err.Error()) From cf88e02e29b5b46146fadb79ec8c9c2fe0fb516d Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 18:16:00 -0400 Subject: [PATCH 06/30] checkpoint; seems to work --- go.mod | 2 +- go.sum | 2 - .../internal/bufcheckserverhandle/lint.go | 121 +++++++------ .../buflintvalidate/buflintvalidate.go | 82 ++++++++- .../internal/buflintvalidate/cel.go | 17 +- .../internal/buflintvalidate/field.go | 160 ++++++++++++++++-- 6 files changed, 307 insertions(+), 77 deletions(-) diff --git a/go.mod b/go.mod index bb8f1796d3..e592083a7d 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( ) // TODO: remove -replace github.com/bufbuild/protovalidate-go => github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd +replace github.com/bufbuild/protovalidate-go => ../pvgojohn // TODO: remove replace buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go => ../pv-gen diff --git a/go.sum b/go.sum index 5e83c0173c..36ea69b674 100644 --- a/go.sum +++ b/go.sum @@ -153,8 +153,6 @@ github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd h1:/JZgJiQWzuahn1u3D15Udv88eR+80HZmxumBvMgbsew= -github.com/jchadwick-buf/protovalidate-go v0.0.0-20240904032402-bfe586b85ffd/go.mod h1:LHDiGCWSM3GagZEnyEZ1sPtFwi6Ja4tVTi/DCc+iDFI= github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ= github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8= github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg= diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index 4b6a12b330..d0eec6df9f 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -31,6 +31,7 @@ import ( "github.com/bufbuild/buf/private/pkg/stringutil" "github.com/bufbuild/bufplugin-go/check" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/descriptorpb" ) @@ -935,56 +936,80 @@ func handleLintPackageVersionSuffix( } // HandleLintProtovalidate is a handle function. -var HandleLintProtovalidate = bufcheckserverutil.NewMultiHandler( - // NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint. - bufcheckserverutil.NewLintMessageRuleHandler(handleLintMessageProtovalidate), - bufcheckserverutil.NewLintFieldRuleHandler(handleLintFieldProtovalidate), +var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( + func( + ctx context.Context, + responseWriter bufcheckserverutil.ResponseWriter, + request bufcheckserverutil.Request, + ) error { + // TODO: refactor so fact this add func is no longer needed + addAnnotationFunc := func( + _ bufprotosource.Descriptor, + location bufprotosource.Location, + _ []bufprotosource.Location, + format string, + args ...interface{}, + ) { + responseWriter.AddProtosourceAnnotation( + location, + nil, + format, + args..., + ) + } + types := new(protoregistry.Types) + files := request.ProtosourceFiles() + // Check shared rules extending protovalidate rules. + // This also registers these extensions to types if they are valid, which will + // be useful later on when example values are checked. Therefore, this check + // regisrer must happen first. + for _, file := range files { + // Regardless if the file is import, we want to add shared rule extensions to the types registry, + // as long as the extension's cel expressions compile. + if err := bufprotosource.ForEachMessage( + func(message bufprotosource.Message) error { + for _, field := range message.Extensions() { + if err := buflintvalidate.CheckAndRegisterSharedRuleExtension(addAnnotationFunc, field, types); err != nil { + return nil + } + } + return nil + }, + file, + ); err != nil { + return err + } + for _, field := range file.Extensions() { + if err := buflintvalidate.CheckAndRegisterSharedRuleExtension(addAnnotationFunc, field, types); err != nil { + return nil + } + } + } + if err := bufcheckserverutil.NewLintMessageRuleHandler( + func( + responseWriter bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.Request, + message bufprotosource.Message, + ) error { + return buflintvalidate.CheckMessage(addAnnotationFunc, message) + }).Handle(ctx, responseWriter, request); err != nil { + return err + } + if err := bufcheckserverutil.NewLintFieldRuleHandler( + func( + responseWriter bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.Request, + field bufprotosource.Field, + ) error { + return buflintvalidate.CheckField(addAnnotationFunc, field, types) + }).Handle(ctx, responseWriter, request); err != nil { + return err + } + // NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint. + return nil + }, ) -func handleLintMessageProtovalidate( - responseWriter bufcheckserverutil.ResponseWriter, - _ bufcheckserverutil.Request, - message bufprotosource.Message, -) error { - addAnnotationFunc := func( - _ bufprotosource.Descriptor, - location bufprotosource.Location, - _ []bufprotosource.Location, - format string, - args ...interface{}, - ) { - responseWriter.AddProtosourceAnnotation( - location, - nil, - format, - args..., - ) - } - return buflintvalidate.CheckMessage(addAnnotationFunc, message) -} - -func handleLintFieldProtovalidate( - responseWriter bufcheckserverutil.ResponseWriter, - _ bufcheckserverutil.Request, - field bufprotosource.Field, -) error { - addAnnotationFunc := func( - _ bufprotosource.Descriptor, - location bufprotosource.Location, - _ []bufprotosource.Location, - format string, - args ...interface{}, - ) { - responseWriter.AddProtosourceAnnotation( - location, - nil, - format, - args..., - ) - } - return buflintvalidate.CheckField(addAnnotationFunc, field) -} - // HandleLintRPCNoClientStreaming is a handle function. var HandleLintRPCNoClientStreaming = bufcheckserverutil.NewLintMethodRuleHandler(handleLintRPCNoClientStreaming) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index b8759b8655..a35336ffe1 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -15,14 +15,92 @@ package buflintvalidate import ( + "strings" + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/syserror" + "github.com/bufbuild/protovalidate-go/celext" "github.com/bufbuild/protovalidate-go/resolver" + "github.com/google/cel-go/cel" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/dynamicpb" ) // https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints const disabledFieldNumberInMesageConstraints = 1 +// TODO: add doc +// Only registers if cel compiles +func CheckAndRegisterSharedRuleExtension( + addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), + field bufprotosource.Field, + // TODO: update parameter type and name + types *protoregistry.Types, +) error { + fieldDescriptor, err := field.AsDescriptor() + if err != nil { + return err + } + // TODO: move to a different func instead of having nested ifs + if fieldDescriptor.IsExtension() { + extendedStandardRuleDescriptor := fieldDescriptor.ContainingMessage() + extendedRuleFullName := extendedStandardRuleDescriptor.FullName() + if strings.HasPrefix(string(extendedRuleFullName), "buf.validate.") && strings.HasSuffix(string(extendedRuleFullName), "Rules") { + // Just to be extra sure. + if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) != nil { + if extendedRules := resolveExt[*validate.SharedFieldConstraints](fieldDescriptor.Options(), validate.E_SharedField); extendedRules != nil { + celEnv, err := celext.DefaultEnv(false) + if err != nil { + return err + } + // TODO: add an example in comment + // This is a bit of hacky, relying on the fact that each *Rules has a const rule, + // we take advantage of it to give "this" a type. + ruleConstFieldDescriptor := extendedStandardRuleDescriptor.Fields().ByName("const") + if ruleConstFieldDescriptor == nil { + // This shouldn't happen + return syserror.Newf("unexpected protovalidate rule without a const rule, which is relied upon by buf lint") + } + thisType := celext.ProtoFieldToCELType(ruleConstFieldDescriptor, false, false) + celEnv, err = celEnv.Extend( + append( + celext.RequiredCELEnvOptions(fieldDescriptor), + cel.Variable("rule", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), + cel.Variable("this", thisType), + )..., + ) + if err != nil { + return err + } + if checkCEL( + celEnv, + extendedRules.GetCel(), + "TODO1", + "TODO2", + "TODO3", + func(index int, format string, args ...interface{}) { + addAnnotationFunc( + field, + // TODO: move 1 to a const + field.OptionExtensionLocation(validate.E_SharedField, 1, int32(index)), + nil, + format, + args..., + ) + }, + ) { + if err := types.RegisterExtension(dynamicpb.NewExtensionType(fieldDescriptor)); err != nil { + return err + } + } + } + } + } + } + return nil +} + // CheckMessage validates that all rules on the message are valid, and any CEL expressions compile. // // addAnnotationFunc adds an annotation with the descriptor and location for check results. @@ -62,6 +140,8 @@ func CheckMessage( func CheckField( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, + // TODO: update parameter type and name + types *protoregistry.Types, ) error { - return checkField(addAnnotationFunc, field) + return checkField(addAnnotationFunc, field, types) } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go index dd6beae1fd..3030b81310 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go @@ -19,7 +19,6 @@ import ( "strings" "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" - "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/shared" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" "github.com/bufbuild/protovalidate-go/celext" "github.com/google/cel-go/cel" @@ -110,14 +109,16 @@ func checkCELForField( return nil } +// Returns true only if all cel expressions compile func checkCEL( celEnv *cel.Env, - celConstraints []*shared.Constraint, + celConstraints []*validate.Constraint, parentName string, parentNameCapitalized string, celName string, add func(int, string, ...interface{}), -) { +) bool { + allCelExpressionsCompile := true idToConstraintIndices := make(map[string][]int, len(celConstraints)) for i, celConstraint := range celConstraints { if celID := celConstraint.GetId(); celID != "" { @@ -143,14 +144,14 @@ func checkCEL( } else { add(i, "%s has an empty %s.id. IDs should always be specified.", parentNameCapitalized, celName) } - if len(strings.TrimSpace(celConstraint.Expression)) == 0 { + if len(strings.TrimSpace(celConstraint.GetExpression())) == 0 { add(i, "%s has an empty %s.expression. Expressions should always be specified.", parentNameCapitalized, celName) continue } - ast, compileIssues := celEnv.Compile(celConstraint.Expression) + ast, compileIssues := celEnv.Compile(celConstraint.GetExpression()) switch { case ast.OutputType().IsAssignableType(cel.BoolType): - if celConstraint.Message == "" { + if celConstraint.GetMessage() == "" { add( i, "%s has an empty %s.message for an expression that evaluates to a boolean. If an expression evaluates to a boolean, a message should always be specified.", @@ -159,7 +160,7 @@ func checkCEL( ) } case ast.OutputType().IsAssignableType(cel.StringType): - if celConstraint.Message != "" { + if celConstraint.GetMessage() != "" { add( i, "%s has a %s with an expression that evaluates to a string, and also has a message. The message is redundant - since the expression evaluates to a string, its result will be printed instead of the message, so the message should be removed.", @@ -180,6 +181,7 @@ func checkCEL( ) } if compileIssues.Err() != nil { + allCelExpressionsCompile = false for _, parsedIssue := range parseCelIssuesText(compileIssues.Err().Error()) { add( i, @@ -205,6 +207,7 @@ func checkCEL( ) } } + return allCelExpressionsCompile } // this depends on the undocumented behavior of cel-go's error message diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 47c2b737d2..8782b54eef 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -29,6 +29,7 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" @@ -154,26 +155,104 @@ var ( func checkField( add func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, + // TODO: update type and name + types *protoregistry.Types, ) error { fieldDescriptor, err := field.AsDescriptor() if err != nil { return err } + adder := &adder{ + field: field, + fieldPrettyTypeName: getFieldTypePrettyNameName(fieldDescriptor), + addFunc: add, + } + // TODO: move to a different func instead of having nested ifs + // if fieldDescriptor.IsExtension() { + // extendedStandardRuleDescriptor := fieldDescriptor.ContainingMessage() + // extendedRuleName := extendedStandardRuleDescriptor.Name() + // if strings.HasPrefix(string(extendedRuleName), "buf.validate.") && strings.HasSuffix(string(extendedRuleName), "Rules") { + // // Just to be extra sure. + // if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleName) != nil { + // if extendedRules := resolveExt[*validate.SharedFieldConstraints](extendedStandardRuleDescriptor.Options(), validate.E_SharedField); extendedRules != nil { + // celEnv, err := celext.DefaultEnv(false) + // if err != nil { + // return err + // } + // celEnv, err = celEnv.Extend( + // append( + // celext.RequiredCELEnvOptions(fieldDescriptor), + // // TODO: this is incorrect + // cel.Variable("this", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), + // // TODO: this might be incorrect + // cel.Variable("rule", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), + // )..., + // ) + // if err != nil { + // return err + // } + // checkCEL( + // celEnv, + // extendedRules.GetCel(), + // fmt.Sprintf("TODO1"), + // fmt.Sprintf("TODO2"), + // fmt.Sprintf("TODO3"), + // func(index int, format string, args ...interface{}) { + // adder.addForPathf( + // []int32{celFieldNumberInFieldConstraints, int32(index)}, + // format, + // args..., + // ) + // }, + // ) + // } + // } + // } + // } constraints := resolver.DefaultResolver{}.ResolveFieldConstraints(fieldDescriptor) return checkConstraintsForField( - &adder{ - field: field, - fieldPrettyTypeName: getFieldTypePrettyNameName(fieldDescriptor), - addFunc: add, - }, + adder, constraints, fieldDescriptor.ContainingMessage(), nil, fieldDescriptor, fieldDescriptor.Cardinality() == protoreflect.Repeated, + // TODO: update type and name + types, ) } +// TODO: this function is copied directly from protovalidate-go +// We have 3 options: +// 1. in protovalidate-go, add DefaultResolver.ResolveSharedFieldConstraints +// 2. in protovalidate-go +func resolveExt[C proto.Message]( + options proto.Message, + extType protoreflect.ExtensionType, +) (constraints C) { + num := extType.TypeDescriptor().Number() + var msg proto.Message + + proto.RangeExtensions(options, func(typ protoreflect.ExtensionType, i interface{}) bool { + if num != typ.TypeDescriptor().Number() { + return true + } + msg, _ = i.(proto.Message) + return false + }) + + if msg == nil { + return constraints + } else if m, ok := msg.(C); ok { + return m + } + + constraints, _ = constraints.ProtoReflect().New().Interface().(C) + b, _ := proto.Marshal(msg) + _ = proto.Unmarshal(b, constraints) + return constraints +} + func checkConstraintsForField( adder *adder, fieldConstraints *validate.FieldConstraints, @@ -189,6 +268,8 @@ func checkConstraintsForField( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, expectRepeatedRule bool, + // TODO: update parameter type and name + types *protoregistry.Types, ) error { if fieldConstraints == nil { return nil @@ -225,10 +306,10 @@ func checkConstraintsForField( typeRulesFieldNumber := int32(typeRulesFieldDescriptor.Number()) // Map and repeated special cases that contain fieldConstraints. if typeRulesFieldNumber == mapRulesFieldNumber { - return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor) + return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor, types) } if typeRulesFieldNumber == repeatedRulesFieldNumber { - return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor) + return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor, types) } typesMatch := checkRulesTypeMatchFieldType(adder, fieldDescriptor, typeRulesFieldNumber, expectRepeatedRule) if !typesMatch { @@ -245,6 +326,10 @@ func checkConstraintsForField( // Bool rules only have one constraint, `const`, which does not need validating. case stringRulesFieldNumber: stringRules := fieldConstraints.GetString_() + err := reparseUnrecognized(stringRules.ProtoReflect(), types) + if err != nil { + return fmt.Errorf("error reparsing message: %w", err) + } if err := checkStringRules(adder, stringRules); err != nil { return err } @@ -301,6 +386,32 @@ func checkConstraintsForField( return nil } +type fieldExclusiveConstraintsResolver struct { + protovalidate.StandardConstraintResolver + targetField protoreflect.FieldDescriptor +} + +func (r *fieldExclusiveConstraintsResolver) ResolveFieldConstraints(desc protoreflect.FieldDescriptor) *validate.FieldConstraints { + if desc.FullName() != r.targetField.FullName() { + return nil + } + return r.StandardConstraintResolver.ResolveFieldConstraints(desc) +} + +func reparseUnrecognized(reflectMessage protoreflect.Message, types *protoregistry.Types) error { + unknown := reflectMessage.GetUnknown() + if len(unknown) > 0 { + reflectMessage.SetUnknown(nil) + options := proto.UnmarshalOptions{ + Resolver: types, + Merge: true, + } + if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil { + return err + } + } + return nil +} func checkFieldFlags( adder *adder, fieldConstraints *validate.FieldConstraints, @@ -428,6 +539,8 @@ func checkRepeatedRules( repeatedRules *validate.RepeatedRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, + // TODO: update parameter type and name + types *protoregistry.Types, ) error { if !fieldDescriptor.IsList() { baseAdder.addForPathf( @@ -470,7 +583,7 @@ func checkRepeatedRules( ) } itemAdder := baseAdder.cloneWithNewBasePath(repeatedRulesFieldNumber, itemsFieldNumberInRepeatedRules) - return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false) + return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false, types) } func checkMapRules( @@ -478,6 +591,8 @@ func checkMapRules( mapRules *validate.MapRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, + // TODO: update parameter type and name + types *protoregistry.Types, ) error { if !fieldDescriptor.IsMap() { baseAdder.addForPathf( @@ -509,12 +624,12 @@ func checkMapRules( ) } keyAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, keysFieldNumberInMapRules) - err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false) + err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false, types) if err != nil { return err } valueAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, valuesFieldNumberInMapRules) - return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false) + return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false, types) } func checkStringRules(adder *adder, stringRules *validate.StringRules) error { @@ -773,7 +888,16 @@ func checkExampleValues( fieldDescriptor protoreflect.FieldDescriptor, exampleValues []protoreflect.Value, ) error { - v, err := protovalidate.New() + v, err := protovalidate.New( + protovalidate.WithStandardConstraintInterceptor( + func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { + return &fieldExclusiveConstraintsResolver{ + StandardConstraintResolver: res, + targetField: fieldDescriptor, + } + }, + ), + ) if err != nil { return err } @@ -814,7 +938,7 @@ func checkExampleValues( // This is needed because the shape of field path in a protovalidate.Violation depends on // the type of the field descriptor. matchViolationFunc := func(violation *validate.Violation) bool { - return violation.FieldPath == string(fieldDescriptor.Name()) + return violation.GetFieldPath() == string(fieldDescriptor.Name()) } if fieldDescriptor.IsList() { // this is linting for items (they have the same descriptor) // TODO: update this comment so that it makes sense list := messageToValidate.NewField(fieldDescriptor).List() @@ -823,7 +947,7 @@ func checkExampleValues( matchViolationFunc = func(violation *validate.Violation) bool { prefix := fieldDescriptor.Name() + "[" suffix := "]" - fieldPath := violation.FieldPath + fieldPath := violation.GetFieldPath() return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) } } else if parentMapFieldDescriptor != nil { @@ -850,8 +974,8 @@ func checkExampleValues( matchViolationFunc = func(violation *validate.Violation) bool { prefix := parentMapFieldDescriptor.Name() + "[" suffix := "]" - fieldPath := violation.FieldPath - return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && violation.ForKey + fieldPath := violation.GetFieldPath() + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && violation.GetForKey() } case "value": mapEntry.Set( @@ -861,8 +985,8 @@ func checkExampleValues( matchViolationFunc = func(violation *validate.Violation) bool { prefix := parentMapFieldDescriptor.Name() + "[" suffix := "]" - fieldPath := violation.FieldPath - return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.ForKey + fieldPath := violation.GetFieldPath() + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.GetForKey() } default: return syserror.Newf("expected key or value as sythetic field name for map entry's field name, got %q", fieldDescriptor.Name()) @@ -885,7 +1009,7 @@ func checkExampleValues( if errors.As(err, &validationErr) { for _, violation := range validationErr.Violations { if matchViolationFunc(violation) { - adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.ConstraintId, violation.Message) + adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.GetConstraintId(), violation.GetMessage()) } } continue From 1ca775b7fcc218098b236070a286e54f1de2ccfa Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 19:27:57 -0400 Subject: [PATCH 07/30] clean up shared rules --- .../internal/bufcheckserverhandle/lint.go | 45 ++++--- .../buflintvalidate/buflintvalidate.go | 76 +----------- .../internal/buflintvalidate/shared_rules.go | 114 ++++++++++++++++++ 3 files changed, 147 insertions(+), 88 deletions(-) create mode 100644 private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index d0eec6df9f..081faba8e8 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -942,7 +942,8 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( responseWriter bufcheckserverutil.ResponseWriter, request bufcheckserverutil.Request, ) error { - // TODO: refactor so fact this add func is no longer needed + // TODO: refactor so that this add func is no longer needed, but for now, + // keeping as is addAnnotationFunc := func( _ bufprotosource.Descriptor, location bufprotosource.Location, @@ -957,19 +958,24 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( args..., ) } - types := new(protoregistry.Types) - files := request.ProtosourceFiles() - // Check shared rules extending protovalidate rules. - // This also registers these extensions to types if they are valid, which will - // be useful later on when example values are checked. Therefore, this check - // regisrer must happen first. - for _, file := range files { - // Regardless if the file is import, we want to add shared rule extensions to the types registry, - // as long as the extension's cel expressions compile. + extensionTypesFromRequest := new(protoregistry.Types) + // This for-loop checks that shared rules' cel expressions compile and add + // those that compile to the extension types, as a side effect. These types + // will be useful later on when example values are checked. Therefore, this + // loop must run before field and messages are checked. + for _, file := range request.ProtosourceFiles() { + // Regardless whether its file is an import, we want to add a shared rule + // extension to the types registry, as long as the extension's cel + // expressions compile. + // TODO: pass a nop addFunc the file is an import, if err := bufprotosource.ForEachMessage( func(message bufprotosource.Message) error { - for _, field := range message.Extensions() { - if err := buflintvalidate.CheckAndRegisterSharedRuleExtension(addAnnotationFunc, field, types); err != nil { + for _, extension := range message.Extensions() { + if err := buflintvalidate.CheckAndRegisterSharedRuleExtension( + addAnnotationFunc, + extension, + extensionTypesFromRequest, + ); err != nil { return nil } } @@ -979,15 +985,19 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( ); err != nil { return err } - for _, field := range file.Extensions() { - if err := buflintvalidate.CheckAndRegisterSharedRuleExtension(addAnnotationFunc, field, types); err != nil { + for _, extension := range file.Extensions() { + if err := buflintvalidate.CheckAndRegisterSharedRuleExtension( + addAnnotationFunc, + extension, + extensionTypesFromRequest, + ); err != nil { return nil } } } if err := bufcheckserverutil.NewLintMessageRuleHandler( func( - responseWriter bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.ResponseWriter, _ bufcheckserverutil.Request, message bufprotosource.Message, ) error { @@ -995,13 +1005,14 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( }).Handle(ctx, responseWriter, request); err != nil { return err } + // At this point the extension types are already populated. if err := bufcheckserverutil.NewLintFieldRuleHandler( func( - responseWriter bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.ResponseWriter, _ bufcheckserverutil.Request, field bufprotosource.Field, ) error { - return buflintvalidate.CheckField(addAnnotationFunc, field, types) + return buflintvalidate.CheckField(addAnnotationFunc, field, extensionTypesFromRequest) }).Handle(ctx, responseWriter, request); err != nil { return err } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index a35336ffe1..a5be300e2f 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -15,90 +15,24 @@ package buflintvalidate import ( - "strings" - "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" - "github.com/bufbuild/buf/private/pkg/syserror" - "github.com/bufbuild/protovalidate-go/celext" "github.com/bufbuild/protovalidate-go/resolver" - "github.com/google/cel-go/cel" "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/types/dynamicpb" ) // https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints const disabledFieldNumberInMesageConstraints = 1 -// TODO: add doc -// Only registers if cel compiles +// CheckAndRegisterSharedRuleExtension checks whether an extension extending a protovalidate rule +// is valid, checking that all of its CEL expressionus compile. If so, the extension type is added to +// the extension types passed in. func CheckAndRegisterSharedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - // TODO: update parameter type and name - types *protoregistry.Types, + extensionTypesToPopulate *protoregistry.Types, ) error { - fieldDescriptor, err := field.AsDescriptor() - if err != nil { - return err - } - // TODO: move to a different func instead of having nested ifs - if fieldDescriptor.IsExtension() { - extendedStandardRuleDescriptor := fieldDescriptor.ContainingMessage() - extendedRuleFullName := extendedStandardRuleDescriptor.FullName() - if strings.HasPrefix(string(extendedRuleFullName), "buf.validate.") && strings.HasSuffix(string(extendedRuleFullName), "Rules") { - // Just to be extra sure. - if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) != nil { - if extendedRules := resolveExt[*validate.SharedFieldConstraints](fieldDescriptor.Options(), validate.E_SharedField); extendedRules != nil { - celEnv, err := celext.DefaultEnv(false) - if err != nil { - return err - } - // TODO: add an example in comment - // This is a bit of hacky, relying on the fact that each *Rules has a const rule, - // we take advantage of it to give "this" a type. - ruleConstFieldDescriptor := extendedStandardRuleDescriptor.Fields().ByName("const") - if ruleConstFieldDescriptor == nil { - // This shouldn't happen - return syserror.Newf("unexpected protovalidate rule without a const rule, which is relied upon by buf lint") - } - thisType := celext.ProtoFieldToCELType(ruleConstFieldDescriptor, false, false) - celEnv, err = celEnv.Extend( - append( - celext.RequiredCELEnvOptions(fieldDescriptor), - cel.Variable("rule", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), - cel.Variable("this", thisType), - )..., - ) - if err != nil { - return err - } - if checkCEL( - celEnv, - extendedRules.GetCel(), - "TODO1", - "TODO2", - "TODO3", - func(index int, format string, args ...interface{}) { - addAnnotationFunc( - field, - // TODO: move 1 to a const - field.OptionExtensionLocation(validate.E_SharedField, 1, int32(index)), - nil, - format, - args..., - ) - }, - ) { - if err := types.RegisterExtension(dynamicpb.NewExtensionType(fieldDescriptor)); err != nil { - return err - } - } - } - } - } - } - return nil + return checkAndRegisterSharedRuleExtension(addAnnotationFunc, field, extensionTypesToPopulate) } // CheckMessage validates that all rules on the message are valid, and any CEL expressions compile. diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go new file mode 100644 index 0000000000..ee5cf41254 --- /dev/null +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go @@ -0,0 +1,114 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// 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 buflintvalidate + +import ( + "strings" + + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/protovalidate-go/celext" + "github.com/google/cel-go/cel" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/dynamicpb" +) + +func checkAndRegisterSharedRuleExtension( + addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), + extension bufprotosource.Field, + extensionTypesToPopulate *protoregistry.Types, +) error { + extensionDescriptor, err := extension.AsDescriptor() + if err != nil { + return err + } + // Double check. + if !extensionDescriptor.IsExtension() { + return nil + } + extendedStandardRuleDescriptor := extensionDescriptor.ContainingMessage() + extendedRuleFullName := extendedStandardRuleDescriptor.FullName() + // This function only lints extensions extending buf.validate.*Rules, e.g. buf.validate.StringRules. + if !(strings.HasPrefix(string(extendedRuleFullName), "buf.validate.") && strings.HasSuffix(string(extendedRuleFullName), "Rules")) { + return nil + } + // Just to be extra sure. + if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { + return nil + } + sharedConstraints := resolveExt[*validate.SharedFieldConstraints](extensionDescriptor.Options(), validate.E_SharedField) + if sharedConstraints == nil { + return nil + } + celEnv, err := celext.DefaultEnv(false) + if err != nil { + return err + } + // Two keywords need type declaration, "this" and "rule", for the expression to compile. + // In this example, an int32 field is added to extend string rules, and therefore, + // "rule" has type int32 and "this" has type "string": + // + // extend buf.validate.StringRules { + // optional int32 my_max = 47892 [(buf.validate.shared_field).cel = { + // id: "mymax" + // message: "at most max" + // expression: "size(this) < rule" + // }]; + // } + // + ruleType := celext.ProtoFieldToCELType(extensionDescriptor, false, false) // TODO: forItems should probably be false? + // This is a bit of hacky, relying on the fact that each *Rules has a const rule, + // and we take advantage of each buf.validate.Rules.const has type , which + // is the type "this" should have. + ruleConstFieldDescriptor := extendedStandardRuleDescriptor.Fields().ByName("const") + if ruleConstFieldDescriptor == nil { + // This isn't necessarily an error, it could be caused by a future buf.validate.*Rules without a const field. + return nil + } + thisType := celext.ProtoFieldToCELType(ruleConstFieldDescriptor, false, false) // TODO: forItems is probably false? + celEnv, err = celEnv.Extend( + append( + celext.RequiredCELEnvOptions(extensionDescriptor), + cel.Variable("rule", ruleType), + cel.Variable("this", thisType), + )..., + ) + if err != nil { + return err + } + allCELExpressionsCompile := checkCEL( + celEnv, + sharedConstraints.GetCel(), + "extension field", + "Extension field", + "(buf.validate.shared_field).cel", + func(index int, format string, args ...interface{}) { + addAnnotationFunc( + extension, + // TODO: move 1 to a const + extension.OptionExtensionLocation(validate.E_SharedField, 1, int32(index)), + nil, + format, + args..., + ) + }, + ) + if allCELExpressionsCompile { + if err := extensionTypesToPopulate.RegisterExtension(dynamicpb.NewExtensionType(extensionDescriptor)); err != nil { + return err + } + } + return nil +} From c664274fba91de38f420f508af4cd110de099194 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 19:46:48 -0400 Subject: [PATCH 08/30] move --- .../buflintvalidate/buflintvalidate.go | 12 +- .../internal/buflintvalidate/field.go | 129 ++---------------- .../internal/buflintvalidate/util.go | 85 ++++++++++++ 3 files changed, 106 insertions(+), 120 deletions(-) create mode 100644 private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index a5be300e2f..03bca543fe 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -18,12 +18,19 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" "github.com/bufbuild/protovalidate-go/resolver" + "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" ) // https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints const disabledFieldNumberInMesageConstraints = 1 +// ExtensionTypeResolver is an extension resolver, the same type as the Resolver in proto.UnmarshalOptions. +type ExtensionTypeResolver interface { + FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) + FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) +} + // CheckAndRegisterSharedRuleExtension checks whether an extension extending a protovalidate rule // is valid, checking that all of its CEL expressionus compile. If so, the extension type is added to // the extension types passed in. @@ -74,8 +81,7 @@ func CheckMessage( func CheckField( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - // TODO: update parameter type and name - types *protoregistry.Types, + extenExtensionTypeResolver ExtensionTypeResolver, ) error { - return checkField(addAnnotationFunc, field, types) + return checkField(addAnnotationFunc, field, extenExtensionTypeResolver) } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 8782b54eef..d61008cab9 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -29,7 +29,6 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/durationpb" @@ -155,8 +154,7 @@ var ( func checkField( add func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - // TODO: update type and name - types *protoregistry.Types, + extenExtensionTypeResolver ExtensionTypeResolver, ) error { fieldDescriptor, err := field.AsDescriptor() if err != nil { @@ -167,48 +165,6 @@ func checkField( fieldPrettyTypeName: getFieldTypePrettyNameName(fieldDescriptor), addFunc: add, } - // TODO: move to a different func instead of having nested ifs - // if fieldDescriptor.IsExtension() { - // extendedStandardRuleDescriptor := fieldDescriptor.ContainingMessage() - // extendedRuleName := extendedStandardRuleDescriptor.Name() - // if strings.HasPrefix(string(extendedRuleName), "buf.validate.") && strings.HasSuffix(string(extendedRuleName), "Rules") { - // // Just to be extra sure. - // if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleName) != nil { - // if extendedRules := resolveExt[*validate.SharedFieldConstraints](extendedStandardRuleDescriptor.Options(), validate.E_SharedField); extendedRules != nil { - // celEnv, err := celext.DefaultEnv(false) - // if err != nil { - // return err - // } - // celEnv, err = celEnv.Extend( - // append( - // celext.RequiredCELEnvOptions(fieldDescriptor), - // // TODO: this is incorrect - // cel.Variable("this", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), - // // TODO: this might be incorrect - // cel.Variable("rule", celext.ProtoFieldToCELType(fieldDescriptor, false, false)), - // )..., - // ) - // if err != nil { - // return err - // } - // checkCEL( - // celEnv, - // extendedRules.GetCel(), - // fmt.Sprintf("TODO1"), - // fmt.Sprintf("TODO2"), - // fmt.Sprintf("TODO3"), - // func(index int, format string, args ...interface{}) { - // adder.addForPathf( - // []int32{celFieldNumberInFieldConstraints, int32(index)}, - // format, - // args..., - // ) - // }, - // ) - // } - // } - // } - // } constraints := resolver.DefaultResolver{}.ResolveFieldConstraints(fieldDescriptor) return checkConstraintsForField( adder, @@ -217,42 +173,10 @@ func checkField( nil, fieldDescriptor, fieldDescriptor.Cardinality() == protoreflect.Repeated, - // TODO: update type and name - types, + extenExtensionTypeResolver, ) } -// TODO: this function is copied directly from protovalidate-go -// We have 3 options: -// 1. in protovalidate-go, add DefaultResolver.ResolveSharedFieldConstraints -// 2. in protovalidate-go -func resolveExt[C proto.Message]( - options proto.Message, - extType protoreflect.ExtensionType, -) (constraints C) { - num := extType.TypeDescriptor().Number() - var msg proto.Message - - proto.RangeExtensions(options, func(typ protoreflect.ExtensionType, i interface{}) bool { - if num != typ.TypeDescriptor().Number() { - return true - } - msg, _ = i.(proto.Message) - return false - }) - - if msg == nil { - return constraints - } else if m, ok := msg.(C); ok { - return m - } - - constraints, _ = constraints.ProtoReflect().New().Interface().(C) - b, _ := proto.Marshal(msg) - _ = proto.Unmarshal(b, constraints) - return constraints -} - func checkConstraintsForField( adder *adder, fieldConstraints *validate.FieldConstraints, @@ -268,8 +192,7 @@ func checkConstraintsForField( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, expectRepeatedRule bool, - // TODO: update parameter type and name - types *protoregistry.Types, + extenExtensionTypeResolver ExtensionTypeResolver, ) error { if fieldConstraints == nil { return nil @@ -306,10 +229,10 @@ func checkConstraintsForField( typeRulesFieldNumber := int32(typeRulesFieldDescriptor.Number()) // Map and repeated special cases that contain fieldConstraints. if typeRulesFieldNumber == mapRulesFieldNumber { - return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor, types) + return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor, extenExtensionTypeResolver) } if typeRulesFieldNumber == repeatedRulesFieldNumber { - return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor, types) + return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor, extenExtensionTypeResolver) } typesMatch := checkRulesTypeMatchFieldType(adder, fieldDescriptor, typeRulesFieldNumber, expectRepeatedRule) if !typesMatch { @@ -326,7 +249,7 @@ func checkConstraintsForField( // Bool rules only have one constraint, `const`, which does not need validating. case stringRulesFieldNumber: stringRules := fieldConstraints.GetString_() - err := reparseUnrecognized(stringRules.ProtoReflect(), types) + err := reparseUnrecognized(stringRules.ProtoReflect(), extenExtensionTypeResolver) if err != nil { return fmt.Errorf("error reparsing message: %w", err) } @@ -386,32 +309,6 @@ func checkConstraintsForField( return nil } -type fieldExclusiveConstraintsResolver struct { - protovalidate.StandardConstraintResolver - targetField protoreflect.FieldDescriptor -} - -func (r *fieldExclusiveConstraintsResolver) ResolveFieldConstraints(desc protoreflect.FieldDescriptor) *validate.FieldConstraints { - if desc.FullName() != r.targetField.FullName() { - return nil - } - return r.StandardConstraintResolver.ResolveFieldConstraints(desc) -} - -func reparseUnrecognized(reflectMessage protoreflect.Message, types *protoregistry.Types) error { - unknown := reflectMessage.GetUnknown() - if len(unknown) > 0 { - reflectMessage.SetUnknown(nil) - options := proto.UnmarshalOptions{ - Resolver: types, - Merge: true, - } - if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil { - return err - } - } - return nil -} func checkFieldFlags( adder *adder, fieldConstraints *validate.FieldConstraints, @@ -539,8 +436,7 @@ func checkRepeatedRules( repeatedRules *validate.RepeatedRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - // TODO: update parameter type and name - types *protoregistry.Types, + extenExtensionTypeResolver ExtensionTypeResolver, ) error { if !fieldDescriptor.IsList() { baseAdder.addForPathf( @@ -583,7 +479,7 @@ func checkRepeatedRules( ) } itemAdder := baseAdder.cloneWithNewBasePath(repeatedRulesFieldNumber, itemsFieldNumberInRepeatedRules) - return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false, types) + return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false, extenExtensionTypeResolver) } func checkMapRules( @@ -591,8 +487,7 @@ func checkMapRules( mapRules *validate.MapRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - // TODO: update parameter type and name - types *protoregistry.Types, + extenExtensionTypeResolver ExtensionTypeResolver, ) error { if !fieldDescriptor.IsMap() { baseAdder.addForPathf( @@ -624,12 +519,12 @@ func checkMapRules( ) } keyAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, keysFieldNumberInMapRules) - err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false, types) + err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false, extenExtensionTypeResolver) if err != nil { return err } valueAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, valuesFieldNumberInMapRules) - return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false, types) + return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false, extenExtensionTypeResolver) } func checkStringRules(adder *adder, stringRules *validate.StringRules) error { @@ -891,7 +786,7 @@ func checkExampleValues( v, err := protovalidate.New( protovalidate.WithStandardConstraintInterceptor( func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { - return &fieldExclusiveConstraintsResolver{ + return &constraintsResolverForTargetField{ StandardConstraintResolver: res, targetField: fieldDescriptor, } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go new file mode 100644 index 0000000000..06dee11577 --- /dev/null +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -0,0 +1,85 @@ +// Copyright 2020-2024 Buf Technologies, Inc. +// +// 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 buflintvalidate + +import ( + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + "github.com/bufbuild/protovalidate-go" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +type constraintsResolverForTargetField struct { + protovalidate.StandardConstraintResolver + targetField protoreflect.FieldDescriptor +} + +func (r *constraintsResolverForTargetField) ResolveFieldConstraints(desc protoreflect.FieldDescriptor) *validate.FieldConstraints { + if desc.FullName() != r.targetField.FullName() { + return nil + } + return r.StandardConstraintResolver.ResolveFieldConstraints(desc) +} + +// TODO: this function is copied directly from protovalidate-go. +// We have 3 options: +// 1. Go to protovalidate-go and add DefaultResolver.ResolveSharedFieldConstraints. +// 2. Go to protovalidate-go and make this public. +// 3. Leave it as is. +func resolveExt[C proto.Message]( + options proto.Message, + extType protoreflect.ExtensionType, +) (constraints C) { + num := extType.TypeDescriptor().Number() + var msg proto.Message + + proto.RangeExtensions(options, func(typ protoreflect.ExtensionType, i interface{}) bool { + if num != typ.TypeDescriptor().Number() { + return true + } + msg, _ = i.(proto.Message) + return false + }) + + if msg == nil { + return constraints + } else if m, ok := msg.(C); ok { + return m + } + + constraints, _ = constraints.ProtoReflect().New().Interface().(C) + b, _ := proto.Marshal(msg) + _ = proto.Unmarshal(b, constraints) + return constraints +} + +// TODO: this is copied from protovalidate-go, with the difference that types is passed as a parameter. +func reparseUnrecognized( + reflectMessage protoreflect.Message, + extenExtensionTypeResolver ExtensionTypeResolver, +) error { + unknown := reflectMessage.GetUnknown() + if len(unknown) > 0 { + reflectMessage.SetUnknown(nil) + options := proto.UnmarshalOptions{ + Resolver: extenExtensionTypeResolver, + Merge: true, + } + if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil { + return err + } + } + return nil +} From 49452e092e6b35e487f86f00145733893a228637 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 19:52:40 -0400 Subject: [PATCH 09/30] small cleanup --- .../internal/buflintvalidate/field.go | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index d61008cab9..012d282949 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -160,14 +160,13 @@ func checkField( if err != nil { return err } - adder := &adder{ - field: field, - fieldPrettyTypeName: getFieldTypePrettyNameName(fieldDescriptor), - addFunc: add, - } constraints := resolver.DefaultResolver{}.ResolveFieldConstraints(fieldDescriptor) return checkConstraintsForField( - adder, + &adder{ + field: field, + fieldPrettyTypeName: getFieldTypePrettyNameName(fieldDescriptor), + addFunc: add, + }, constraints, fieldDescriptor.ContainingMessage(), nil, @@ -246,35 +245,25 @@ func checkConstraintsForField( } switch typeRulesFieldNumber { case boolRulesFieldNumber: - // Bool rules only have one constraint, `const`, which does not need validating. + // Bool rules only have `const` and does not need validating. case stringRulesFieldNumber: - stringRules := fieldConstraints.GetString_() - err := reparseUnrecognized(stringRules.ProtoReflect(), extenExtensionTypeResolver) - if err != nil { - return fmt.Errorf("error reparsing message: %w", err) - } - if err := checkStringRules(adder, stringRules); err != nil { + if err := checkStringRules(adder, fieldConstraints.GetString_()); err != nil { return err } case bytesRulesFieldNumber: - bytesRules := fieldConstraints.GetBytes() - if err := checkBytesRules(adder, bytesRules); err != nil { + if err := checkBytesRules(adder, fieldConstraints.GetBytes()); err != nil { return err } case enumRulesFieldNumber: - enumRules := fieldConstraints.GetEnum() - checkEnumRules(adder, enumRules) + checkEnumRules(adder, fieldConstraints.GetEnum()) case anyRulesFieldNumber: - // Any does not have example values. checkAnyRules(adder, fieldConstraints.GetAny()) case durationRulesFieldNumber: - durationRules := fieldConstraints.GetDuration() - if err := checkDurationRules(adder, durationRules); err != nil { + if err := checkDurationRules(adder, fieldConstraints.GetDuration()); err != nil { return err } case timestampRulesFieldNumber: - timestampRules := fieldConstraints.GetTimestamp() - if err := checkTimestampRules(adder, timestampRules); err != nil { + if err := checkTimestampRules(adder, fieldConstraints.GetTimestamp()); err != nil { return err } } From e7f0f340c8467b7b8d7e40a89741aeaedc30f89a Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 20:01:51 -0400 Subject: [PATCH 10/30] rearrange for less diff --- .../internal/buflintvalidate/field.go | 56 ++++++++----------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 012d282949..91fd498c36 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -237,36 +237,6 @@ func checkConstraintsForField( if !typesMatch { return nil } - if numberRulesCheckFunc, ok := fieldNumberToCheckNumberRulesFunc[typeRulesFieldNumber]; ok { - numberRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() - if err := numberRulesCheckFunc(adder, typeRulesFieldNumber, numberRulesMessage); err != nil { - return nil - } - } - switch typeRulesFieldNumber { - case boolRulesFieldNumber: - // Bool rules only have `const` and does not need validating. - case stringRulesFieldNumber: - if err := checkStringRules(adder, fieldConstraints.GetString_()); err != nil { - return err - } - case bytesRulesFieldNumber: - if err := checkBytesRules(adder, fieldConstraints.GetBytes()); err != nil { - return err - } - case enumRulesFieldNumber: - checkEnumRules(adder, fieldConstraints.GetEnum()) - case anyRulesFieldNumber: - checkAnyRules(adder, fieldConstraints.GetAny()) - case durationRulesFieldNumber: - if err := checkDurationRules(adder, fieldConstraints.GetDuration()); err != nil { - return err - } - case timestampRulesFieldNumber: - if err := checkTimestampRules(adder, fieldConstraints.GetTimestamp()); err != nil { - return err - } - } typeRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() var exampleValues []protoreflect.Value var exampleFieldNumber int32 @@ -284,7 +254,7 @@ func checkConstraintsForField( return true }) if len(exampleValues) > 0 { - return checkExampleValues( + if err := checkExampleValues( adder, []int32{typeRulesFieldNumber, exampleFieldNumber}, fieldConstraints, @@ -293,7 +263,29 @@ func checkConstraintsForField( parentMapFieldDescriptor, fieldDescriptor, exampleValues, - ) + ); err != nil { + return err + } + } + if numberRulesCheckFunc, ok := fieldNumberToCheckNumberRulesFunc[typeRulesFieldNumber]; ok { + numberRulesMessage := fieldConstraintsMessage.Get(typeRulesFieldDescriptor).Message() + return numberRulesCheckFunc(adder, typeRulesFieldNumber, numberRulesMessage) + } + switch typeRulesFieldNumber { + case boolRulesFieldNumber: + // Bool rules only have `const` and does not need validating. + case stringRulesFieldNumber: + return checkStringRules(adder, fieldConstraints.GetString_()) + case bytesRulesFieldNumber: + return checkBytesRules(adder, fieldConstraints.GetBytes()) + case enumRulesFieldNumber: + checkEnumRules(adder, fieldConstraints.GetEnum()) + case anyRulesFieldNumber: + checkAnyRules(adder, fieldConstraints.GetAny()) + case durationRulesFieldNumber: + return checkDurationRules(adder, fieldConstraints.GetDuration()) + case timestampRulesFieldNumber: + return checkTimestampRules(adder, fieldConstraints.GetTimestamp()) } return nil } From 0bfd6ffa5692e26185cacceb06cf1294b610a19e Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 20:21:23 -0400 Subject: [PATCH 11/30] rename --- .../buflintvalidate/buflintvalidate.go | 4 ++-- .../internal/buflintvalidate/field.go | 20 +++++++++---------- .../internal/buflintvalidate/util.go | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index 03bca543fe..329fe828a4 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -81,7 +81,7 @@ func CheckMessage( func CheckField( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { - return checkField(addAnnotationFunc, field, extenExtensionTypeResolver) + return checkField(addAnnotationFunc, field, extensionTypeResolver) } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 91fd498c36..57d99ce19d 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -154,7 +154,7 @@ var ( func checkField( add func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { fieldDescriptor, err := field.AsDescriptor() if err != nil { @@ -172,7 +172,7 @@ func checkField( nil, fieldDescriptor, fieldDescriptor.Cardinality() == protoreflect.Repeated, - extenExtensionTypeResolver, + extensionTypeResolver, ) } @@ -191,7 +191,7 @@ func checkConstraintsForField( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, expectRepeatedRule bool, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { if fieldConstraints == nil { return nil @@ -228,10 +228,10 @@ func checkConstraintsForField( typeRulesFieldNumber := int32(typeRulesFieldDescriptor.Number()) // Map and repeated special cases that contain fieldConstraints. if typeRulesFieldNumber == mapRulesFieldNumber { - return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor, extenExtensionTypeResolver) + return checkMapRules(adder, fieldConstraints.GetMap(), fieldDescriptor, containingMessageDescriptor, extensionTypeResolver) } if typeRulesFieldNumber == repeatedRulesFieldNumber { - return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor, extenExtensionTypeResolver) + return checkRepeatedRules(adder, fieldConstraints.GetRepeated(), fieldDescriptor, containingMessageDescriptor, extensionTypeResolver) } typesMatch := checkRulesTypeMatchFieldType(adder, fieldDescriptor, typeRulesFieldNumber, expectRepeatedRule) if !typesMatch { @@ -417,7 +417,7 @@ func checkRepeatedRules( repeatedRules *validate.RepeatedRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { if !fieldDescriptor.IsList() { baseAdder.addForPathf( @@ -460,7 +460,7 @@ func checkRepeatedRules( ) } itemAdder := baseAdder.cloneWithNewBasePath(repeatedRulesFieldNumber, itemsFieldNumberInRepeatedRules) - return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false, extenExtensionTypeResolver) + return checkConstraintsForField(itemAdder, repeatedRules.Items, containingMessageDescriptor, nil, fieldDescriptor, false, extensionTypeResolver) } func checkMapRules( @@ -468,7 +468,7 @@ func checkMapRules( mapRules *validate.MapRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { if !fieldDescriptor.IsMap() { baseAdder.addForPathf( @@ -500,12 +500,12 @@ func checkMapRules( ) } keyAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, keysFieldNumberInMapRules) - err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false, extenExtensionTypeResolver) + err := checkConstraintsForField(keyAdder, mapRules.Keys, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapKey(), false, extensionTypeResolver) if err != nil { return err } valueAdder := baseAdder.cloneWithNewBasePath(mapRulesFieldNumber, valuesFieldNumberInMapRules) - return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false, extenExtensionTypeResolver) + return checkConstraintsForField(valueAdder, mapRules.Values, containingMessageDescriptor, fieldDescriptor, fieldDescriptor.MapValue(), false, extensionTypeResolver) } func checkStringRules(adder *adder, stringRules *validate.StringRules) error { diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index 06dee11577..f0ebd1c177 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -68,13 +68,13 @@ func resolveExt[C proto.Message]( // TODO: this is copied from protovalidate-go, with the difference that types is passed as a parameter. func reparseUnrecognized( reflectMessage protoreflect.Message, - extenExtensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver ExtensionTypeResolver, ) error { unknown := reflectMessage.GetUnknown() if len(unknown) > 0 { reflectMessage.SetUnknown(nil) options := proto.UnmarshalOptions{ - Resolver: extenExtensionTypeResolver, + Resolver: extensionTypeResolver, Merge: true, } if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil { From 452d1af765d2ddfeb4768c4197b6e1b334bfe3d0 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 20:53:19 -0400 Subject: [PATCH 12/30] add back reparsing, which was dropped by mistake; cleanup --- .../internal/buflintvalidate/field.go | 131 ++++++++++-------- .../internal/buflintvalidate/util.go | 10 ++ 2 files changed, 80 insertions(+), 61 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 57d99ce19d..0d4937eb50 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -263,6 +263,7 @@ func checkConstraintsForField( parentMapFieldDescriptor, fieldDescriptor, exampleValues, + extensionTypeResolver, ); err != nil { return err } @@ -763,22 +764,10 @@ func checkExampleValues( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, exampleValues []protoreflect.Value, + extensionTypeResolver ExtensionTypeResolver, ) error { - v, err := protovalidate.New( - protovalidate.WithStandardConstraintInterceptor( - func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { - return &constraintsResolverForTargetField{ - StandardConstraintResolver: res, - targetField: fieldDescriptor, - } - }, - ), - ) - if err != nil { - return err - } + reparseUnrecognized(typeRulesMessage, extensionTypeResolver) hasConstraints := len(fieldConstraints.GetCel()) > 0 - // TODO: check if this checks shared rules // TODO: add a test where only shared rules and examples are specified if !hasConstraints { typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { @@ -794,40 +783,73 @@ func checkExampleValues( // No need to check if example values satifisy constraints, because there is none. return nil } + // For each example value, instantiate a message of its containing message's type + // and set the field that we are linting to the example value: + // containingMessage { + // ... + // fieldToLint: exampleValue, + // ... + // } + // and validate this message instance with protovalidate and filter the structured + // errors by field name to determine whether this example value fails rules defined + // on the same field. + v, err := protovalidate.New( + protovalidate.WithStandardConstraintInterceptor( + func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { + // Pass a constraint resolver interceptor so that constraints on other + // fields are not looked at by the validator. + return &constraintsResolverForTargetField{ + StandardConstraintResolver: res, + targetField: fieldDescriptor, + } + }, + ), + ) + if err != nil { + return err + } + // The shape of field path in a protovalidate.Violation depends on the type of the field descriptor. + // TODO: verify that this can be relied on. + violationFilterFunc := func(violation *validate.Violation) bool { + return violation.GetFieldPath() == string(fieldDescriptor.Name()) + } + switch { + case fieldDescriptor.IsList(): + // Field path looks like repeate_field[10] + violationFilterFunc = func(violation *validate.Violation) bool { + prefix := fieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.GetFieldPath() + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) + } + case parentMapFieldDescriptor != nil && fieldDescriptor.Name() == "key": + // Field path looks like map_field["the key value that failed"] and ForKey is set to true. + violationFilterFunc = func(violation *validate.Violation) bool { + prefix := parentMapFieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.GetFieldPath() + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && violation.GetForKey() + } + case parentMapFieldDescriptor != nil && fieldDescriptor.Name() == "value": + // Field path looks like map_field["the key value that failed"], but ForKey is set to false. + violationFilterFunc = func(violation *validate.Violation) bool { + prefix := parentMapFieldDescriptor.Name() + "[" + suffix := "]" + fieldPath := violation.GetFieldPath() + return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.GetForKey() + } + } + // TODO: test it for repeated min item for exampleValueIndex, exampleValue := range exampleValues { - // For each example value, instantiate a message of its containing message's type - // and set the field that we are linting to the example value: - // containingMessage { - // ... - // fieldToLint: exampleValue, - // ... - // } - // and validate this message instance with protovalidate and filter the structured - // errors by field name to determine whether this example value fails rules defined - // on the same field. - // - // Note: there might be cel expressions defined on the message level that the example - // value would cause to fail, but we have no way of knowing that the example value is - // the reason, therefore it's ok to only filter for field level failures. messageToValidate := dynamicpb.NewMessage(containingMessageDescriptor) - // TODO: test it for repeated min item - // This is needed because the shape of field path in a protovalidate.Violation depends on - // the type of the field descriptor. - matchViolationFunc := func(violation *validate.Violation) bool { - return violation.GetFieldPath() == string(fieldDescriptor.Name()) - } - if fieldDescriptor.IsList() { // this is linting for items (they have the same descriptor) // TODO: update this comment so that it makes sense + switch { + case fieldDescriptor.IsList(): list := messageToValidate.NewField(fieldDescriptor).List() list.Append(exampleValue) messageToValidate.Set(fieldDescriptor, protoreflect.ValueOfList(list)) - matchViolationFunc = func(violation *validate.Violation) bool { - prefix := fieldDescriptor.Name() + "[" - suffix := "]" - fieldPath := violation.GetFieldPath() - return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) - } - } else if parentMapFieldDescriptor != nil { + case parentMapFieldDescriptor != nil: mapEntryMessageDescriptor := fieldDescriptor.ContainingMessage() + // We are being very defensive, because a type mismatch may cause a panic in protoreflect. if !mapEntryMessageDescriptor.IsMapEntry() { return syserror.Newf("containing message %q is not a map", mapEntryMessageDescriptor.Name()) } @@ -847,28 +869,16 @@ func checkExampleValues( exampleValue.MapKey(), dynamicpb.NewMessage(parentMapFieldDescriptor.Message()).NewField(parentMapFieldDescriptor.MapValue()), ) - matchViolationFunc = func(violation *validate.Violation) bool { - prefix := parentMapFieldDescriptor.Name() + "[" - suffix := "]" - fieldPath := violation.GetFieldPath() - return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && violation.GetForKey() - } case "value": mapEntry.Set( dynamicpb.NewMessage(parentMapFieldDescriptor.Message()).NewField(parentMapFieldDescriptor.MapKey()).MapKey(), exampleValue, ) - matchViolationFunc = func(violation *validate.Violation) bool { - prefix := parentMapFieldDescriptor.Name() + "[" - suffix := "]" - fieldPath := violation.GetFieldPath() - return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.GetForKey() - } default: return syserror.Newf("expected key or value as sythetic field name for map entry's field name, got %q", fieldDescriptor.Name()) } messageToValidate.Set(parentMapFieldDescriptor, protoreflect.ValueOfMap(mapEntry)) - } else { + default: messageToValidate.Set(fieldDescriptor, exampleValue) } err := v.Validate(messageToValidate) @@ -877,14 +887,14 @@ func checkExampleValues( } var compilationErr *protovalidate.CompilationError if errors.As(err, &compilationErr) { - // Expression failing to compile meaning some custom shared rules are invalid, - // which is checked in this rule (PROTOVALIDATE), but not in this code block. + // Expression failing to compile meaning some CEL expressions are invalid, + // which is checked in this rule (PROTOVALIDATE), but not by this function. break } validationErr := &protovalidate.ValidationError{} if errors.As(err, &validationErr) { for _, violation := range validationErr.Violations { - if matchViolationFunc(violation) { + if violationFilterFunc(violation) { adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), `"%v" is an example value but does not satisfy rule %q: %s`, exampleValue.Interface(), violation.GetConstraintId(), violation.GetMessage()) } } @@ -892,14 +902,13 @@ func checkExampleValues( } runtimeError := &protovalidate.RuntimeError{} if errors.As(err, &runtimeError) { - // TODO: what to do here? - // return an error? + // TODO: what to do here? return an error? + // TODO: see if i can test this with zero division adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), "example fail at runtime: %s", runtimeError.Error()) continue } return fmt.Errorf("unexpected error from protovalidate: %s", err.Error()) } - return nil } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index f0ebd1c177..d750e5bd80 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -21,6 +21,8 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" ) +// This implements protovalidate.StandardConstraintResolver, see checkExampleValues' comment +// for why this is needed. type constraintsResolverForTargetField struct { protovalidate.StandardConstraintResolver targetField protoreflect.FieldDescriptor @@ -33,6 +35,14 @@ func (r *constraintsResolverForTargetField) ResolveFieldConstraints(desc protore return r.StandardConstraintResolver.ResolveFieldConstraints(desc) } +func (r *constraintsResolverForTargetField) ResolveMessageConstraints(desc protoreflect.MessageDescriptor) *validate.MessageConstraints { + return nil +} + +func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protoreflect.OneofDescriptor) *validate.OneofConstraints { + return nil +} + // TODO: this function is copied directly from protovalidate-go. // We have 3 options: // 1. Go to protovalidate-go and add DefaultResolver.ResolveSharedFieldConstraints. From b72616262832dd54466ee9ebecf26a6e4e53e2d7 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 20:57:15 -0400 Subject: [PATCH 13/30] add commit info --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e592083a7d..c66a3cb3de 100644 --- a/go.mod +++ b/go.mod @@ -122,7 +122,7 @@ require ( ) // TODO: remove -replace github.com/bufbuild/protovalidate-go => ../pvgojohn +replace github.com/bufbuild/protovalidate-go => ../pvgojohn // commit e474f9b456a57181c0c8ad6785be9c9c2ab0a0be // TODO: remove -replace buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go => ../pv-gen +replace buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go => ../pv-gen // commit 4fd7a369f04577fc71d8ff33ed733105aae84894 From 74c86d2cae07aa9b455a31151f66620056f12c46 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Thu, 12 Sep 2024 20:58:51 -0400 Subject: [PATCH 14/30] comment --- .../bufcheckserver/internal/bufcheckserverhandle/lint.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index 081faba8e8..d2fbae0716 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -942,8 +942,7 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( responseWriter bufcheckserverutil.ResponseWriter, request bufcheckserverutil.Request, ) error { - // TODO: refactor so that this add func is no longer needed, but for now, - // keeping as is + // TODO: refactor so that add func is no longer needed addAnnotationFunc := func( _ bufprotosource.Descriptor, location bufprotosource.Location, From c16b0bd3ddc54ad755fcae8d44943e7f30abd441 Mon Sep 17 00:00:00 2001 From: Oliver Sun Date: Fri, 13 Sep 2024 12:40:21 -0400 Subject: [PATCH 15/30] update protovalidate files in test --- private/buf/cmd/buf/buf_test.go | 9 +- .../internal/bufcheckserverhandle/lint.go | 2 + .../buflintvalidate/buflintvalidate.go | 2 +- .../buf/validate/expression.proto | 92 -- .../buf/validate/priv/private.proto | 41 - .../protovalidate/buf/validate/validate.proto | 1367 +++++++++++++---- 6 files changed, 1078 insertions(+), 435 deletions(-) delete mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto delete mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto diff --git a/private/buf/cmd/buf/buf_test.go b/private/buf/cmd/buf/buf_test.go index 5af6d05c96..8a217af72d 100644 --- a/private/buf/cmd/buf/buf_test.go +++ b/private/buf/cmd/buf/buf_test.go @@ -2907,8 +2907,7 @@ testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) +testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), @@ -2934,8 +2933,7 @@ testdata/check_plugins/current/proto/api/v1/service.proto:17:14:RPC request type testdata/check_plugins/current/proto/api/v1/service.proto:17:42:RPC response type "GetFooTestResponse" should be named "GetFooResponse" or "FooServiceTestGetFooResponse". testdata/check_plugins/current/proto/api/v1/service.proto:26:1:"ListFooResponse" is a pagination response without a page token field named "page_token" (buf-plugin-rpc-ext) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) +testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), @@ -2985,8 +2983,7 @@ testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.pr filepath.FromSlash(` testdata/check_plugins/current/proto/api/v1/service.proto:11:1:Service name "api.v1.FooServiceMock" has banned suffix "Mock". (buf-plugin-suffix) testdata/check_plugins/current/proto/common/v1alpha1/messages.proto:16:5:field "common.v1alpha1.Four.FourTwo.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/expression.proto:42:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) -testdata/check_plugins/current/vendor/protovalidate/buf/validate/priv/private.proto:38:3:field "buf.validate.priv.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) +testdata/check_plugins/current/vendor/protovalidate/buf/validate/validate.proto:94:3:field "buf.validate.Constraint.id" does not have rule (buf.validate.field).string.tuuid set (buf-plugin-protovalidate-ext) `), "lint", filepath.Join("testdata", "check_plugins", "current"), diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index d2fbae0716..e2f9bc9159 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -966,6 +966,8 @@ var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( // Regardless whether its file is an import, we want to add a shared rule // extension to the types registry, as long as the extension's cel // expressions compile. + // TODO: add a test where a shared rule is defined in an import and a + // non-import uses this rule. // TODO: pass a nop addFunc the file is an import, if err := bufprotosource.ForEachMessage( func(message bufprotosource.Message) error { diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index 329fe828a4..e1ee8a9d08 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -74,7 +74,7 @@ func CheckMessage( // CheckField validates that all rules on the field are valid, and any CEL expressions compile. // // For a set of rules to be valid, it must -// 1. permit _some_ value +// 1. permit _some_ value and all example values, if any // 2. have a type compatible with the field it validates. // // addAnnotationFunc adds an annotation with the descriptor and location for check results. diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto deleted file mode 100644 index 72ce36dcd8..0000000000 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2023 Buf Technologies, Inc. -// -// 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. - -syntax = "proto3"; - -package buf.validate; - -option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; -option java_multiple_files = true; -option java_outer_classname = "ExpressionProto"; -option java_package = "build.buf.validate"; - -// `Constraint` represents a validation rule written in the Common Expression -// Language (CEL) syntax. Each Constraint includes a unique identifier, an -// optional error message, and the CEL expression to evaluate. For more -// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). -// -// ```proto -// message Foo { -// option (buf.validate.message).cel = { -// id: "foo.bar" -// message: "bar must be greater than 0" -// expression: "this.bar > 0" -// }; -// int32 bar = 1; -// } -// ``` -message Constraint { - // `id` is a string that serves as a machine-readable name for this Constraint. - // It should be unique within its scope, which could be either a message or a field. - string id = 1; - - // `message` is an optional field that provides a human-readable error message - // for this Constraint when the CEL expression evaluates to false. If a - // non-empty message is provided, any strings resulting from the CEL - // expression evaluation are ignored. - string message = 2; - - // `expression` is the actual CEL expression that will be evaluated for - // validation. This string must resolve to either a boolean or a string - // value. If the expression evaluates to false or a non-empty string, the - // validation is considered failed, and the message is rejected. - string expression = 3; -} - -// `Violations` is a collection of `Violation` messages. This message type is returned by -// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. -// Each individual violation is represented by a `Violation` message. -message Violations { - // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. - repeated Violation violations = 1; -} - -// `Violation` represents a single instance where a validation rule, expressed -// as a `Constraint`, was not met. It provides information about the field that -// caused the violation, the specific constraint that wasn't fulfilled, and a -// human-readable error message. -// -// ```json -// { -// "fieldPath": "bar", -// "constraintId": "foo.bar", -// "message": "bar must be greater than 0" -// } -// ``` -message Violation { - // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. - // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. - string field_path = 1; - - // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. - // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. - string constraint_id = 2; - - // `message` is a human-readable error message that describes the nature of the violation. - // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. - string message = 3; - - // `for_key` indicates whether the violation was caused by a map key, rather than a value. - bool for_key = 4; -} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto deleted file mode 100644 index ddaf938a61..0000000000 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 Buf Technologies, Inc. -// -// 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. - -syntax = "proto3"; - -package buf.validate.priv; - -import "google/protobuf/descriptor.proto"; - -option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/priv"; -option java_multiple_files = true; -option java_outer_classname = "PrivateProto"; -option java_package = "build.buf.validate.priv"; - -extend google.protobuf.FieldOptions { - // Do not use. Internal to protovalidate library - optional FieldConstraints field = 1160; -} - -// Do not use. Internal to protovalidate library -message FieldConstraints { - repeated Constraint cel = 1; -} - -// Do not use. Internal to protovalidate library -message Constraint { - string id = 1; - string message = 2; - string expression = 3; -} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto index fcc8cac448..133221be2d 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto @@ -12,12 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package buf.validate; -import "buf/validate/expression.proto"; -import "buf/validate/priv/private.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -55,6 +53,94 @@ extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldConstraints field = 1159; + + // Specifies shared rules. When extending a standard constraint message, this + // adds additional CEL expressions that apply when the extension is used. + // + // ```proto + // extend buf.validate.Int32Rules { + // bool is_zero [(buf.validate.shared_field).cel = { + // id: "int32.is_zero", + // message: "value must be zero", + // expression: "rule ? this == 0 : ''", + // }]; + // } + // + // message Foo { + // int32 reserved = 1 [(buf.validate.field).int32.(is_zero) = true]; + // } + // ``` + optional SharedFieldConstraints shared_field = 1160; +} + +// `Constraint` represents a validation rule written in the Common Expression +// Language (CEL) syntax. Each Constraint includes a unique identifier, an +// optional error message, and the CEL expression to evaluate. For more +// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). +// +// ```proto +// message Foo { +// option (buf.validate.message).cel = { +// id: "foo.bar" +// message: "bar must be greater than 0" +// expression: "this.bar > 0" +// }; +// int32 bar = 1; +// } +// ``` +message Constraint { + // `id` is a string that serves as a machine-readable name for this Constraint. + // It should be unique within its scope, which could be either a message or a field. + optional string id = 1; + + // `message` is an optional field that provides a human-readable error message + // for this Constraint when the CEL expression evaluates to false. If a + // non-empty message is provided, any strings resulting from the CEL + // expression evaluation are ignored. + optional string message = 2; + + // `expression` is the actual CEL expression that will be evaluated for + // validation. This string must resolve to either a boolean or a string + // value. If the expression evaluates to false or a non-empty string, the + // validation is considered failed, and the message is rejected. + optional string expression = 3; +} + +// `Violations` is a collection of `Violation` messages. This message type is returned by +// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. +// Each individual violation is represented by a `Violation` message. +message Violations { + // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. + repeated Violation violations = 1; +} + +// `Violation` represents a single instance where a validation rule, expressed +// as a `Constraint`, was not met. It provides information about the field that +// caused the violation, the specific constraint that wasn't fulfilled, and a +// human-readable error message. +// +// ```json +// { +// "fieldPath": "bar", +// "constraintId": "foo.bar", +// "message": "bar must be greater than 0" +// } +// ``` +message Violation { + // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. + // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. + optional string field_path = 1; + + // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. + // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. + optional string constraint_id = 2; + + // `message` is a human-readable error message that describes the nature of the violation. + // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. + optional string message = 3; + + // `for_key` indicates whether the violation was caused by a map key, rather than a value. + optional bool for_key = 4; } // MessageConstraints represents validation rules that are applied to the entire message. @@ -148,7 +234,7 @@ message FieldConstraints { // optional MyOtherMessage value = 1 [(buf.validate.field).required = true]; // } // ``` - bool required = 25; + optional bool required = 25; // Skip validation on the field if its value matches the specified criteria. // See Ignore enum for details. // @@ -162,7 +248,7 @@ message FieldConstraints { // ]; // } // ``` - Ignore ignore = 27; + optional Ignore ignore = 27; oneof type { // Scalar Field Types @@ -194,9 +280,29 @@ message FieldConstraints { } // DEPRECATED: use ignore=IGNORE_ALWAYS instead. TODO: remove this field pre-v1. - bool skipped = 24 [deprecated = true]; + optional bool skipped = 24 [deprecated = true]; // DEPRECATED: use ignore=IGNORE_IF_UNPOPULATED instead. TODO: remove this field pre-v1. - bool ignore_empty = 26 [deprecated = true]; + optional bool ignore_empty = 26 [deprecated = true]; +} + +// SharedFieldConstraints are custom constraints that can be re-used with +// multiple fields. +message SharedFieldConstraints { + // `cel` is a repeated field used to represent a textual expression + // in the Common Expression Language (CEL) syntax. For more information on + // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). + // + // ```proto + // message MyMessage { + // // The field `value` must be greater than 42. + // optional int32 value = 1 [(buf.validate.shared_field).cel = { + // id: "my_message.value", + // message: "value must be greater than 42", + // expression: "this > 42", + // }]; + // } + // ``` + repeated Constraint cel = 1; } // Specifies how FieldConstraints.ignore behaves. See the documentation for @@ -365,7 +471,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.const = 42.0]; // } // ``` - optional float const = 1 [(priv.field).cel = { + optional float const = 1 [(shared_field).cel = { id: "float.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -381,7 +487,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lt = 10.0]; // } // ``` - float lt = 2 [(priv.field).cel = { + float lt = 2 [(shared_field).cel = { id: "float.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -398,7 +504,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lte = 10.0]; // } // ``` - float lte = 3 [(priv.field).cel = { + float lte = 3 [(shared_field).cel = { id: "float.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -426,31 +532,31 @@ message FloatRules { // } // ``` float gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "float.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -477,31 +583,31 @@ message FloatRules { // } // ``` float gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "float.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "float.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -520,7 +626,7 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float in = 6 [(priv.field).cel = { + repeated float in = 6 [(shared_field).cel = { id: "float.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -535,17 +641,52 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float not_in = 7 [(priv.field).cel = { + repeated float not_in = 7 [(shared_field).cel = { id: "float.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - bool finite = 8 [(priv.field).cel = { + optional bool finite = 8 [(shared_field).cel = { id: "float.finite" expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFloat { + // float value = 1 [ + // (buf.validate.field).float.example = 1.0, + // (buf.validate.field).float.example = "Infinity" + // ]; + // } + // ``` + repeated float example = 9 [(shared_field).cel = { + id: "float.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // DoubleRules describes the constraints applied to `double` values. These @@ -560,7 +701,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.const = 42.0]; // } // ``` - optional double const = 1 [(priv.field).cel = { + optional double const = 1 [(shared_field).cel = { id: "double.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -575,7 +716,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lt = 10.0]; // } // ``` - double lt = 2 [(priv.field).cel = { + double lt = 2 [(shared_field).cel = { id: "double.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -592,7 +733,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lte = 10.0]; // } // ``` - double lte = 3 [(priv.field).cel = { + double lte = 3 [(shared_field).cel = { id: "double.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -619,31 +760,31 @@ message DoubleRules { // } // ``` double gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "double.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -670,31 +811,31 @@ message DoubleRules { // } // ``` double gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "double.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "double.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -712,7 +853,7 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double in = 6 [(priv.field).cel = { + repeated double in = 6 [(shared_field).cel = { id: "double.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -727,17 +868,52 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double not_in = 7 [(priv.field).cel = { + repeated double not_in = 7 [(shared_field).cel = { id: "double.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - bool finite = 8 [(priv.field).cel = { + optional bool finite = 8 [(shared_field).cel = { id: "double.finite" expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDouble { + // double value = 1 [ + // (buf.validate.field).double.example = 1.0, + // (buf.validate.field).double.example = "Infinity" + // ]; + // } + // ``` + repeated double example = 9 [(shared_field).cel = { + id: "double.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // Int32Rules describes the constraints applied to `int32` values. These @@ -752,7 +928,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.const = 42]; // } // ``` - optional int32 const = 1 [(priv.field).cel = { + optional int32 const = 1 [(shared_field).cel = { id: "int32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -767,7 +943,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lt = 10]; // } // ``` - int32 lt = 2 [(priv.field).cel = { + int32 lt = 2 [(shared_field).cel = { id: "int32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -784,7 +960,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lte = 10]; // } // ``` - int32 lte = 3 [(priv.field).cel = { + int32 lte = 3 [(shared_field).cel = { id: "int32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -811,31 +987,31 @@ message Int32Rules { // } // ``` int32 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "int32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -862,31 +1038,31 @@ message Int32Rules { // } // ``` int32 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "int32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -905,7 +1081,7 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { in: [1, 2, 3] }; // } // ``` - repeated int32 in = 6 [(priv.field).cel = { + repeated int32 in = 6 [(shared_field).cel = { id: "int32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -920,10 +1096,45 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { not_in: [1, 2, 3] }; // } // ``` - repeated int32 not_in = 7 [(priv.field).cel = { + repeated int32 not_in = 7 [(shared_field).cel = { id: "int32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt32 { + // int32 value = 1 [ + // (buf.validate.field).int32.example = 1, + // (buf.validate.field).int32.example = -10 + // ]; + // } + // ``` + repeated int32 example = 8 [(shared_field).cel = { + id: "int32.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // Int64Rules describes the constraints applied to `int64` values. These @@ -938,7 +1149,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.const = 42]; // } // ``` - optional int64 const = 1 [(priv.field).cel = { + optional int64 const = 1 [(shared_field).cel = { id: "int64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -953,7 +1164,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lt = 10]; // } // ``` - int64 lt = 2 [(priv.field).cel = { + int64 lt = 2 [(shared_field).cel = { id: "int64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -970,7 +1181,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lte = 10]; // } // ``` - int64 lte = 3 [(priv.field).cel = { + int64 lte = 3 [(shared_field).cel = { id: "int64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -997,31 +1208,31 @@ message Int64Rules { // } // ``` int64 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "int64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1048,31 +1259,31 @@ message Int64Rules { // } // ``` int64 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "int64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "int64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1091,7 +1302,7 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { in: [1, 2, 3] }; // } // ``` - repeated int64 in = 6 [(priv.field).cel = { + repeated int64 in = 6 [(shared_field).cel = { id: "int64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1106,10 +1317,45 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { not_in: [1, 2, 3] }; // } // ``` - repeated int64 not_in = 7 [(priv.field).cel = { + repeated int64 not_in = 7 [(shared_field).cel = { id: "int64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt64 { + // int64 value = 1 [ + // (buf.validate.field).int64.example = 1, + // (buf.validate.field).int64.example = -10 + // ]; + // } + // ``` + repeated int64 example = 9 [(shared_field).cel = { + id: "int64.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // UInt32Rules describes the constraints applied to `uint32` values. These @@ -1124,7 +1370,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.const = 42]; // } // ``` - optional uint32 const = 1 [(priv.field).cel = { + optional uint32 const = 1 [(shared_field).cel = { id: "uint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1139,7 +1385,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lt = 10]; // } // ``` - uint32 lt = 2 [(priv.field).cel = { + uint32 lt = 2 [(shared_field).cel = { id: "uint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1156,7 +1402,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lte = 10]; // } // ``` - uint32 lte = 3 [(priv.field).cel = { + uint32 lte = 3 [(shared_field).cel = { id: "uint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1183,31 +1429,31 @@ message UInt32Rules { // } // ``` uint32 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1234,31 +1480,31 @@ message UInt32Rules { // } // ``` uint32 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1277,7 +1523,7 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { in: [1, 2, 3] }; // } // ``` - repeated uint32 in = 6 [(priv.field).cel = { + repeated uint32 in = 6 [(shared_field).cel = { id: "uint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1292,10 +1538,45 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint32 not_in = 7 [(priv.field).cel = { + repeated uint32 not_in = 7 [(shared_field).cel = { id: "uint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt32 { + // uint32 value = 1 [ + // (buf.validate.field).uint32.example = 1, + // (buf.validate.field).uint32.example = 10 + // ]; + // } + // ``` + repeated uint32 example = 8 [(shared_field).cel = { + id: "uint32.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // UInt64Rules describes the constraints applied to `uint64` values. These @@ -1310,7 +1591,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.const = 42]; // } // ``` - optional uint64 const = 1 [(priv.field).cel = { + optional uint64 const = 1 [(shared_field).cel = { id: "uint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1325,7 +1606,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lt = 10]; // } // ``` - uint64 lt = 2 [(priv.field).cel = { + uint64 lt = 2 [(shared_field).cel = { id: "uint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1342,7 +1623,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lte = 10]; // } // ``` - uint64 lte = 3 [(priv.field).cel = { + uint64 lte = 3 [(shared_field).cel = { id: "uint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1369,31 +1650,31 @@ message UInt64Rules { // } // ``` uint64 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1420,31 +1701,31 @@ message UInt64Rules { // } // ``` uint64 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "uint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1462,7 +1743,7 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { in: [1, 2, 3] }; // } // ``` - repeated uint64 in = 6 [(priv.field).cel = { + repeated uint64 in = 6 [(shared_field).cel = { id: "uint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1477,10 +1758,45 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint64 not_in = 7 [(priv.field).cel = { + repeated uint64 not_in = 7 [(shared_field).cel = { id: "uint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt64 { + // uint64 value = 1 [ + // (buf.validate.field).uint64.example = 1, + // (buf.validate.field).uint64.example = -10 + // ]; + // } + // ``` + repeated uint64 example = 8 [(shared_field).cel = { + id: "uint64.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // SInt32Rules describes the constraints applied to `sint32` values. @@ -1494,7 +1810,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.const = 42]; // } // ``` - optional sint32 const = 1 [(priv.field).cel = { + optional sint32 const = 1 [(shared_field).cel = { id: "sint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1509,7 +1825,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lt = 10]; // } // ``` - sint32 lt = 2 [(priv.field).cel = { + sint32 lt = 2 [(shared_field).cel = { id: "sint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1526,7 +1842,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lte = 10]; // } // ``` - sint32 lte = 3 [(priv.field).cel = { + sint32 lte = 3 [(shared_field).cel = { id: "sint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1553,31 +1869,31 @@ message SInt32Rules { // } // ``` sint32 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1604,31 +1920,31 @@ message SInt32Rules { // } // ``` sint32 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1647,7 +1963,7 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { in: [1, 2, 3] }; // } // ``` - repeated sint32 in = 6 [(priv.field).cel = { + repeated sint32 in = 6 [(shared_field).cel = { id: "sint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1662,10 +1978,45 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint32 not_in = 7 [(priv.field).cel = { + repeated sint32 not_in = 7 [(shared_field).cel = { id: "sint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt32 { + // sint32 value = 1 [ + // (buf.validate.field).sint32.example = 1, + // (buf.validate.field).sint32.example = -10 + // ]; + // } + // ``` + repeated sint32 example = 8 [(shared_field).cel = { + id: "sint32.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // SInt64Rules describes the constraints applied to `sint64` values. @@ -1679,7 +2030,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.const = 42]; // } // ``` - optional sint64 const = 1 [(priv.field).cel = { + optional sint64 const = 1 [(shared_field).cel = { id: "sint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1694,7 +2045,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lt = 10]; // } // ``` - sint64 lt = 2 [(priv.field).cel = { + sint64 lt = 2 [(shared_field).cel = { id: "sint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1711,7 +2062,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lte = 10]; // } // ``` - sint64 lte = 3 [(priv.field).cel = { + sint64 lte = 3 [(shared_field).cel = { id: "sint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1738,31 +2089,31 @@ message SInt64Rules { // } // ``` sint64 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1789,31 +2140,31 @@ message SInt64Rules { // } // ``` sint64 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1832,7 +2183,7 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { in: [1, 2, 3] }; // } // ``` - repeated sint64 in = 6 [(priv.field).cel = { + repeated sint64 in = 6 [(shared_field).cel = { id: "sint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1847,10 +2198,45 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint64 not_in = 7 [(priv.field).cel = { + repeated sint64 not_in = 7 [(shared_field).cel = { id: "sint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt64 { + // sint64 value = 1 [ + // (buf.validate.field).sint64.example = 1, + // (buf.validate.field).sint64.example = -10 + // ]; + // } + // ``` + repeated sint64 example = 8 [(shared_field).cel = { + id: "sint64.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // Fixed32Rules describes the constraints applied to `fixed32` values. @@ -1864,7 +2250,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.const = 42]; // } // ``` - optional fixed32 const = 1 [(priv.field).cel = { + optional fixed32 const = 1 [(shared_field).cel = { id: "fixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1879,7 +2265,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lt = 10]; // } // ``` - fixed32 lt = 2 [(priv.field).cel = { + fixed32 lt = 2 [(shared_field).cel = { id: "fixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1896,7 +2282,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lte = 10]; // } // ``` - fixed32 lte = 3 [(priv.field).cel = { + fixed32 lte = 3 [(shared_field).cel = { id: "fixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1923,31 +2309,31 @@ message Fixed32Rules { // } // ``` fixed32 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1974,31 +2360,31 @@ message Fixed32Rules { // } // ``` fixed32 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2017,7 +2403,7 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { in: [1, 2, 3] }; // } // ``` - repeated fixed32 in = 6 [(priv.field).cel = { + repeated fixed32 in = 6 [(shared_field).cel = { id: "fixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2032,10 +2418,45 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed32 not_in = 7 [(priv.field).cel = { + repeated fixed32 not_in = 7 [(shared_field).cel = { id: "fixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed32 { + // fixed32 value = 1 [ + // (buf.validate.field).fixed32.example = 1, + // (buf.validate.field).fixed32.example = 2 + // ]; + // } + // ``` + repeated fixed32 example = 8 [(shared_field).cel = { + id: "fixed32.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // Fixed64Rules describes the constraints applied to `fixed64` values. @@ -2049,7 +2470,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.const = 42]; // } // ``` - optional fixed64 const = 1 [(priv.field).cel = { + optional fixed64 const = 1 [(shared_field).cel = { id: "fixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2064,7 +2485,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lt = 10]; // } // ``` - fixed64 lt = 2 [(priv.field).cel = { + fixed64 lt = 2 [(shared_field).cel = { id: "fixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2081,7 +2502,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lte = 10]; // } // ``` - fixed64 lte = 3 [(priv.field).cel = { + fixed64 lte = 3 [(shared_field).cel = { id: "fixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2108,31 +2529,31 @@ message Fixed64Rules { // } // ``` fixed64 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2159,31 +2580,31 @@ message Fixed64Rules { // } // ``` fixed64 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "fixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2202,7 +2623,7 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { in: [1, 2, 3] }; // } // ``` - repeated fixed64 in = 6 [(priv.field).cel = { + repeated fixed64 in = 6 [(shared_field).cel = { id: "fixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2217,10 +2638,45 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed64 not_in = 7 [(priv.field).cel = { + repeated fixed64 not_in = 7 [(shared_field).cel = { id: "fixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed64 { + // fixed64 value = 1 [ + // (buf.validate.field).fixed64.example = 1, + // (buf.validate.field).fixed64.example = 2 + // ]; + // } + // ``` + repeated fixed64 example = 8 [(shared_field).cel = { + id: "fixed64.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // SFixed32Rules describes the constraints applied to `fixed32` values. @@ -2234,7 +2690,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.const = 42]; // } // ``` - optional sfixed32 const = 1 [(priv.field).cel = { + optional sfixed32 const = 1 [(shared_field).cel = { id: "sfixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2249,7 +2705,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lt = 10]; // } // ``` - sfixed32 lt = 2 [(priv.field).cel = { + sfixed32 lt = 2 [(shared_field).cel = { id: "sfixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2266,7 +2722,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lte = 10]; // } // ``` - sfixed32 lte = 3 [(priv.field).cel = { + sfixed32 lte = 3 [(shared_field).cel = { id: "sfixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2293,31 +2749,31 @@ message SFixed32Rules { // } // ``` sfixed32 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2344,31 +2800,31 @@ message SFixed32Rules { // } // ``` sfixed32 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2387,7 +2843,7 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed32 in = 6 [(priv.field).cel = { + repeated sfixed32 in = 6 [(shared_field).cel = { id: "sfixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2402,10 +2858,45 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed32 not_in = 7 [(priv.field).cel = { + repeated sfixed32 not_in = 7 [(shared_field).cel = { id: "sfixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed32 { + // sfixed32 value = 1 [ + // (buf.validate.field).sfixed32.example = 1, + // (buf.validate.field).sfixed32.example = 2 + // ]; + // } + // ``` + repeated sfixed32 example = 8 [(shared_field).cel = { + id: "sfixed32.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // SFixed64Rules describes the constraints applied to `fixed64` values. @@ -2419,7 +2910,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.const = 42]; // } // ``` - optional sfixed64 const = 1 [(priv.field).cel = { + optional sfixed64 const = 1 [(shared_field).cel = { id: "sfixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2434,7 +2925,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lt = 10]; // } // ``` - sfixed64 lt = 2 [(priv.field).cel = { + sfixed64 lt = 2 [(shared_field).cel = { id: "sfixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2451,7 +2942,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lte = 10]; // } // ``` - sfixed64 lte = 3 [(priv.field).cel = { + sfixed64 lte = 3 [(shared_field).cel = { id: "sfixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2478,31 +2969,31 @@ message SFixed64Rules { // } // ``` sfixed64 gt = 4 [ - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2529,31 +3020,31 @@ message SFixed64Rules { // } // ``` sfixed64 gte = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "sfixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2572,7 +3063,7 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed64 in = 6 [(priv.field).cel = { + repeated sfixed64 in = 6 [(shared_field).cel = { id: "sfixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2587,10 +3078,45 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed64 not_in = 7 [(priv.field).cel = { + repeated sfixed64 not_in = 7 [(shared_field).cel = { id: "sfixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed64 { + // sfixed64 value = 1 [ + // (buf.validate.field).sfixed64.example = 1, + // (buf.validate.field).sfixed64.example = 2 + // ]; + // } + // ``` + repeated sfixed64 example = 8 [(shared_field).cel = { + id: "sfixed64.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // BoolRules describes the constraints applied to `bool` values. These rules @@ -2605,10 +3131,45 @@ message BoolRules { // bool value = 1 [(buf.validate.field).bool.const = true]; // } // ``` - optional bool const = 1 [(priv.field).cel = { + optional bool const = 1 [(shared_field).cel = { id: "bool.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBool { + // bool value = 1 [ + // (buf.validate.field).bool.example = 1, + // (buf.validate.field).bool.example = 2 + // ]; + // } + // ``` + repeated bool example = 2 [(shared_field).cel = { + id: "bool.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // StringRules describes the constraints applied to `string` values These @@ -2623,7 +3184,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.const = "hello"]; // } // ``` - optional string const = 1 [(priv.field).cel = { + optional string const = 1 [(shared_field).cel = { id: "string.const" expression: "this != rules.const ? 'value must equal `%s`'.format([rules.const]) : ''" }]; @@ -2639,7 +3200,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len = 5]; // } // ``` - optional uint64 len = 19 [(priv.field).cel = { + optional uint64 len = 19 [(shared_field).cel = { id: "string.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s characters'.format([rules.len]) : ''" }]; @@ -2655,7 +3216,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.min_len = 3]; // } // ``` - optional uint64 min_len = 2 [(priv.field).cel = { + optional uint64 min_len = 2 [(shared_field).cel = { id: "string.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s characters'.format([rules.min_len]) : ''" }]; @@ -2671,7 +3232,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_len = 10]; // } // ``` - optional uint64 max_len = 3 [(priv.field).cel = { + optional uint64 max_len = 3 [(shared_field).cel = { id: "string.max_len" expression: "uint(this.size()) > rules.max_len ? 'value length must be at most %s characters'.format([rules.max_len]) : ''" }]; @@ -2686,7 +3247,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len_bytes = 6]; // } // ``` - optional uint64 len_bytes = 20 [(priv.field).cel = { + optional uint64 len_bytes = 20 [(shared_field).cel = { id: "string.len_bytes" expression: "uint(bytes(this).size()) != rules.len_bytes ? 'value length must be %s bytes'.format([rules.len_bytes]) : ''" }]; @@ -2702,7 +3263,7 @@ message StringRules { // } // // ``` - optional uint64 min_bytes = 4 [(priv.field).cel = { + optional uint64 min_bytes = 4 [(shared_field).cel = { id: "string.min_bytes" expression: "uint(bytes(this).size()) < rules.min_bytes ? 'value length must be at least %s bytes'.format([rules.min_bytes]) : ''" }]; @@ -2717,7 +3278,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_bytes = 8]; // } // ``` - optional uint64 max_bytes = 5 [(priv.field).cel = { + optional uint64 max_bytes = 5 [(shared_field).cel = { id: "string.max_bytes" expression: "uint(bytes(this).size()) > rules.max_bytes ? 'value length must be at most %s bytes'.format([rules.max_bytes]) : ''" }]; @@ -2733,7 +3294,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z]//$"]; // } // ``` - optional string pattern = 6 [(priv.field).cel = { + optional string pattern = 6 [(shared_field).cel = { id: "string.pattern" expression: "!this.matches(rules.pattern) ? 'value does not match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -2749,7 +3310,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.prefix = "pre"]; // } // ``` - optional string prefix = 7 [(priv.field).cel = { + optional string prefix = 7 [(shared_field).cel = { id: "string.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix `%s`'.format([rules.prefix]) : ''" }]; @@ -2764,7 +3325,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.suffix = "post"]; // } // ``` - optional string suffix = 8 [(priv.field).cel = { + optional string suffix = 8 [(shared_field).cel = { id: "string.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix `%s`'.format([rules.suffix]) : ''" }]; @@ -2779,7 +3340,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.contains = "inside"]; // } // ``` - optional string contains = 9 [(priv.field).cel = { + optional string contains = 9 [(shared_field).cel = { id: "string.contains" expression: "!this.contains(rules.contains) ? 'value does not contain substring `%s`'.format([rules.contains]) : ''" }]; @@ -2794,7 +3355,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.not_contains = "inside"]; // } // ``` - optional string not_contains = 23 [(priv.field).cel = { + optional string not_contains = 23 [(shared_field).cel = { id: "string.not_contains" expression: "this.contains(rules.not_contains) ? 'value contains substring `%s`'.format([rules.not_contains]) : ''" }]; @@ -2809,7 +3370,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.in = "apple", (buf.validate.field).string.in = "banana"]; // } // ``` - repeated string in = 10 [(priv.field).cel = { + repeated string in = 10 [(shared_field).cel = { id: "string.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2823,7 +3384,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.not_in = "orange", (buf.validate.field).string.not_in = "grape"]; // } // ``` - repeated string not_in = 11 [(priv.field).cel = { + repeated string not_in = 11 [(shared_field).cel = { id: "string.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -2842,12 +3403,12 @@ message StringRules { // } // ``` bool email = 12 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.email" message: "value must be a valid email address" expression: "this == '' || this.isEmail()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.email_empty" message: "value is empty, which is not a valid email address" expression: "this != ''" @@ -2866,12 +3427,12 @@ message StringRules { // } // ``` bool hostname = 13 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.hostname" message: "value must be a valid hostname" expression: "this == '' || this.isHostname()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.hostname_empty" message: "value is empty, which is not a valid hostname" expression: "this != ''" @@ -2890,12 +3451,12 @@ message StringRules { // } // ``` bool ip = 14 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ip" message: "value must be a valid IP address" expression: "this == '' || this.isIp()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ip_empty" message: "value is empty, which is not a valid IP address" expression: "this != ''" @@ -2913,12 +3474,12 @@ message StringRules { // } // ``` bool ipv4 = 15 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4" message: "value must be a valid IPv4 address" expression: "this == '' || this.isIp(4)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "this != ''" @@ -2936,12 +3497,12 @@ message StringRules { // } // ``` bool ipv6 = 16 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6" message: "value must be a valid IPv6 address" expression: "this == '' || this.isIp(6)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "this != ''" @@ -2959,12 +3520,12 @@ message StringRules { // } // ``` bool uri = 17 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.uri" message: "value must be a valid URI" expression: "this == '' || this.isUri()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.uri_empty" message: "value is empty, which is not a valid URI" expression: "this != ''" @@ -2981,7 +3542,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.uri_ref = true]; // } // ``` - bool uri_ref = 18 [(priv.field).cel = { + bool uri_ref = 18 [(shared_field).cel = { id: "string.uri_ref" message: "value must be a valid URI" expression: "this.isUriRef()" @@ -3000,12 +3561,12 @@ message StringRules { // } // ``` bool address = 21 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.address" message: "value must be a valid hostname, or ip address" expression: "this == '' || this.isHostname() || this.isIp()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.address_empty" message: "value is empty, which is not a valid hostname, or ip address" expression: "this != ''" @@ -3023,12 +3584,12 @@ message StringRules { // } // ``` bool uuid = 22 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.uuid" message: "value must be a valid UUID" expression: "this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.uuid_empty" message: "value is empty, which is not a valid UUID" expression: "this != ''" @@ -3047,12 +3608,12 @@ message StringRules { // } // ``` bool tuuid = 33 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.tuuid" message: "value must be a valid trimmed UUID" expression: "this == '' || this.matches('^[0-9a-fA-F]{32}$')" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.tuuid_empty" message: "value is empty, which is not a valid trimmed UUID" expression: "this != ''" @@ -3071,12 +3632,12 @@ message StringRules { // } // ``` bool ip_with_prefixlen = 26 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ip_with_prefixlen" message: "value must be a valid IP prefix" expression: "this == '' || this.isIpPrefix()" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ip_with_prefixlen_empty" message: "value is empty, which is not a valid IP prefix" expression: "this != ''" @@ -3095,12 +3656,12 @@ message StringRules { // } // ``` bool ipv4_with_prefixlen = 27 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4_with_prefixlen" message: "value must be a valid IPv4 address with prefix length" expression: "this == '' || this.isIpPrefix(4)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4_with_prefixlen_empty" message: "value is empty, which is not a valid IPv4 address with prefix length" expression: "this != ''" @@ -3119,12 +3680,12 @@ message StringRules { // } // ``` bool ipv6_with_prefixlen = 28 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6_with_prefixlen" message: "value must be a valid IPv6 address with prefix length" expression: "this == '' || this.isIpPrefix(6)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6_with_prefixlen_empty" message: "value is empty, which is not a valid IPv6 address with prefix length" expression: "this != ''" @@ -3143,12 +3704,12 @@ message StringRules { // } // ``` bool ip_prefix = 29 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ip_prefix" message: "value must be a valid IP prefix" expression: "this == '' || this.isIpPrefix(true)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ip_prefix_empty" message: "value is empty, which is not a valid IP prefix" expression: "this != ''" @@ -3167,12 +3728,12 @@ message StringRules { // } // ``` bool ipv4_prefix = 30 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4_prefix" message: "value must be a valid IPv4 prefix" expression: "this == '' || this.isIpPrefix(4, true)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv4_prefix_empty" message: "value is empty, which is not a valid IPv4 prefix" expression: "this != ''" @@ -3191,12 +3752,12 @@ message StringRules { // } // ``` bool ipv6_prefix = 31 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6_prefix" message: "value must be a valid IPv6 prefix" expression: "this == '' || this.isIpPrefix(6, true)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.ipv6_prefix_empty" message: "value is empty, which is not a valid IPv6 prefix" expression: "this != ''" @@ -3208,12 +3769,12 @@ message StringRules { // must be in the range of 0-65535, inclusive. IPv6 addresses must be delimited // with square brackets (e.g., `[::1]:1234`). bool host_and_port = 32 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.host_and_port" message: "value must be a valid host (hostname or IP address) and port pair" expression: "this == '' || this.isHostAndPort(true)" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.host_and_port_empty" message: "value is empty, which is not a valid host and port pair" expression: "this != ''" @@ -3241,7 +3802,7 @@ message StringRules { // | KNOWN_REGEX_HTTP_HEADER_NAME | 1 | HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2) | // | KNOWN_REGEX_HTTP_HEADER_VALUE | 2 | HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4) | KnownRegex well_known_regex = 24 [ - (priv.field).cel = { + (shared_field).cel = { id: "string.well_known_regex.header_name" message: "value must be a valid HTTP header name" expression: @@ -3249,12 +3810,12 @@ message StringRules { "'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :" "'^[^\\u0000\\u000A\\u000D]+$')" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.well_known_regex.header_name_empty" message: "value is empty, which is not a valid HTTP header name" expression: "rules.well_known_regex != 1 || this != ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "string.well_known_regex.header_value" message: "value must be a valid HTTP header value" expression: @@ -3278,6 +3839,41 @@ message StringRules { // } // ``` optional bool strict = 25; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyString { + // string value = 1 [ + // (buf.validate.field).string.example = 1, + // (buf.validate.field).string.example = 2 + // ]; + // } + // ``` + repeated string example = 34 [(shared_field).cel = { + id: "string.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // WellKnownRegex contain some well-known patterns. @@ -3303,7 +3899,7 @@ message BytesRules { // bytes value = 1 [(buf.validate.field).bytes.const = "\x01\x02\x03\x04"]; // } // ``` - optional bytes const = 1 [(priv.field).cel = { + optional bytes const = 1 [(shared_field).cel = { id: "bytes.const" expression: "this != rules.const ? 'value must be %x'.format([rules.const]) : ''" }]; @@ -3317,7 +3913,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.len = 4]; // } // ``` - optional uint64 len = 13 [(priv.field).cel = { + optional uint64 len = 13 [(shared_field).cel = { id: "bytes.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s bytes'.format([rules.len]) : ''" }]; @@ -3332,7 +3928,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.min_len = 2]; // } // ``` - optional uint64 min_len = 2 [(priv.field).cel = { + optional uint64 min_len = 2 [(shared_field).cel = { id: "bytes.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s bytes'.format([rules.min_len]) : ''" }]; @@ -3347,7 +3943,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.max_len = 6]; // } // ``` - optional uint64 max_len = 3 [(priv.field).cel = { + optional uint64 max_len = 3 [(shared_field).cel = { id: "bytes.max_len" expression: "uint(this.size()) > rules.max_len ? 'value must be at most %s bytes'.format([rules.max_len]) : ''" }]; @@ -3364,7 +3960,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]+$"]; // } // ``` - optional string pattern = 4 [(priv.field).cel = { + optional string pattern = 4 [(shared_field).cel = { id: "bytes.pattern" expression: "!string(this).matches(rules.pattern) ? 'value must match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -3379,7 +3975,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.prefix = "\x01\x02"]; // } // ``` - optional bytes prefix = 5 [(priv.field).cel = { + optional bytes prefix = 5 [(shared_field).cel = { id: "bytes.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix %x'.format([rules.prefix]) : ''" }]; @@ -3394,7 +3990,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.suffix = "\x03\x04"]; // } // ``` - optional bytes suffix = 6 [(priv.field).cel = { + optional bytes suffix = 6 [(shared_field).cel = { id: "bytes.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix %x'.format([rules.suffix]) : ''" }]; @@ -3409,7 +4005,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; // } // ``` - optional bytes contains = 7 [(priv.field).cel = { + optional bytes contains = 7 [(shared_field).cel = { id: "bytes.contains" expression: "!this.contains(rules.contains) ? 'value does not contain %x'.format([rules.contains]) : ''" }]; @@ -3424,7 +4020,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes in = 8 [(priv.field).cel = { + repeated bytes in = 8 [(shared_field).cel = { id: "bytes.in" expression: "dyn(rules)['in'].size() > 0 && !(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3440,7 +4036,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.not_in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes not_in = 9 [(priv.field).cel = { + repeated bytes not_in = 9 [(shared_field).cel = { id: "bytes.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -3458,12 +4054,12 @@ message BytesRules { // } // ``` bool ip = 10 [ - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ip" message: "value must be a valid IP address" expression: "this.size() == 0 || this.size() == 4 || this.size() == 16" }, - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ip_empty" message: "value is empty, which is not a valid IP address" expression: "this.size() != 0" @@ -3480,12 +4076,12 @@ message BytesRules { // } // ``` bool ipv4 = 11 [ - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ipv4" message: "value must be a valid IPv4 address" expression: "this.size() == 0 || this.size() == 4" }, - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "this.size() != 0" @@ -3501,18 +4097,53 @@ message BytesRules { // } // ``` bool ipv6 = 12 [ - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ipv6" message: "value must be a valid IPv6 address" expression: "this.size() == 0 || this.size() == 16" }, - (priv.field).cel = { + (shared_field).cel = { id: "bytes.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "this.size() != 0" } ]; } + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBytes { + // bytes value = 1 [ + // (buf.validate.field).bytes.example = "\x01\x02", + // (buf.validate.field).bytes.example = "\x02\x03" + // ]; + // } + // ``` + repeated bytes example = 14 [(shared_field).cel = { + id: "bytes.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // EnumRules describe the constraints applied to `enum` values. @@ -3532,7 +4163,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum.const = 1]; // } // ``` - optional int32 const = 1 [(priv.field).cel = { + optional int32 const = 1 [(shared_field).cel = { id: "enum.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3570,7 +4201,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { in: [1, 2]}]; // } // ``` - repeated int32 in = 3 [(priv.field).cel = { + repeated int32 in = 3 [(shared_field).cel = { id: "enum.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3591,10 +4222,49 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { not_in: [1, 2]}]; // } // ``` - repeated int32 not_in = 4 [(priv.field).cel = { + repeated int32 not_in = 4 [(shared_field).cel = { id: "enum.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // (buf.validate.field).enum.example = 1, + // (buf.validate.field).enum.example = 2 + // } + // ``` + repeated int32 example = 5 [(shared_field).cel = { + id: "enum.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // RepeatedRules describe the constraints applied to `repeated` values. @@ -3610,7 +4280,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.min_items = 2]; // } // ``` - optional uint64 min_items = 1 [(priv.field).cel = { + optional uint64 min_items = 1 [(shared_field).cel = { id: "repeated.min_items" expression: "uint(this.size()) < rules.min_items ? 'value must contain at least %d item(s)'.format([rules.min_items]) : ''" }]; @@ -3626,7 +4296,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.max_items = 3]; // } // ``` - optional uint64 max_items = 2 [(priv.field).cel = { + optional uint64 max_items = 2 [(shared_field).cel = { id: "repeated.max_items" expression: "uint(this.size()) > rules.max_items ? 'value must contain no more than %s item(s)'.format([rules.max_items]) : ''" }]; @@ -3641,7 +4311,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.unique = true]; // } // ``` - optional bool unique = 3 [(priv.field).cel = { + optional bool unique = 3 [(shared_field).cel = { id: "repeated.unique" message: "repeated value must contain unique items" expression: "this.unique()" @@ -3663,6 +4333,24 @@ message RepeatedRules { // } // ``` optional FieldConstraints items = 4; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // MapRules describe the constraints applied to `map` values. @@ -3676,7 +4364,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.min_pairs = 2]; // } // ``` - optional uint64 min_pairs = 1 [(priv.field).cel = { + optional uint64 min_pairs = 1 [(shared_field).cel = { id: "map.min_pairs" expression: "uint(this.size()) < rules.min_pairs ? 'map must be at least %d entries'.format([rules.min_pairs]) : ''" }]; @@ -3690,7 +4378,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.max_pairs = 3]; // } // ``` - optional uint64 max_pairs = 2 [(priv.field).cel = { + optional uint64 max_pairs = 2 [(shared_field).cel = { id: "map.max_pairs" expression: "uint(this.size()) > rules.max_pairs ? 'map must be at most %d entries'.format([rules.max_pairs]) : ''" }]; @@ -3726,6 +4414,24 @@ message MapRules { // } // ``` optional FieldConstraints values = 5; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // AnyRules describe constraints applied exclusively to the `google.protobuf.Any` well-known type. @@ -3765,7 +4471,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.const = "5s"]; // } // ``` - optional google.protobuf.Duration const = 2 [(priv.field).cel = { + optional google.protobuf.Duration const = 2 [(shared_field).cel = { id: "duration.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3780,7 +4486,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = "5s"]; // } // ``` - google.protobuf.Duration lt = 3 [(priv.field).cel = { + google.protobuf.Duration lt = 3 [(shared_field).cel = { id: "duration.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -3797,7 +4503,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lte = "10s"]; // } // ``` - google.protobuf.Duration lte = 4 [(priv.field).cel = { + google.protobuf.Duration lte = 4 [(shared_field).cel = { id: "duration.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -3824,31 +4530,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gt = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "duration.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -3875,31 +4581,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gte = 6 [ - (priv.field).cel = { + (shared_field).cel = { id: "duration.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "duration.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -3918,7 +4624,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration in = 7 [(priv.field).cel = { + repeated google.protobuf.Duration in = 7 [(shared_field).cel = { id: "duration.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3934,10 +4640,45 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.not_in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration not_in = 8 [(priv.field).cel = { + repeated google.protobuf.Duration not_in = 8 [(shared_field).cel = { id: "duration.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDuration { + // google.protobuf.Duration value = 1 [ + // (buf.validate.field).duration.example = { seconds: 1 }, + // (buf.validate.field).duration.example = { seconds: 2 }, + // ]; + // } + // ``` + repeated google.protobuf.Duration example = 9 [(shared_field).cel = { + id: "duration.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } // TimestampRules describe the constraints applied exclusively to the `google.protobuf.Timestamp` well-known type. @@ -3950,7 +4691,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.const = {seconds: 1727998800}]; // } // ``` - optional google.protobuf.Timestamp const = 2 [(priv.field).cel = { + optional google.protobuf.Timestamp const = 2 [(shared_field).cel = { id: "timestamp.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3963,7 +4704,7 @@ message TimestampRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = { seconds: 259200 }]; // } // ``` - google.protobuf.Timestamp lt = 3 [(priv.field).cel = { + google.protobuf.Timestamp lt = 3 [(shared_field).cel = { id: "timestamp.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -3978,7 +4719,7 @@ message TimestampRules { // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.lte = { seconds: 1678867200 }]; // } // ``` - google.protobuf.Timestamp lte = 4 [(priv.field).cel = { + google.protobuf.Timestamp lte = 4 [(shared_field).cel = { id: "timestamp.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -3993,7 +4734,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.lt_now = true]; // } // ``` - bool lt_now = 7 [(priv.field).cel = { + bool lt_now = 7 [(shared_field).cel = { id: "timestamp.lt_now" expression: "this > now ? 'value must be less than now' : ''" }]; @@ -4018,31 +4759,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gt = 5 [ - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -4069,31 +4810,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gte = 6 [ - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (shared_field).cel = { id: "timestamp.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -4109,7 +4850,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.gt_now = true]; // } // ``` - bool gt_now = 8 [(priv.field).cel = { + bool gt_now = 8 [(shared_field).cel = { id: "timestamp.gt_now" expression: "this < now ? 'value must be greater than now' : ''" }]; @@ -4123,8 +4864,44 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.within = {seconds: 3600}]; // } // ``` - optional google.protobuf.Duration within = 9 [(priv.field).cel = { + optional google.protobuf.Duration within = 9 [(shared_field).cel = { id: "timestamp.within" expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyTimestamp { + // google.protobuf.Timestamp value = 1 [ + // (buf.validate.field).timestamp.example = { seconds: 1672444800 }, + // (buf.validate.field).timestamp.example = { seconds: 1672531200 }, + // ]; + // } + // ``` + + repeated google.protobuf.Timestamp example = 10 [(shared_field).cel = { + id: "timestamp.example" + expression: "true" + }]; + + // Reserved for shared rules using extension numbers defined within the + // [Protobuf Global Extension Registry][1]. Extension fields in this range + // that have the (buf.validate.shared_field) option set will be treated as + // shared field constraints that can then be set on field options of other + // messages to apply reusable field constraints. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to 49999; + + // Reserved for shared rules using extension numbers defined using a random + // number between 50000 and 536870911. Extensions in this range that have the + // (shared_field) option set will be treated as shared field constraints that + // can then be set on field options of other messages to apply reusable field + // constraints. + // + // It is recommended to use a random number generator, such as random.org. + extensions 50000 to max; } From f50049dcc1c5544f6be42e2de6597045c3e302d6 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Wed, 25 Sep 2024 12:57:34 -0400 Subject: [PATCH 16/30] Update shared rules -> predefined rules. --- .../buflintvalidate/{shared_rules.go => predefined_rules.go} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/{shared_rules.go => predefined_rules.go} (95%) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go similarity index 95% rename from private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go rename to private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index ee5cf41254..def7309e42 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/shared_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -48,7 +48,7 @@ func checkAndRegisterSharedRuleExtension( if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { return nil } - sharedConstraints := resolveExt[*validate.SharedFieldConstraints](extensionDescriptor.Options(), validate.E_SharedField) + sharedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined) if sharedConstraints == nil { return nil } @@ -98,7 +98,7 @@ func checkAndRegisterSharedRuleExtension( addAnnotationFunc( extension, // TODO: move 1 to a const - extension.OptionExtensionLocation(validate.E_SharedField, 1, int32(index)), + extension.OptionExtensionLocation(validate.E_Predefined, 1, int32(index)), nil, format, args..., From 282e462bbd65feb693f89b029dadaa8117e346c1 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Wed, 25 Sep 2024 13:00:32 -0400 Subject: [PATCH 17/30] Fix CI --- .golangci.yml | 6 + .../internal/buflintvalidate/field.go | 6 +- .../buf/validate/expression.proto | 92 ++ .../buf/validate/priv/private.proto | 41 + .../protovalidate/buf/validate/validate.proto | 1367 ++++------------- 5 files changed, 438 insertions(+), 1074 deletions(-) create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto diff --git a/.golangci.yml b/.golangci.yml index 3222e2676e..82edf5d61d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -342,3 +342,9 @@ issues: # to set the source path for the location, this operation should be safe. path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/cel.go text: "G115:" + - linters: + - gosec + # This checks the cel constraints for predefined rules from an Image, and converts loop indices to int32 + # to set the source path for the location, this operation should be safe. + path: private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go + text: "G115:" diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 0d4937eb50..0ef22ce042 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -241,7 +241,7 @@ func checkConstraintsForField( var exampleValues []protoreflect.Value var exampleFieldNumber int32 typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { - // TODO: make "exmaple" a const + // TODO: make "example" a const if string(fd.Name()) == "example" { exampleFieldNumber = int32(fd.Number()) // This assumed all *Rules.Example are repeated, otherwise it panics. @@ -766,7 +766,9 @@ func checkExampleValues( exampleValues []protoreflect.Value, extensionTypeResolver ExtensionTypeResolver, ) error { - reparseUnrecognized(typeRulesMessage, extensionTypeResolver) + if err := reparseUnrecognized(typeRulesMessage, extensionTypeResolver); err != nil { + return err + } hasConstraints := len(fieldConstraints.GetCel()) > 0 // TODO: add a test where only shared rules and examples are specified if !hasConstraints { diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto new file mode 100644 index 0000000000..72ce36dcd8 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto @@ -0,0 +1,92 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// 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. + +syntax = "proto3"; + +package buf.validate; + +option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; +option java_multiple_files = true; +option java_outer_classname = "ExpressionProto"; +option java_package = "build.buf.validate"; + +// `Constraint` represents a validation rule written in the Common Expression +// Language (CEL) syntax. Each Constraint includes a unique identifier, an +// optional error message, and the CEL expression to evaluate. For more +// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). +// +// ```proto +// message Foo { +// option (buf.validate.message).cel = { +// id: "foo.bar" +// message: "bar must be greater than 0" +// expression: "this.bar > 0" +// }; +// int32 bar = 1; +// } +// ``` +message Constraint { + // `id` is a string that serves as a machine-readable name for this Constraint. + // It should be unique within its scope, which could be either a message or a field. + string id = 1; + + // `message` is an optional field that provides a human-readable error message + // for this Constraint when the CEL expression evaluates to false. If a + // non-empty message is provided, any strings resulting from the CEL + // expression evaluation are ignored. + string message = 2; + + // `expression` is the actual CEL expression that will be evaluated for + // validation. This string must resolve to either a boolean or a string + // value. If the expression evaluates to false or a non-empty string, the + // validation is considered failed, and the message is rejected. + string expression = 3; +} + +// `Violations` is a collection of `Violation` messages. This message type is returned by +// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. +// Each individual violation is represented by a `Violation` message. +message Violations { + // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. + repeated Violation violations = 1; +} + +// `Violation` represents a single instance where a validation rule, expressed +// as a `Constraint`, was not met. It provides information about the field that +// caused the violation, the specific constraint that wasn't fulfilled, and a +// human-readable error message. +// +// ```json +// { +// "fieldPath": "bar", +// "constraintId": "foo.bar", +// "message": "bar must be greater than 0" +// } +// ``` +message Violation { + // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. + // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. + string field_path = 1; + + // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. + // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. + string constraint_id = 2; + + // `message` is a human-readable error message that describes the nature of the violation. + // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. + string message = 3; + + // `for_key` indicates whether the violation was caused by a map key, rather than a value. + bool for_key = 4; +} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto new file mode 100644 index 0000000000..ddaf938a61 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto @@ -0,0 +1,41 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// 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. + +syntax = "proto3"; + +package buf.validate.priv; + +import "google/protobuf/descriptor.proto"; + +option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/priv"; +option java_multiple_files = true; +option java_outer_classname = "PrivateProto"; +option java_package = "build.buf.validate.priv"; + +extend google.protobuf.FieldOptions { + // Do not use. Internal to protovalidate library + optional FieldConstraints field = 1160; +} + +// Do not use. Internal to protovalidate library +message FieldConstraints { + repeated Constraint cel = 1; +} + +// Do not use. Internal to protovalidate library +message Constraint { + string id = 1; + string message = 2; + string expression = 3; +} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto index 133221be2d..fcc8cac448 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto @@ -12,10 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto2"; +syntax = "proto3"; package buf.validate; +import "buf/validate/expression.proto"; +import "buf/validate/priv/private.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -53,94 +55,6 @@ extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldConstraints field = 1159; - - // Specifies shared rules. When extending a standard constraint message, this - // adds additional CEL expressions that apply when the extension is used. - // - // ```proto - // extend buf.validate.Int32Rules { - // bool is_zero [(buf.validate.shared_field).cel = { - // id: "int32.is_zero", - // message: "value must be zero", - // expression: "rule ? this == 0 : ''", - // }]; - // } - // - // message Foo { - // int32 reserved = 1 [(buf.validate.field).int32.(is_zero) = true]; - // } - // ``` - optional SharedFieldConstraints shared_field = 1160; -} - -// `Constraint` represents a validation rule written in the Common Expression -// Language (CEL) syntax. Each Constraint includes a unique identifier, an -// optional error message, and the CEL expression to evaluate. For more -// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). -// -// ```proto -// message Foo { -// option (buf.validate.message).cel = { -// id: "foo.bar" -// message: "bar must be greater than 0" -// expression: "this.bar > 0" -// }; -// int32 bar = 1; -// } -// ``` -message Constraint { - // `id` is a string that serves as a machine-readable name for this Constraint. - // It should be unique within its scope, which could be either a message or a field. - optional string id = 1; - - // `message` is an optional field that provides a human-readable error message - // for this Constraint when the CEL expression evaluates to false. If a - // non-empty message is provided, any strings resulting from the CEL - // expression evaluation are ignored. - optional string message = 2; - - // `expression` is the actual CEL expression that will be evaluated for - // validation. This string must resolve to either a boolean or a string - // value. If the expression evaluates to false or a non-empty string, the - // validation is considered failed, and the message is rejected. - optional string expression = 3; -} - -// `Violations` is a collection of `Violation` messages. This message type is returned by -// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. -// Each individual violation is represented by a `Violation` message. -message Violations { - // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. - repeated Violation violations = 1; -} - -// `Violation` represents a single instance where a validation rule, expressed -// as a `Constraint`, was not met. It provides information about the field that -// caused the violation, the specific constraint that wasn't fulfilled, and a -// human-readable error message. -// -// ```json -// { -// "fieldPath": "bar", -// "constraintId": "foo.bar", -// "message": "bar must be greater than 0" -// } -// ``` -message Violation { - // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. - // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. - optional string field_path = 1; - - // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. - // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. - optional string constraint_id = 2; - - // `message` is a human-readable error message that describes the nature of the violation. - // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. - optional string message = 3; - - // `for_key` indicates whether the violation was caused by a map key, rather than a value. - optional bool for_key = 4; } // MessageConstraints represents validation rules that are applied to the entire message. @@ -234,7 +148,7 @@ message FieldConstraints { // optional MyOtherMessage value = 1 [(buf.validate.field).required = true]; // } // ``` - optional bool required = 25; + bool required = 25; // Skip validation on the field if its value matches the specified criteria. // See Ignore enum for details. // @@ -248,7 +162,7 @@ message FieldConstraints { // ]; // } // ``` - optional Ignore ignore = 27; + Ignore ignore = 27; oneof type { // Scalar Field Types @@ -280,29 +194,9 @@ message FieldConstraints { } // DEPRECATED: use ignore=IGNORE_ALWAYS instead. TODO: remove this field pre-v1. - optional bool skipped = 24 [deprecated = true]; + bool skipped = 24 [deprecated = true]; // DEPRECATED: use ignore=IGNORE_IF_UNPOPULATED instead. TODO: remove this field pre-v1. - optional bool ignore_empty = 26 [deprecated = true]; -} - -// SharedFieldConstraints are custom constraints that can be re-used with -// multiple fields. -message SharedFieldConstraints { - // `cel` is a repeated field used to represent a textual expression - // in the Common Expression Language (CEL) syntax. For more information on - // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). - // - // ```proto - // message MyMessage { - // // The field `value` must be greater than 42. - // optional int32 value = 1 [(buf.validate.shared_field).cel = { - // id: "my_message.value", - // message: "value must be greater than 42", - // expression: "this > 42", - // }]; - // } - // ``` - repeated Constraint cel = 1; + bool ignore_empty = 26 [deprecated = true]; } // Specifies how FieldConstraints.ignore behaves. See the documentation for @@ -471,7 +365,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.const = 42.0]; // } // ``` - optional float const = 1 [(shared_field).cel = { + optional float const = 1 [(priv.field).cel = { id: "float.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -487,7 +381,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lt = 10.0]; // } // ``` - float lt = 2 [(shared_field).cel = { + float lt = 2 [(priv.field).cel = { id: "float.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -504,7 +398,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lte = 10.0]; // } // ``` - float lte = 3 [(shared_field).cel = { + float lte = 3 [(priv.field).cel = { id: "float.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -532,31 +426,31 @@ message FloatRules { // } // ``` float gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "float.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -583,31 +477,31 @@ message FloatRules { // } // ``` float gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "float.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "float.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -626,7 +520,7 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float in = 6 [(shared_field).cel = { + repeated float in = 6 [(priv.field).cel = { id: "float.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -641,52 +535,17 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float not_in = 7 [(shared_field).cel = { + repeated float not_in = 7 [(priv.field).cel = { id: "float.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - optional bool finite = 8 [(shared_field).cel = { + bool finite = 8 [(priv.field).cel = { id: "float.finite" expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyFloat { - // float value = 1 [ - // (buf.validate.field).float.example = 1.0, - // (buf.validate.field).float.example = "Infinity" - // ]; - // } - // ``` - repeated float example = 9 [(shared_field).cel = { - id: "float.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // DoubleRules describes the constraints applied to `double` values. These @@ -701,7 +560,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.const = 42.0]; // } // ``` - optional double const = 1 [(shared_field).cel = { + optional double const = 1 [(priv.field).cel = { id: "double.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -716,7 +575,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lt = 10.0]; // } // ``` - double lt = 2 [(shared_field).cel = { + double lt = 2 [(priv.field).cel = { id: "double.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -733,7 +592,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lte = 10.0]; // } // ``` - double lte = 3 [(shared_field).cel = { + double lte = 3 [(priv.field).cel = { id: "double.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -760,31 +619,31 @@ message DoubleRules { // } // ``` double gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "double.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -811,31 +670,31 @@ message DoubleRules { // } // ``` double gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "double.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "double.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -853,7 +712,7 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double in = 6 [(shared_field).cel = { + repeated double in = 6 [(priv.field).cel = { id: "double.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -868,52 +727,17 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double not_in = 7 [(shared_field).cel = { + repeated double not_in = 7 [(priv.field).cel = { id: "double.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - optional bool finite = 8 [(shared_field).cel = { + bool finite = 8 [(priv.field).cel = { id: "double.finite" expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyDouble { - // double value = 1 [ - // (buf.validate.field).double.example = 1.0, - // (buf.validate.field).double.example = "Infinity" - // ]; - // } - // ``` - repeated double example = 9 [(shared_field).cel = { - id: "double.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // Int32Rules describes the constraints applied to `int32` values. These @@ -928,7 +752,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.const = 42]; // } // ``` - optional int32 const = 1 [(shared_field).cel = { + optional int32 const = 1 [(priv.field).cel = { id: "int32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -943,7 +767,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lt = 10]; // } // ``` - int32 lt = 2 [(shared_field).cel = { + int32 lt = 2 [(priv.field).cel = { id: "int32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -960,7 +784,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lte = 10]; // } // ``` - int32 lte = 3 [(shared_field).cel = { + int32 lte = 3 [(priv.field).cel = { id: "int32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -987,31 +811,31 @@ message Int32Rules { // } // ``` int32 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "int32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1038,31 +862,31 @@ message Int32Rules { // } // ``` int32 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "int32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1081,7 +905,7 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { in: [1, 2, 3] }; // } // ``` - repeated int32 in = 6 [(shared_field).cel = { + repeated int32 in = 6 [(priv.field).cel = { id: "int32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1096,45 +920,10 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { not_in: [1, 2, 3] }; // } // ``` - repeated int32 not_in = 7 [(shared_field).cel = { + repeated int32 not_in = 7 [(priv.field).cel = { id: "int32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyInt32 { - // int32 value = 1 [ - // (buf.validate.field).int32.example = 1, - // (buf.validate.field).int32.example = -10 - // ]; - // } - // ``` - repeated int32 example = 8 [(shared_field).cel = { - id: "int32.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // Int64Rules describes the constraints applied to `int64` values. These @@ -1149,7 +938,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.const = 42]; // } // ``` - optional int64 const = 1 [(shared_field).cel = { + optional int64 const = 1 [(priv.field).cel = { id: "int64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1164,7 +953,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lt = 10]; // } // ``` - int64 lt = 2 [(shared_field).cel = { + int64 lt = 2 [(priv.field).cel = { id: "int64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1181,7 +970,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lte = 10]; // } // ``` - int64 lte = 3 [(shared_field).cel = { + int64 lte = 3 [(priv.field).cel = { id: "int64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1208,31 +997,31 @@ message Int64Rules { // } // ``` int64 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "int64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1259,31 +1048,31 @@ message Int64Rules { // } // ``` int64 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "int64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "int64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1302,7 +1091,7 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { in: [1, 2, 3] }; // } // ``` - repeated int64 in = 6 [(shared_field).cel = { + repeated int64 in = 6 [(priv.field).cel = { id: "int64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1317,45 +1106,10 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { not_in: [1, 2, 3] }; // } // ``` - repeated int64 not_in = 7 [(shared_field).cel = { + repeated int64 not_in = 7 [(priv.field).cel = { id: "int64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyInt64 { - // int64 value = 1 [ - // (buf.validate.field).int64.example = 1, - // (buf.validate.field).int64.example = -10 - // ]; - // } - // ``` - repeated int64 example = 9 [(shared_field).cel = { - id: "int64.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // UInt32Rules describes the constraints applied to `uint32` values. These @@ -1370,7 +1124,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.const = 42]; // } // ``` - optional uint32 const = 1 [(shared_field).cel = { + optional uint32 const = 1 [(priv.field).cel = { id: "uint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1385,7 +1139,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lt = 10]; // } // ``` - uint32 lt = 2 [(shared_field).cel = { + uint32 lt = 2 [(priv.field).cel = { id: "uint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1402,7 +1156,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lte = 10]; // } // ``` - uint32 lte = 3 [(shared_field).cel = { + uint32 lte = 3 [(priv.field).cel = { id: "uint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1429,31 +1183,31 @@ message UInt32Rules { // } // ``` uint32 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1480,31 +1234,31 @@ message UInt32Rules { // } // ``` uint32 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1523,7 +1277,7 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { in: [1, 2, 3] }; // } // ``` - repeated uint32 in = 6 [(shared_field).cel = { + repeated uint32 in = 6 [(priv.field).cel = { id: "uint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1538,45 +1292,10 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint32 not_in = 7 [(shared_field).cel = { + repeated uint32 not_in = 7 [(priv.field).cel = { id: "uint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyUInt32 { - // uint32 value = 1 [ - // (buf.validate.field).uint32.example = 1, - // (buf.validate.field).uint32.example = 10 - // ]; - // } - // ``` - repeated uint32 example = 8 [(shared_field).cel = { - id: "uint32.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // UInt64Rules describes the constraints applied to `uint64` values. These @@ -1591,7 +1310,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.const = 42]; // } // ``` - optional uint64 const = 1 [(shared_field).cel = { + optional uint64 const = 1 [(priv.field).cel = { id: "uint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1606,7 +1325,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lt = 10]; // } // ``` - uint64 lt = 2 [(shared_field).cel = { + uint64 lt = 2 [(priv.field).cel = { id: "uint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1623,7 +1342,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lte = 10]; // } // ``` - uint64 lte = 3 [(shared_field).cel = { + uint64 lte = 3 [(priv.field).cel = { id: "uint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1650,31 +1369,31 @@ message UInt64Rules { // } // ``` uint64 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1701,31 +1420,31 @@ message UInt64Rules { // } // ``` uint64 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "uint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1743,7 +1462,7 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { in: [1, 2, 3] }; // } // ``` - repeated uint64 in = 6 [(shared_field).cel = { + repeated uint64 in = 6 [(priv.field).cel = { id: "uint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1758,45 +1477,10 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint64 not_in = 7 [(shared_field).cel = { + repeated uint64 not_in = 7 [(priv.field).cel = { id: "uint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyUInt64 { - // uint64 value = 1 [ - // (buf.validate.field).uint64.example = 1, - // (buf.validate.field).uint64.example = -10 - // ]; - // } - // ``` - repeated uint64 example = 8 [(shared_field).cel = { - id: "uint64.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // SInt32Rules describes the constraints applied to `sint32` values. @@ -1810,7 +1494,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.const = 42]; // } // ``` - optional sint32 const = 1 [(shared_field).cel = { + optional sint32 const = 1 [(priv.field).cel = { id: "sint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1825,7 +1509,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lt = 10]; // } // ``` - sint32 lt = 2 [(shared_field).cel = { + sint32 lt = 2 [(priv.field).cel = { id: "sint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1842,7 +1526,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lte = 10]; // } // ``` - sint32 lte = 3 [(shared_field).cel = { + sint32 lte = 3 [(priv.field).cel = { id: "sint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1869,31 +1553,31 @@ message SInt32Rules { // } // ``` sint32 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1920,31 +1604,31 @@ message SInt32Rules { // } // ``` sint32 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1963,7 +1647,7 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { in: [1, 2, 3] }; // } // ``` - repeated sint32 in = 6 [(shared_field).cel = { + repeated sint32 in = 6 [(priv.field).cel = { id: "sint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1978,45 +1662,10 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint32 not_in = 7 [(shared_field).cel = { + repeated sint32 not_in = 7 [(priv.field).cel = { id: "sint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MySInt32 { - // sint32 value = 1 [ - // (buf.validate.field).sint32.example = 1, - // (buf.validate.field).sint32.example = -10 - // ]; - // } - // ``` - repeated sint32 example = 8 [(shared_field).cel = { - id: "sint32.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // SInt64Rules describes the constraints applied to `sint64` values. @@ -2030,7 +1679,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.const = 42]; // } // ``` - optional sint64 const = 1 [(shared_field).cel = { + optional sint64 const = 1 [(priv.field).cel = { id: "sint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2045,7 +1694,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lt = 10]; // } // ``` - sint64 lt = 2 [(shared_field).cel = { + sint64 lt = 2 [(priv.field).cel = { id: "sint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2062,7 +1711,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lte = 10]; // } // ``` - sint64 lte = 3 [(shared_field).cel = { + sint64 lte = 3 [(priv.field).cel = { id: "sint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2089,31 +1738,31 @@ message SInt64Rules { // } // ``` sint64 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2140,31 +1789,31 @@ message SInt64Rules { // } // ``` sint64 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2183,7 +1832,7 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { in: [1, 2, 3] }; // } // ``` - repeated sint64 in = 6 [(shared_field).cel = { + repeated sint64 in = 6 [(priv.field).cel = { id: "sint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2198,45 +1847,10 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint64 not_in = 7 [(shared_field).cel = { + repeated sint64 not_in = 7 [(priv.field).cel = { id: "sint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MySInt64 { - // sint64 value = 1 [ - // (buf.validate.field).sint64.example = 1, - // (buf.validate.field).sint64.example = -10 - // ]; - // } - // ``` - repeated sint64 example = 8 [(shared_field).cel = { - id: "sint64.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // Fixed32Rules describes the constraints applied to `fixed32` values. @@ -2250,7 +1864,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.const = 42]; // } // ``` - optional fixed32 const = 1 [(shared_field).cel = { + optional fixed32 const = 1 [(priv.field).cel = { id: "fixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2265,7 +1879,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lt = 10]; // } // ``` - fixed32 lt = 2 [(shared_field).cel = { + fixed32 lt = 2 [(priv.field).cel = { id: "fixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2282,7 +1896,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lte = 10]; // } // ``` - fixed32 lte = 3 [(shared_field).cel = { + fixed32 lte = 3 [(priv.field).cel = { id: "fixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2309,31 +1923,31 @@ message Fixed32Rules { // } // ``` fixed32 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2360,31 +1974,31 @@ message Fixed32Rules { // } // ``` fixed32 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2403,7 +2017,7 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { in: [1, 2, 3] }; // } // ``` - repeated fixed32 in = 6 [(shared_field).cel = { + repeated fixed32 in = 6 [(priv.field).cel = { id: "fixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2418,45 +2032,10 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed32 not_in = 7 [(shared_field).cel = { + repeated fixed32 not_in = 7 [(priv.field).cel = { id: "fixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyFixed32 { - // fixed32 value = 1 [ - // (buf.validate.field).fixed32.example = 1, - // (buf.validate.field).fixed32.example = 2 - // ]; - // } - // ``` - repeated fixed32 example = 8 [(shared_field).cel = { - id: "fixed32.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // Fixed64Rules describes the constraints applied to `fixed64` values. @@ -2470,7 +2049,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.const = 42]; // } // ``` - optional fixed64 const = 1 [(shared_field).cel = { + optional fixed64 const = 1 [(priv.field).cel = { id: "fixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2485,7 +2064,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lt = 10]; // } // ``` - fixed64 lt = 2 [(shared_field).cel = { + fixed64 lt = 2 [(priv.field).cel = { id: "fixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2502,7 +2081,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lte = 10]; // } // ``` - fixed64 lte = 3 [(shared_field).cel = { + fixed64 lte = 3 [(priv.field).cel = { id: "fixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2529,31 +2108,31 @@ message Fixed64Rules { // } // ``` fixed64 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2580,31 +2159,31 @@ message Fixed64Rules { // } // ``` fixed64 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "fixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2623,7 +2202,7 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { in: [1, 2, 3] }; // } // ``` - repeated fixed64 in = 6 [(shared_field).cel = { + repeated fixed64 in = 6 [(priv.field).cel = { id: "fixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2638,45 +2217,10 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed64 not_in = 7 [(shared_field).cel = { + repeated fixed64 not_in = 7 [(priv.field).cel = { id: "fixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyFixed64 { - // fixed64 value = 1 [ - // (buf.validate.field).fixed64.example = 1, - // (buf.validate.field).fixed64.example = 2 - // ]; - // } - // ``` - repeated fixed64 example = 8 [(shared_field).cel = { - id: "fixed64.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // SFixed32Rules describes the constraints applied to `fixed32` values. @@ -2690,7 +2234,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.const = 42]; // } // ``` - optional sfixed32 const = 1 [(shared_field).cel = { + optional sfixed32 const = 1 [(priv.field).cel = { id: "sfixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2705,7 +2249,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lt = 10]; // } // ``` - sfixed32 lt = 2 [(shared_field).cel = { + sfixed32 lt = 2 [(priv.field).cel = { id: "sfixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2722,7 +2266,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lte = 10]; // } // ``` - sfixed32 lte = 3 [(shared_field).cel = { + sfixed32 lte = 3 [(priv.field).cel = { id: "sfixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2749,31 +2293,31 @@ message SFixed32Rules { // } // ``` sfixed32 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2800,31 +2344,31 @@ message SFixed32Rules { // } // ``` sfixed32 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2843,7 +2387,7 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed32 in = 6 [(shared_field).cel = { + repeated sfixed32 in = 6 [(priv.field).cel = { id: "sfixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2858,45 +2402,10 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed32 not_in = 7 [(shared_field).cel = { + repeated sfixed32 not_in = 7 [(priv.field).cel = { id: "sfixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MySFixed32 { - // sfixed32 value = 1 [ - // (buf.validate.field).sfixed32.example = 1, - // (buf.validate.field).sfixed32.example = 2 - // ]; - // } - // ``` - repeated sfixed32 example = 8 [(shared_field).cel = { - id: "sfixed32.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // SFixed64Rules describes the constraints applied to `fixed64` values. @@ -2910,7 +2419,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.const = 42]; // } // ``` - optional sfixed64 const = 1 [(shared_field).cel = { + optional sfixed64 const = 1 [(priv.field).cel = { id: "sfixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2925,7 +2434,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lt = 10]; // } // ``` - sfixed64 lt = 2 [(shared_field).cel = { + sfixed64 lt = 2 [(priv.field).cel = { id: "sfixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2942,7 +2451,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lte = 10]; // } // ``` - sfixed64 lte = 3 [(shared_field).cel = { + sfixed64 lte = 3 [(priv.field).cel = { id: "sfixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2969,31 +2478,31 @@ message SFixed64Rules { // } // ``` sfixed64 gt = 4 [ - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -3020,31 +2529,31 @@ message SFixed64Rules { // } // ``` sfixed64 gte = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "sfixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -3063,7 +2572,7 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed64 in = 6 [(shared_field).cel = { + repeated sfixed64 in = 6 [(priv.field).cel = { id: "sfixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3078,45 +2587,10 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed64 not_in = 7 [(shared_field).cel = { + repeated sfixed64 not_in = 7 [(priv.field).cel = { id: "sfixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MySFixed64 { - // sfixed64 value = 1 [ - // (buf.validate.field).sfixed64.example = 1, - // (buf.validate.field).sfixed64.example = 2 - // ]; - // } - // ``` - repeated sfixed64 example = 8 [(shared_field).cel = { - id: "sfixed64.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // BoolRules describes the constraints applied to `bool` values. These rules @@ -3131,45 +2605,10 @@ message BoolRules { // bool value = 1 [(buf.validate.field).bool.const = true]; // } // ``` - optional bool const = 1 [(shared_field).cel = { + optional bool const = 1 [(priv.field).cel = { id: "bool.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyBool { - // bool value = 1 [ - // (buf.validate.field).bool.example = 1, - // (buf.validate.field).bool.example = 2 - // ]; - // } - // ``` - repeated bool example = 2 [(shared_field).cel = { - id: "bool.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // StringRules describes the constraints applied to `string` values These @@ -3184,7 +2623,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.const = "hello"]; // } // ``` - optional string const = 1 [(shared_field).cel = { + optional string const = 1 [(priv.field).cel = { id: "string.const" expression: "this != rules.const ? 'value must equal `%s`'.format([rules.const]) : ''" }]; @@ -3200,7 +2639,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len = 5]; // } // ``` - optional uint64 len = 19 [(shared_field).cel = { + optional uint64 len = 19 [(priv.field).cel = { id: "string.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s characters'.format([rules.len]) : ''" }]; @@ -3216,7 +2655,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.min_len = 3]; // } // ``` - optional uint64 min_len = 2 [(shared_field).cel = { + optional uint64 min_len = 2 [(priv.field).cel = { id: "string.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s characters'.format([rules.min_len]) : ''" }]; @@ -3232,7 +2671,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_len = 10]; // } // ``` - optional uint64 max_len = 3 [(shared_field).cel = { + optional uint64 max_len = 3 [(priv.field).cel = { id: "string.max_len" expression: "uint(this.size()) > rules.max_len ? 'value length must be at most %s characters'.format([rules.max_len]) : ''" }]; @@ -3247,7 +2686,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len_bytes = 6]; // } // ``` - optional uint64 len_bytes = 20 [(shared_field).cel = { + optional uint64 len_bytes = 20 [(priv.field).cel = { id: "string.len_bytes" expression: "uint(bytes(this).size()) != rules.len_bytes ? 'value length must be %s bytes'.format([rules.len_bytes]) : ''" }]; @@ -3263,7 +2702,7 @@ message StringRules { // } // // ``` - optional uint64 min_bytes = 4 [(shared_field).cel = { + optional uint64 min_bytes = 4 [(priv.field).cel = { id: "string.min_bytes" expression: "uint(bytes(this).size()) < rules.min_bytes ? 'value length must be at least %s bytes'.format([rules.min_bytes]) : ''" }]; @@ -3278,7 +2717,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_bytes = 8]; // } // ``` - optional uint64 max_bytes = 5 [(shared_field).cel = { + optional uint64 max_bytes = 5 [(priv.field).cel = { id: "string.max_bytes" expression: "uint(bytes(this).size()) > rules.max_bytes ? 'value length must be at most %s bytes'.format([rules.max_bytes]) : ''" }]; @@ -3294,7 +2733,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z]//$"]; // } // ``` - optional string pattern = 6 [(shared_field).cel = { + optional string pattern = 6 [(priv.field).cel = { id: "string.pattern" expression: "!this.matches(rules.pattern) ? 'value does not match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -3310,7 +2749,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.prefix = "pre"]; // } // ``` - optional string prefix = 7 [(shared_field).cel = { + optional string prefix = 7 [(priv.field).cel = { id: "string.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix `%s`'.format([rules.prefix]) : ''" }]; @@ -3325,7 +2764,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.suffix = "post"]; // } // ``` - optional string suffix = 8 [(shared_field).cel = { + optional string suffix = 8 [(priv.field).cel = { id: "string.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix `%s`'.format([rules.suffix]) : ''" }]; @@ -3340,7 +2779,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.contains = "inside"]; // } // ``` - optional string contains = 9 [(shared_field).cel = { + optional string contains = 9 [(priv.field).cel = { id: "string.contains" expression: "!this.contains(rules.contains) ? 'value does not contain substring `%s`'.format([rules.contains]) : ''" }]; @@ -3355,7 +2794,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.not_contains = "inside"]; // } // ``` - optional string not_contains = 23 [(shared_field).cel = { + optional string not_contains = 23 [(priv.field).cel = { id: "string.not_contains" expression: "this.contains(rules.not_contains) ? 'value contains substring `%s`'.format([rules.not_contains]) : ''" }]; @@ -3370,7 +2809,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.in = "apple", (buf.validate.field).string.in = "banana"]; // } // ``` - repeated string in = 10 [(shared_field).cel = { + repeated string in = 10 [(priv.field).cel = { id: "string.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3384,7 +2823,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.not_in = "orange", (buf.validate.field).string.not_in = "grape"]; // } // ``` - repeated string not_in = 11 [(shared_field).cel = { + repeated string not_in = 11 [(priv.field).cel = { id: "string.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -3403,12 +2842,12 @@ message StringRules { // } // ``` bool email = 12 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.email" message: "value must be a valid email address" expression: "this == '' || this.isEmail()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.email_empty" message: "value is empty, which is not a valid email address" expression: "this != ''" @@ -3427,12 +2866,12 @@ message StringRules { // } // ``` bool hostname = 13 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.hostname" message: "value must be a valid hostname" expression: "this == '' || this.isHostname()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.hostname_empty" message: "value is empty, which is not a valid hostname" expression: "this != ''" @@ -3451,12 +2890,12 @@ message StringRules { // } // ``` bool ip = 14 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ip" message: "value must be a valid IP address" expression: "this == '' || this.isIp()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ip_empty" message: "value is empty, which is not a valid IP address" expression: "this != ''" @@ -3474,12 +2913,12 @@ message StringRules { // } // ``` bool ipv4 = 15 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4" message: "value must be a valid IPv4 address" expression: "this == '' || this.isIp(4)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "this != ''" @@ -3497,12 +2936,12 @@ message StringRules { // } // ``` bool ipv6 = 16 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6" message: "value must be a valid IPv6 address" expression: "this == '' || this.isIp(6)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "this != ''" @@ -3520,12 +2959,12 @@ message StringRules { // } // ``` bool uri = 17 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.uri" message: "value must be a valid URI" expression: "this == '' || this.isUri()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.uri_empty" message: "value is empty, which is not a valid URI" expression: "this != ''" @@ -3542,7 +2981,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.uri_ref = true]; // } // ``` - bool uri_ref = 18 [(shared_field).cel = { + bool uri_ref = 18 [(priv.field).cel = { id: "string.uri_ref" message: "value must be a valid URI" expression: "this.isUriRef()" @@ -3561,12 +3000,12 @@ message StringRules { // } // ``` bool address = 21 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.address" message: "value must be a valid hostname, or ip address" expression: "this == '' || this.isHostname() || this.isIp()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.address_empty" message: "value is empty, which is not a valid hostname, or ip address" expression: "this != ''" @@ -3584,12 +3023,12 @@ message StringRules { // } // ``` bool uuid = 22 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.uuid" message: "value must be a valid UUID" expression: "this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.uuid_empty" message: "value is empty, which is not a valid UUID" expression: "this != ''" @@ -3608,12 +3047,12 @@ message StringRules { // } // ``` bool tuuid = 33 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.tuuid" message: "value must be a valid trimmed UUID" expression: "this == '' || this.matches('^[0-9a-fA-F]{32}$')" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.tuuid_empty" message: "value is empty, which is not a valid trimmed UUID" expression: "this != ''" @@ -3632,12 +3071,12 @@ message StringRules { // } // ``` bool ip_with_prefixlen = 26 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ip_with_prefixlen" message: "value must be a valid IP prefix" expression: "this == '' || this.isIpPrefix()" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ip_with_prefixlen_empty" message: "value is empty, which is not a valid IP prefix" expression: "this != ''" @@ -3656,12 +3095,12 @@ message StringRules { // } // ``` bool ipv4_with_prefixlen = 27 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4_with_prefixlen" message: "value must be a valid IPv4 address with prefix length" expression: "this == '' || this.isIpPrefix(4)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4_with_prefixlen_empty" message: "value is empty, which is not a valid IPv4 address with prefix length" expression: "this != ''" @@ -3680,12 +3119,12 @@ message StringRules { // } // ``` bool ipv6_with_prefixlen = 28 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6_with_prefixlen" message: "value must be a valid IPv6 address with prefix length" expression: "this == '' || this.isIpPrefix(6)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6_with_prefixlen_empty" message: "value is empty, which is not a valid IPv6 address with prefix length" expression: "this != ''" @@ -3704,12 +3143,12 @@ message StringRules { // } // ``` bool ip_prefix = 29 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ip_prefix" message: "value must be a valid IP prefix" expression: "this == '' || this.isIpPrefix(true)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ip_prefix_empty" message: "value is empty, which is not a valid IP prefix" expression: "this != ''" @@ -3728,12 +3167,12 @@ message StringRules { // } // ``` bool ipv4_prefix = 30 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4_prefix" message: "value must be a valid IPv4 prefix" expression: "this == '' || this.isIpPrefix(4, true)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv4_prefix_empty" message: "value is empty, which is not a valid IPv4 prefix" expression: "this != ''" @@ -3752,12 +3191,12 @@ message StringRules { // } // ``` bool ipv6_prefix = 31 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6_prefix" message: "value must be a valid IPv6 prefix" expression: "this == '' || this.isIpPrefix(6, true)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.ipv6_prefix_empty" message: "value is empty, which is not a valid IPv6 prefix" expression: "this != ''" @@ -3769,12 +3208,12 @@ message StringRules { // must be in the range of 0-65535, inclusive. IPv6 addresses must be delimited // with square brackets (e.g., `[::1]:1234`). bool host_and_port = 32 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.host_and_port" message: "value must be a valid host (hostname or IP address) and port pair" expression: "this == '' || this.isHostAndPort(true)" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.host_and_port_empty" message: "value is empty, which is not a valid host and port pair" expression: "this != ''" @@ -3802,7 +3241,7 @@ message StringRules { // | KNOWN_REGEX_HTTP_HEADER_NAME | 1 | HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2) | // | KNOWN_REGEX_HTTP_HEADER_VALUE | 2 | HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4) | KnownRegex well_known_regex = 24 [ - (shared_field).cel = { + (priv.field).cel = { id: "string.well_known_regex.header_name" message: "value must be a valid HTTP header name" expression: @@ -3810,12 +3249,12 @@ message StringRules { "'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :" "'^[^\\u0000\\u000A\\u000D]+$')" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.well_known_regex.header_name_empty" message: "value is empty, which is not a valid HTTP header name" expression: "rules.well_known_regex != 1 || this != ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "string.well_known_regex.header_value" message: "value must be a valid HTTP header value" expression: @@ -3839,41 +3278,6 @@ message StringRules { // } // ``` optional bool strict = 25; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyString { - // string value = 1 [ - // (buf.validate.field).string.example = 1, - // (buf.validate.field).string.example = 2 - // ]; - // } - // ``` - repeated string example = 34 [(shared_field).cel = { - id: "string.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // WellKnownRegex contain some well-known patterns. @@ -3899,7 +3303,7 @@ message BytesRules { // bytes value = 1 [(buf.validate.field).bytes.const = "\x01\x02\x03\x04"]; // } // ``` - optional bytes const = 1 [(shared_field).cel = { + optional bytes const = 1 [(priv.field).cel = { id: "bytes.const" expression: "this != rules.const ? 'value must be %x'.format([rules.const]) : ''" }]; @@ -3913,7 +3317,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.len = 4]; // } // ``` - optional uint64 len = 13 [(shared_field).cel = { + optional uint64 len = 13 [(priv.field).cel = { id: "bytes.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s bytes'.format([rules.len]) : ''" }]; @@ -3928,7 +3332,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.min_len = 2]; // } // ``` - optional uint64 min_len = 2 [(shared_field).cel = { + optional uint64 min_len = 2 [(priv.field).cel = { id: "bytes.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s bytes'.format([rules.min_len]) : ''" }]; @@ -3943,7 +3347,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.max_len = 6]; // } // ``` - optional uint64 max_len = 3 [(shared_field).cel = { + optional uint64 max_len = 3 [(priv.field).cel = { id: "bytes.max_len" expression: "uint(this.size()) > rules.max_len ? 'value must be at most %s bytes'.format([rules.max_len]) : ''" }]; @@ -3960,7 +3364,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]+$"]; // } // ``` - optional string pattern = 4 [(shared_field).cel = { + optional string pattern = 4 [(priv.field).cel = { id: "bytes.pattern" expression: "!string(this).matches(rules.pattern) ? 'value must match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -3975,7 +3379,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.prefix = "\x01\x02"]; // } // ``` - optional bytes prefix = 5 [(shared_field).cel = { + optional bytes prefix = 5 [(priv.field).cel = { id: "bytes.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix %x'.format([rules.prefix]) : ''" }]; @@ -3990,7 +3394,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.suffix = "\x03\x04"]; // } // ``` - optional bytes suffix = 6 [(shared_field).cel = { + optional bytes suffix = 6 [(priv.field).cel = { id: "bytes.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix %x'.format([rules.suffix]) : ''" }]; @@ -4005,7 +3409,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; // } // ``` - optional bytes contains = 7 [(shared_field).cel = { + optional bytes contains = 7 [(priv.field).cel = { id: "bytes.contains" expression: "!this.contains(rules.contains) ? 'value does not contain %x'.format([rules.contains]) : ''" }]; @@ -4020,7 +3424,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes in = 8 [(shared_field).cel = { + repeated bytes in = 8 [(priv.field).cel = { id: "bytes.in" expression: "dyn(rules)['in'].size() > 0 && !(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -4036,7 +3440,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.not_in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes not_in = 9 [(shared_field).cel = { + repeated bytes not_in = 9 [(priv.field).cel = { id: "bytes.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -4054,12 +3458,12 @@ message BytesRules { // } // ``` bool ip = 10 [ - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ip" message: "value must be a valid IP address" expression: "this.size() == 0 || this.size() == 4 || this.size() == 16" }, - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ip_empty" message: "value is empty, which is not a valid IP address" expression: "this.size() != 0" @@ -4076,12 +3480,12 @@ message BytesRules { // } // ``` bool ipv4 = 11 [ - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ipv4" message: "value must be a valid IPv4 address" expression: "this.size() == 0 || this.size() == 4" }, - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" expression: "this.size() != 0" @@ -4097,53 +3501,18 @@ message BytesRules { // } // ``` bool ipv6 = 12 [ - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ipv6" message: "value must be a valid IPv6 address" expression: "this.size() == 0 || this.size() == 16" }, - (shared_field).cel = { + (priv.field).cel = { id: "bytes.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" expression: "this.size() != 0" } ]; } - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyBytes { - // bytes value = 1 [ - // (buf.validate.field).bytes.example = "\x01\x02", - // (buf.validate.field).bytes.example = "\x02\x03" - // ]; - // } - // ``` - repeated bytes example = 14 [(shared_field).cel = { - id: "bytes.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // EnumRules describe the constraints applied to `enum` values. @@ -4163,7 +3532,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum.const = 1]; // } // ``` - optional int32 const = 1 [(shared_field).cel = { + optional int32 const = 1 [(priv.field).cel = { id: "enum.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -4201,7 +3570,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { in: [1, 2]}]; // } // ``` - repeated int32 in = 3 [(shared_field).cel = { + repeated int32 in = 3 [(priv.field).cel = { id: "enum.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -4222,49 +3591,10 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { not_in: [1, 2]}]; // } // ``` - repeated int32 not_in = 4 [(shared_field).cel = { + repeated int32 not_in = 4 [(priv.field).cel = { id: "enum.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // enum MyEnum { - // MY_ENUM_UNSPECIFIED = 0; - // MY_ENUM_VALUE1 = 1; - // MY_ENUM_VALUE2 = 2; - // } - // - // message MyMessage { - // (buf.validate.field).enum.example = 1, - // (buf.validate.field).enum.example = 2 - // } - // ``` - repeated int32 example = 5 [(shared_field).cel = { - id: "enum.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // RepeatedRules describe the constraints applied to `repeated` values. @@ -4280,7 +3610,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.min_items = 2]; // } // ``` - optional uint64 min_items = 1 [(shared_field).cel = { + optional uint64 min_items = 1 [(priv.field).cel = { id: "repeated.min_items" expression: "uint(this.size()) < rules.min_items ? 'value must contain at least %d item(s)'.format([rules.min_items]) : ''" }]; @@ -4296,7 +3626,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.max_items = 3]; // } // ``` - optional uint64 max_items = 2 [(shared_field).cel = { + optional uint64 max_items = 2 [(priv.field).cel = { id: "repeated.max_items" expression: "uint(this.size()) > rules.max_items ? 'value must contain no more than %s item(s)'.format([rules.max_items]) : ''" }]; @@ -4311,7 +3641,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.unique = true]; // } // ``` - optional bool unique = 3 [(shared_field).cel = { + optional bool unique = 3 [(priv.field).cel = { id: "repeated.unique" message: "repeated value must contain unique items" expression: "this.unique()" @@ -4333,24 +3663,6 @@ message RepeatedRules { // } // ``` optional FieldConstraints items = 4; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // MapRules describe the constraints applied to `map` values. @@ -4364,7 +3676,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.min_pairs = 2]; // } // ``` - optional uint64 min_pairs = 1 [(shared_field).cel = { + optional uint64 min_pairs = 1 [(priv.field).cel = { id: "map.min_pairs" expression: "uint(this.size()) < rules.min_pairs ? 'map must be at least %d entries'.format([rules.min_pairs]) : ''" }]; @@ -4378,7 +3690,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.max_pairs = 3]; // } // ``` - optional uint64 max_pairs = 2 [(shared_field).cel = { + optional uint64 max_pairs = 2 [(priv.field).cel = { id: "map.max_pairs" expression: "uint(this.size()) > rules.max_pairs ? 'map must be at most %d entries'.format([rules.max_pairs]) : ''" }]; @@ -4414,24 +3726,6 @@ message MapRules { // } // ``` optional FieldConstraints values = 5; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // AnyRules describe constraints applied exclusively to the `google.protobuf.Any` well-known type. @@ -4471,7 +3765,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.const = "5s"]; // } // ``` - optional google.protobuf.Duration const = 2 [(shared_field).cel = { + optional google.protobuf.Duration const = 2 [(priv.field).cel = { id: "duration.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -4486,7 +3780,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = "5s"]; // } // ``` - google.protobuf.Duration lt = 3 [(shared_field).cel = { + google.protobuf.Duration lt = 3 [(priv.field).cel = { id: "duration.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -4503,7 +3797,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lte = "10s"]; // } // ``` - google.protobuf.Duration lte = 4 [(shared_field).cel = { + google.protobuf.Duration lte = 4 [(priv.field).cel = { id: "duration.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -4530,31 +3824,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gt = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "duration.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -4581,31 +3875,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gte = 6 [ - (shared_field).cel = { + (priv.field).cel = { id: "duration.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "duration.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -4624,7 +3918,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration in = 7 [(shared_field).cel = { + repeated google.protobuf.Duration in = 7 [(priv.field).cel = { id: "duration.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -4640,45 +3934,10 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.not_in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration not_in = 8 [(shared_field).cel = { + repeated google.protobuf.Duration not_in = 8 [(priv.field).cel = { id: "duration.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyDuration { - // google.protobuf.Duration value = 1 [ - // (buf.validate.field).duration.example = { seconds: 1 }, - // (buf.validate.field).duration.example = { seconds: 2 }, - // ]; - // } - // ``` - repeated google.protobuf.Duration example = 9 [(shared_field).cel = { - id: "duration.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } // TimestampRules describe the constraints applied exclusively to the `google.protobuf.Timestamp` well-known type. @@ -4691,7 +3950,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.const = {seconds: 1727998800}]; // } // ``` - optional google.protobuf.Timestamp const = 2 [(shared_field).cel = { + optional google.protobuf.Timestamp const = 2 [(priv.field).cel = { id: "timestamp.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -4704,7 +3963,7 @@ message TimestampRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = { seconds: 259200 }]; // } // ``` - google.protobuf.Timestamp lt = 3 [(shared_field).cel = { + google.protobuf.Timestamp lt = 3 [(priv.field).cel = { id: "timestamp.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -4719,7 +3978,7 @@ message TimestampRules { // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.lte = { seconds: 1678867200 }]; // } // ``` - google.protobuf.Timestamp lte = 4 [(shared_field).cel = { + google.protobuf.Timestamp lte = 4 [(priv.field).cel = { id: "timestamp.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -4734,7 +3993,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.lt_now = true]; // } // ``` - bool lt_now = 7 [(shared_field).cel = { + bool lt_now = 7 [(priv.field).cel = { id: "timestamp.lt_now" expression: "this > now ? 'value must be less than now' : ''" }]; @@ -4759,31 +4018,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gt = 5 [ - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -4810,31 +4069,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gte = 6 [ - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (shared_field).cel = { + (priv.field).cel = { id: "timestamp.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -4850,7 +4109,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.gt_now = true]; // } // ``` - bool gt_now = 8 [(shared_field).cel = { + bool gt_now = 8 [(priv.field).cel = { id: "timestamp.gt_now" expression: "this < now ? 'value must be greater than now' : ''" }]; @@ -4864,44 +4123,8 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.within = {seconds: 3600}]; // } // ``` - optional google.protobuf.Duration within = 9 [(shared_field).cel = { + optional google.protobuf.Duration within = 9 [(priv.field).cel = { id: "timestamp.within" expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''" }]; - - // `example` specifies values that the field may have. These values SHOULD - // conform to other constraints. `example` values will not impact validation - // but may be used as helpful guidance on how to populate the given field. - // - // ```proto - // message MyTimestamp { - // google.protobuf.Timestamp value = 1 [ - // (buf.validate.field).timestamp.example = { seconds: 1672444800 }, - // (buf.validate.field).timestamp.example = { seconds: 1672531200 }, - // ]; - // } - // ``` - - repeated google.protobuf.Timestamp example = 10 [(shared_field).cel = { - id: "timestamp.example" - expression: "true" - }]; - - // Reserved for shared rules using extension numbers defined within the - // [Protobuf Global Extension Registry][1]. Extension fields in this range - // that have the (buf.validate.shared_field) option set will be treated as - // shared field constraints that can then be set on field options of other - // messages to apply reusable field constraints. - // - // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md - extensions 1000 to 49999; - - // Reserved for shared rules using extension numbers defined using a random - // number between 50000 and 536870911. Extensions in this range that have the - // (shared_field) option set will be treated as shared field constraints that - // can then be set on field options of other messages to apply reusable field - // constraints. - // - // It is recommended to use a random number generator, such as random.org. - extensions 50000 to max; } From c6486f47e87553af0e86d8a037414efe15e03cd8 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Wed, 25 Sep 2024 14:22:33 -0400 Subject: [PATCH 18/30] Fix CI for real --- make/buf/all.mk | 2 +- .../buf/validate/expression.proto | 92 -- .../buf/validate/priv/private.proto | 41 - .../protovalidate/buf/validate/validate.proto | 1335 ++++++++++++----- 4 files changed, 997 insertions(+), 473 deletions(-) delete mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto delete mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto diff --git a/make/buf/all.mk b/make/buf/all.mk index 4f0aa08257..d22f9054ac 100644 --- a/make/buf/all.mk +++ b/make/buf/all.mk @@ -43,7 +43,7 @@ LICENSE_HEADER_LICENSE_TYPE := apache LICENSE_HEADER_COPYRIGHT_HOLDER := Buf Technologies, Inc. LICENSE_HEADER_YEAR_RANGE := 2020-2024 LICENSE_HEADER_IGNORES := \/testdata enterprise -PROTOVALIDATE_VERSION := v0.7.1 +PROTOVALIDATE_VERSION := v0.8.1 # Comment out to use released buf BUF_GO_INSTALL_PATH := ./cmd/buf diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto deleted file mode 100644 index 72ce36dcd8..0000000000 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/expression.proto +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2023 Buf Technologies, Inc. -// -// 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. - -syntax = "proto3"; - -package buf.validate; - -option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; -option java_multiple_files = true; -option java_outer_classname = "ExpressionProto"; -option java_package = "build.buf.validate"; - -// `Constraint` represents a validation rule written in the Common Expression -// Language (CEL) syntax. Each Constraint includes a unique identifier, an -// optional error message, and the CEL expression to evaluate. For more -// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). -// -// ```proto -// message Foo { -// option (buf.validate.message).cel = { -// id: "foo.bar" -// message: "bar must be greater than 0" -// expression: "this.bar > 0" -// }; -// int32 bar = 1; -// } -// ``` -message Constraint { - // `id` is a string that serves as a machine-readable name for this Constraint. - // It should be unique within its scope, which could be either a message or a field. - string id = 1; - - // `message` is an optional field that provides a human-readable error message - // for this Constraint when the CEL expression evaluates to false. If a - // non-empty message is provided, any strings resulting from the CEL - // expression evaluation are ignored. - string message = 2; - - // `expression` is the actual CEL expression that will be evaluated for - // validation. This string must resolve to either a boolean or a string - // value. If the expression evaluates to false or a non-empty string, the - // validation is considered failed, and the message is rejected. - string expression = 3; -} - -// `Violations` is a collection of `Violation` messages. This message type is returned by -// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. -// Each individual violation is represented by a `Violation` message. -message Violations { - // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. - repeated Violation violations = 1; -} - -// `Violation` represents a single instance where a validation rule, expressed -// as a `Constraint`, was not met. It provides information about the field that -// caused the violation, the specific constraint that wasn't fulfilled, and a -// human-readable error message. -// -// ```json -// { -// "fieldPath": "bar", -// "constraintId": "foo.bar", -// "message": "bar must be greater than 0" -// } -// ``` -message Violation { - // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. - // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. - string field_path = 1; - - // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. - // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. - string constraint_id = 2; - - // `message` is a human-readable error message that describes the nature of the violation. - // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. - string message = 3; - - // `for_key` indicates whether the violation was caused by a map key, rather than a value. - bool for_key = 4; -} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto deleted file mode 100644 index ddaf938a61..0000000000 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/priv/private.proto +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2023 Buf Technologies, Inc. -// -// 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. - -syntax = "proto3"; - -package buf.validate.priv; - -import "google/protobuf/descriptor.proto"; - -option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate/priv"; -option java_multiple_files = true; -option java_outer_classname = "PrivateProto"; -option java_package = "build.buf.validate.priv"; - -extend google.protobuf.FieldOptions { - // Do not use. Internal to protovalidate library - optional FieldConstraints field = 1160; -} - -// Do not use. Internal to protovalidate library -message FieldConstraints { - repeated Constraint cel = 1; -} - -// Do not use. Internal to protovalidate library -message Constraint { - string id = 1; - string message = 2; - string expression = 3; -} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto index fcc8cac448..7236347aa3 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate/buf/validate/validate.proto @@ -12,12 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -syntax = "proto3"; +syntax = "proto2"; package buf.validate; -import "buf/validate/expression.proto"; -import "buf/validate/priv/private.proto"; import "google/protobuf/descriptor.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -55,6 +53,57 @@ extend google.protobuf.FieldOptions { // Rules specify the validations to be performed on this field. By default, // no validation is performed against a field. optional FieldConstraints field = 1159; + + // Specifies predefined rules. When extending a standard constraint message, + // this adds additional CEL expressions that apply when the extension is used. + // + // ```proto + // extend buf.validate.Int32Rules { + // bool is_zero [(buf.validate.predefined).cel = { + // id: "int32.is_zero", + // message: "value must be zero", + // expression: "!rule || this == 0", + // }]; + // } + // + // message Foo { + // int32 reserved = 1 [(buf.validate.field).int32.(is_zero) = true]; + // } + // ``` + optional PredefinedConstraints predefined = 1160; +} + +// `Constraint` represents a validation rule written in the Common Expression +// Language (CEL) syntax. Each Constraint includes a unique identifier, an +// optional error message, and the CEL expression to evaluate. For more +// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). +// +// ```proto +// message Foo { +// option (buf.validate.message).cel = { +// id: "foo.bar" +// message: "bar must be greater than 0" +// expression: "this.bar > 0" +// }; +// int32 bar = 1; +// } +// ``` +message Constraint { + // `id` is a string that serves as a machine-readable name for this Constraint. + // It should be unique within its scope, which could be either a message or a field. + optional string id = 1; + + // `message` is an optional field that provides a human-readable error message + // for this Constraint when the CEL expression evaluates to false. If a + // non-empty message is provided, any strings resulting from the CEL + // expression evaluation are ignored. + optional string message = 2; + + // `expression` is the actual CEL expression that will be evaluated for + // validation. This string must resolve to either a boolean or a string + // value. If the expression evaluates to false or a non-empty string, the + // validation is considered failed, and the message is rejected. + optional string expression = 3; } // MessageConstraints represents validation rules that are applied to the entire message. @@ -148,7 +197,7 @@ message FieldConstraints { // optional MyOtherMessage value = 1 [(buf.validate.field).required = true]; // } // ``` - bool required = 25; + optional bool required = 25; // Skip validation on the field if its value matches the specified criteria. // See Ignore enum for details. // @@ -162,7 +211,7 @@ message FieldConstraints { // ]; // } // ``` - Ignore ignore = 27; + optional Ignore ignore = 27; oneof type { // Scalar Field Types @@ -194,9 +243,29 @@ message FieldConstraints { } // DEPRECATED: use ignore=IGNORE_ALWAYS instead. TODO: remove this field pre-v1. - bool skipped = 24 [deprecated = true]; + optional bool skipped = 24 [deprecated = true]; // DEPRECATED: use ignore=IGNORE_IF_UNPOPULATED instead. TODO: remove this field pre-v1. - bool ignore_empty = 26 [deprecated = true]; + optional bool ignore_empty = 26 [deprecated = true]; +} + +// PredefinedConstraints are custom constraints that can be re-used with +// multiple fields. +message PredefinedConstraints { + // `cel` is a repeated field used to represent a textual expression + // in the Common Expression Language (CEL) syntax. For more information on + // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). + // + // ```proto + // message MyMessage { + // // The field `value` must be greater than 42. + // optional int32 value = 1 [(buf.validate.predefined).cel = { + // id: "my_message.value", + // message: "value must be greater than 42", + // expression: "this > 42", + // }]; + // } + // ``` + repeated Constraint cel = 1; } // Specifies how FieldConstraints.ignore behaves. See the documentation for @@ -365,7 +434,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.const = 42.0]; // } // ``` - optional float const = 1 [(priv.field).cel = { + optional float const = 1 [(predefined).cel = { id: "float.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -381,7 +450,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lt = 10.0]; // } // ``` - float lt = 2 [(priv.field).cel = { + float lt = 2 [(predefined).cel = { id: "float.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -398,7 +467,7 @@ message FloatRules { // float value = 1 [(buf.validate.field).float.lte = 10.0]; // } // ``` - float lte = 3 [(priv.field).cel = { + float lte = 3 [(predefined).cel = { id: "float.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -426,31 +495,31 @@ message FloatRules { // } // ``` float gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "float.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -477,31 +546,31 @@ message FloatRules { // } // ``` float gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "float.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "float.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -520,7 +589,7 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float in = 6 [(priv.field).cel = { + repeated float in = 6 [(predefined).cel = { id: "float.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -535,17 +604,46 @@ message FloatRules { // repeated float value = 1 (buf.validate.field).float = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated float not_in = 7 [(priv.field).cel = { + repeated float not_in = 7 [(predefined).cel = { id: "float.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - bool finite = 8 [(priv.field).cel = { + optional bool finite = 8 [(predefined).cel = { id: "float.finite" - expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" + expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFloat { + // float value = 1 [ + // (buf.validate.field).float.example = 1.0, + // (buf.validate.field).float.example = "Infinity" + // ]; + // } + // ``` + repeated float example = 9 [(predefined).cel = { + id: "float.example" + expression: "true" }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // DoubleRules describes the constraints applied to `double` values. These @@ -560,7 +658,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.const = 42.0]; // } // ``` - optional double const = 1 [(priv.field).cel = { + optional double const = 1 [(predefined).cel = { id: "double.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -575,7 +673,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lt = 10.0]; // } // ``` - double lt = 2 [(priv.field).cel = { + double lt = 2 [(predefined).cel = { id: "double.lt" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" @@ -592,7 +690,7 @@ message DoubleRules { // double value = 1 [(buf.validate.field).double.lte = 10.0]; // } // ``` - double lte = 3 [(priv.field).cel = { + double lte = 3 [(predefined).cel = { id: "double.lte" expression: "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" @@ -619,31 +717,31 @@ message DoubleRules { // } // ``` double gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "double.gt" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" @@ -670,31 +768,31 @@ message DoubleRules { // } // ``` double gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "double.gte" expression: "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "double.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" @@ -712,7 +810,7 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double in = 6 [(priv.field).cel = { + repeated double in = 6 [(predefined).cel = { id: "double.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -727,17 +825,46 @@ message DoubleRules { // repeated double value = 1 (buf.validate.field).double = { not_in: [1.0, 2.0, 3.0] }; // } // ``` - repeated double not_in = 7 [(priv.field).cel = { + repeated double not_in = 7 [(predefined).cel = { id: "double.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; // `finite` requires the field value to be finite. If the field value is // infinite or NaN, an error message is generated. - bool finite = 8 [(priv.field).cel = { + optional bool finite = 8 [(predefined).cel = { id: "double.finite" - expression: "this.isNan() || this.isInf() ? 'value must be finite' : ''" + expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDouble { + // double value = 1 [ + // (buf.validate.field).double.example = 1.0, + // (buf.validate.field).double.example = "Infinity" + // ]; + // } + // ``` + repeated double example = 9 [(predefined).cel = { + id: "double.example" + expression: "true" }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // Int32Rules describes the constraints applied to `int32` values. These @@ -752,7 +879,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.const = 42]; // } // ``` - optional int32 const = 1 [(priv.field).cel = { + optional int32 const = 1 [(predefined).cel = { id: "int32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -767,7 +894,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lt = 10]; // } // ``` - int32 lt = 2 [(priv.field).cel = { + int32 lt = 2 [(predefined).cel = { id: "int32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -784,7 +911,7 @@ message Int32Rules { // int32 value = 1 [(buf.validate.field).int32.lte = 10]; // } // ``` - int32 lte = 3 [(priv.field).cel = { + int32 lte = 3 [(predefined).cel = { id: "int32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -811,31 +938,31 @@ message Int32Rules { // } // ``` int32 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "int32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -862,31 +989,31 @@ message Int32Rules { // } // ``` int32 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "int32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -905,7 +1032,7 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { in: [1, 2, 3] }; // } // ``` - repeated int32 in = 6 [(priv.field).cel = { + repeated int32 in = 6 [(predefined).cel = { id: "int32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -920,10 +1047,39 @@ message Int32Rules { // repeated int32 value = 1 (buf.validate.field).int32 = { not_in: [1, 2, 3] }; // } // ``` - repeated int32 not_in = 7 [(priv.field).cel = { + repeated int32 not_in = 7 [(predefined).cel = { id: "int32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt32 { + // int32 value = 1 [ + // (buf.validate.field).int32.example = 1, + // (buf.validate.field).int32.example = -10 + // ]; + // } + // ``` + repeated int32 example = 8 [(predefined).cel = { + id: "int32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // Int64Rules describes the constraints applied to `int64` values. These @@ -938,7 +1094,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.const = 42]; // } // ``` - optional int64 const = 1 [(priv.field).cel = { + optional int64 const = 1 [(predefined).cel = { id: "int64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -953,7 +1109,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lt = 10]; // } // ``` - int64 lt = 2 [(priv.field).cel = { + int64 lt = 2 [(predefined).cel = { id: "int64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -970,7 +1126,7 @@ message Int64Rules { // int64 value = 1 [(buf.validate.field).int64.lte = 10]; // } // ``` - int64 lte = 3 [(priv.field).cel = { + int64 lte = 3 [(predefined).cel = { id: "int64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -997,31 +1153,31 @@ message Int64Rules { // } // ``` int64 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "int64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1048,31 +1204,31 @@ message Int64Rules { // } // ``` int64 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "int64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "int64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1091,7 +1247,7 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { in: [1, 2, 3] }; // } // ``` - repeated int64 in = 6 [(priv.field).cel = { + repeated int64 in = 6 [(predefined).cel = { id: "int64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1106,10 +1262,39 @@ message Int64Rules { // repeated int64 value = 1 (buf.validate.field).int64 = { not_in: [1, 2, 3] }; // } // ``` - repeated int64 not_in = 7 [(priv.field).cel = { + repeated int64 not_in = 7 [(predefined).cel = { id: "int64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt64 { + // int64 value = 1 [ + // (buf.validate.field).int64.example = 1, + // (buf.validate.field).int64.example = -10 + // ]; + // } + // ``` + repeated int64 example = 9 [(predefined).cel = { + id: "int64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // UInt32Rules describes the constraints applied to `uint32` values. These @@ -1124,7 +1309,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.const = 42]; // } // ``` - optional uint32 const = 1 [(priv.field).cel = { + optional uint32 const = 1 [(predefined).cel = { id: "uint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1139,7 +1324,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lt = 10]; // } // ``` - uint32 lt = 2 [(priv.field).cel = { + uint32 lt = 2 [(predefined).cel = { id: "uint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1156,7 +1341,7 @@ message UInt32Rules { // uint32 value = 1 [(buf.validate.field).uint32.lte = 10]; // } // ``` - uint32 lte = 3 [(priv.field).cel = { + uint32 lte = 3 [(predefined).cel = { id: "uint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1183,31 +1368,31 @@ message UInt32Rules { // } // ``` uint32 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "uint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1234,31 +1419,31 @@ message UInt32Rules { // } // ``` uint32 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "uint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1277,7 +1462,7 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { in: [1, 2, 3] }; // } // ``` - repeated uint32 in = 6 [(priv.field).cel = { + repeated uint32 in = 6 [(predefined).cel = { id: "uint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1292,10 +1477,39 @@ message UInt32Rules { // repeated uint32 value = 1 (buf.validate.field).uint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint32 not_in = 7 [(priv.field).cel = { + repeated uint32 not_in = 7 [(predefined).cel = { id: "uint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt32 { + // uint32 value = 1 [ + // (buf.validate.field).uint32.example = 1, + // (buf.validate.field).uint32.example = 10 + // ]; + // } + // ``` + repeated uint32 example = 8 [(predefined).cel = { + id: "uint32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // UInt64Rules describes the constraints applied to `uint64` values. These @@ -1310,7 +1524,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.const = 42]; // } // ``` - optional uint64 const = 1 [(priv.field).cel = { + optional uint64 const = 1 [(predefined).cel = { id: "uint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1325,7 +1539,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lt = 10]; // } // ``` - uint64 lt = 2 [(priv.field).cel = { + uint64 lt = 2 [(predefined).cel = { id: "uint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1342,7 +1556,7 @@ message UInt64Rules { // uint64 value = 1 [(buf.validate.field).uint64.lte = 10]; // } // ``` - uint64 lte = 3 [(priv.field).cel = { + uint64 lte = 3 [(predefined).cel = { id: "uint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1369,31 +1583,31 @@ message UInt64Rules { // } // ``` uint64 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "uint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1420,31 +1634,31 @@ message UInt64Rules { // } // ``` uint64 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "uint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "uint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1462,7 +1676,7 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { in: [1, 2, 3] }; // } // ``` - repeated uint64 in = 6 [(priv.field).cel = { + repeated uint64 in = 6 [(predefined).cel = { id: "uint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1477,10 +1691,39 @@ message UInt64Rules { // repeated uint64 value = 1 (buf.validate.field).uint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated uint64 not_in = 7 [(priv.field).cel = { + repeated uint64 not_in = 7 [(predefined).cel = { id: "uint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt64 { + // uint64 value = 1 [ + // (buf.validate.field).uint64.example = 1, + // (buf.validate.field).uint64.example = -10 + // ]; + // } + // ``` + repeated uint64 example = 8 [(predefined).cel = { + id: "uint64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // SInt32Rules describes the constraints applied to `sint32` values. @@ -1494,7 +1737,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.const = 42]; // } // ``` - optional sint32 const = 1 [(priv.field).cel = { + optional sint32 const = 1 [(predefined).cel = { id: "sint32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1509,7 +1752,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lt = 10]; // } // ``` - sint32 lt = 2 [(priv.field).cel = { + sint32 lt = 2 [(predefined).cel = { id: "sint32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1526,7 +1769,7 @@ message SInt32Rules { // sint32 value = 1 [(buf.validate.field).sint32.lte = 10]; // } // ``` - sint32 lte = 3 [(priv.field).cel = { + sint32 lte = 3 [(predefined).cel = { id: "sint32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1553,31 +1796,31 @@ message SInt32Rules { // } // ``` sint32 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "sint32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1604,31 +1847,31 @@ message SInt32Rules { // } // ``` sint32 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "sint32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1647,7 +1890,7 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { in: [1, 2, 3] }; // } // ``` - repeated sint32 in = 6 [(priv.field).cel = { + repeated sint32 in = 6 [(predefined).cel = { id: "sint32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1662,10 +1905,39 @@ message SInt32Rules { // repeated sint32 value = 1 (buf.validate.field).sint32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint32 not_in = 7 [(priv.field).cel = { + repeated sint32 not_in = 7 [(predefined).cel = { id: "sint32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt32 { + // sint32 value = 1 [ + // (buf.validate.field).sint32.example = 1, + // (buf.validate.field).sint32.example = -10 + // ]; + // } + // ``` + repeated sint32 example = 8 [(predefined).cel = { + id: "sint32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // SInt64Rules describes the constraints applied to `sint64` values. @@ -1679,7 +1951,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.const = 42]; // } // ``` - optional sint64 const = 1 [(priv.field).cel = { + optional sint64 const = 1 [(predefined).cel = { id: "sint64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1694,7 +1966,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lt = 10]; // } // ``` - sint64 lt = 2 [(priv.field).cel = { + sint64 lt = 2 [(predefined).cel = { id: "sint64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1711,7 +1983,7 @@ message SInt64Rules { // sint64 value = 1 [(buf.validate.field).sint64.lte = 10]; // } // ``` - sint64 lte = 3 [(priv.field).cel = { + sint64 lte = 3 [(predefined).cel = { id: "sint64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1738,31 +2010,31 @@ message SInt64Rules { // } // ``` sint64 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "sint64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1789,31 +2061,31 @@ message SInt64Rules { // } // ``` sint64 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "sint64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sint64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -1832,7 +2104,7 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { in: [1, 2, 3] }; // } // ``` - repeated sint64 in = 6 [(priv.field).cel = { + repeated sint64 in = 6 [(predefined).cel = { id: "sint64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -1847,10 +2119,39 @@ message SInt64Rules { // repeated sint64 value = 1 (buf.validate.field).sint64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sint64 not_in = 7 [(priv.field).cel = { + repeated sint64 not_in = 7 [(predefined).cel = { id: "sint64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt64 { + // sint64 value = 1 [ + // (buf.validate.field).sint64.example = 1, + // (buf.validate.field).sint64.example = -10 + // ]; + // } + // ``` + repeated sint64 example = 8 [(predefined).cel = { + id: "sint64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // Fixed32Rules describes the constraints applied to `fixed32` values. @@ -1864,7 +2165,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.const = 42]; // } // ``` - optional fixed32 const = 1 [(priv.field).cel = { + optional fixed32 const = 1 [(predefined).cel = { id: "fixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -1879,7 +2180,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lt = 10]; // } // ``` - fixed32 lt = 2 [(priv.field).cel = { + fixed32 lt = 2 [(predefined).cel = { id: "fixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -1896,7 +2197,7 @@ message Fixed32Rules { // fixed32 value = 1 [(buf.validate.field).fixed32.lte = 10]; // } // ``` - fixed32 lte = 3 [(priv.field).cel = { + fixed32 lte = 3 [(predefined).cel = { id: "fixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -1923,31 +2224,31 @@ message Fixed32Rules { // } // ``` fixed32 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -1974,31 +2275,31 @@ message Fixed32Rules { // } // ``` fixed32 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2017,7 +2318,7 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { in: [1, 2, 3] }; // } // ``` - repeated fixed32 in = 6 [(priv.field).cel = { + repeated fixed32 in = 6 [(predefined).cel = { id: "fixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2032,10 +2333,39 @@ message Fixed32Rules { // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed32 not_in = 7 [(priv.field).cel = { + repeated fixed32 not_in = 7 [(predefined).cel = { id: "fixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed32 { + // fixed32 value = 1 [ + // (buf.validate.field).fixed32.example = 1, + // (buf.validate.field).fixed32.example = 2 + // ]; + // } + // ``` + repeated fixed32 example = 8 [(predefined).cel = { + id: "fixed32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // Fixed64Rules describes the constraints applied to `fixed64` values. @@ -2049,7 +2379,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.const = 42]; // } // ``` - optional fixed64 const = 1 [(priv.field).cel = { + optional fixed64 const = 1 [(predefined).cel = { id: "fixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2064,7 +2394,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lt = 10]; // } // ``` - fixed64 lt = 2 [(priv.field).cel = { + fixed64 lt = 2 [(predefined).cel = { id: "fixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2081,7 +2411,7 @@ message Fixed64Rules { // fixed64 value = 1 [(buf.validate.field).fixed64.lte = 10]; // } // ``` - fixed64 lte = 3 [(priv.field).cel = { + fixed64 lte = 3 [(predefined).cel = { id: "fixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2108,31 +2438,31 @@ message Fixed64Rules { // } // ``` fixed64 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2159,31 +2489,31 @@ message Fixed64Rules { // } // ``` fixed64 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "fixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2202,7 +2532,7 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { in: [1, 2, 3] }; // } // ``` - repeated fixed64 in = 6 [(priv.field).cel = { + repeated fixed64 in = 6 [(predefined).cel = { id: "fixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2217,10 +2547,39 @@ message Fixed64Rules { // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated fixed64 not_in = 7 [(priv.field).cel = { + repeated fixed64 not_in = 7 [(predefined).cel = { id: "fixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed64 { + // fixed64 value = 1 [ + // (buf.validate.field).fixed64.example = 1, + // (buf.validate.field).fixed64.example = 2 + // ]; + // } + // ``` + repeated fixed64 example = 8 [(predefined).cel = { + id: "fixed64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // SFixed32Rules describes the constraints applied to `fixed32` values. @@ -2234,7 +2593,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.const = 42]; // } // ``` - optional sfixed32 const = 1 [(priv.field).cel = { + optional sfixed32 const = 1 [(predefined).cel = { id: "sfixed32.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2249,7 +2608,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lt = 10]; // } // ``` - sfixed32 lt = 2 [(priv.field).cel = { + sfixed32 lt = 2 [(predefined).cel = { id: "sfixed32.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2266,7 +2625,7 @@ message SFixed32Rules { // sfixed32 value = 1 [(buf.validate.field).sfixed32.lte = 10]; // } // ``` - sfixed32 lte = 3 [(priv.field).cel = { + sfixed32 lte = 3 [(predefined).cel = { id: "sfixed32.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2293,31 +2652,31 @@ message SFixed32Rules { // } // ``` sfixed32 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2344,31 +2703,31 @@ message SFixed32Rules { // } // ``` sfixed32 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed32.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2387,7 +2746,7 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed32 in = 6 [(priv.field).cel = { + repeated sfixed32 in = 6 [(predefined).cel = { id: "sfixed32.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2402,10 +2761,39 @@ message SFixed32Rules { // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed32 not_in = 7 [(priv.field).cel = { + repeated sfixed32 not_in = 7 [(predefined).cel = { id: "sfixed32.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed32 { + // sfixed32 value = 1 [ + // (buf.validate.field).sfixed32.example = 1, + // (buf.validate.field).sfixed32.example = 2 + // ]; + // } + // ``` + repeated sfixed32 example = 8 [(predefined).cel = { + id: "sfixed32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // SFixed64Rules describes the constraints applied to `fixed64` values. @@ -2419,7 +2807,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.const = 42]; // } // ``` - optional sfixed64 const = 1 [(priv.field).cel = { + optional sfixed64 const = 1 [(predefined).cel = { id: "sfixed64.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -2434,7 +2822,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lt = 10]; // } // ``` - sfixed64 lt = 2 [(priv.field).cel = { + sfixed64 lt = 2 [(predefined).cel = { id: "sfixed64.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -2451,7 +2839,7 @@ message SFixed64Rules { // sfixed64 value = 1 [(buf.validate.field).sfixed64.lte = 10]; // } // ``` - sfixed64 lte = 3 [(priv.field).cel = { + sfixed64 lte = 3 [(predefined).cel = { id: "sfixed64.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -2478,31 +2866,31 @@ message SFixed64Rules { // } // ``` sfixed64 gt = 4 [ - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -2529,31 +2917,31 @@ message SFixed64Rules { // } // ``` sfixed64 gte = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "sfixed64.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -2572,7 +2960,7 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { in: [1, 2, 3] }; // } // ``` - repeated sfixed64 in = 6 [(priv.field).cel = { + repeated sfixed64 in = 6 [(predefined).cel = { id: "sfixed64.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2587,10 +2975,39 @@ message SFixed64Rules { // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { not_in: [1, 2, 3] }; // } // ``` - repeated sfixed64 not_in = 7 [(priv.field).cel = { + repeated sfixed64 not_in = 7 [(predefined).cel = { id: "sfixed64.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed64 { + // sfixed64 value = 1 [ + // (buf.validate.field).sfixed64.example = 1, + // (buf.validate.field).sfixed64.example = 2 + // ]; + // } + // ``` + repeated sfixed64 example = 8 [(predefined).cel = { + id: "sfixed64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // BoolRules describes the constraints applied to `bool` values. These rules @@ -2605,10 +3022,39 @@ message BoolRules { // bool value = 1 [(buf.validate.field).bool.const = true]; // } // ``` - optional bool const = 1 [(priv.field).cel = { + optional bool const = 1 [(predefined).cel = { id: "bool.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBool { + // bool value = 1 [ + // (buf.validate.field).bool.example = 1, + // (buf.validate.field).bool.example = 2 + // ]; + // } + // ``` + repeated bool example = 2 [(predefined).cel = { + id: "bool.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // StringRules describes the constraints applied to `string` values These @@ -2623,7 +3069,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.const = "hello"]; // } // ``` - optional string const = 1 [(priv.field).cel = { + optional string const = 1 [(predefined).cel = { id: "string.const" expression: "this != rules.const ? 'value must equal `%s`'.format([rules.const]) : ''" }]; @@ -2639,7 +3085,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len = 5]; // } // ``` - optional uint64 len = 19 [(priv.field).cel = { + optional uint64 len = 19 [(predefined).cel = { id: "string.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s characters'.format([rules.len]) : ''" }]; @@ -2655,7 +3101,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.min_len = 3]; // } // ``` - optional uint64 min_len = 2 [(priv.field).cel = { + optional uint64 min_len = 2 [(predefined).cel = { id: "string.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s characters'.format([rules.min_len]) : ''" }]; @@ -2671,7 +3117,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_len = 10]; // } // ``` - optional uint64 max_len = 3 [(priv.field).cel = { + optional uint64 max_len = 3 [(predefined).cel = { id: "string.max_len" expression: "uint(this.size()) > rules.max_len ? 'value length must be at most %s characters'.format([rules.max_len]) : ''" }]; @@ -2686,7 +3132,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.len_bytes = 6]; // } // ``` - optional uint64 len_bytes = 20 [(priv.field).cel = { + optional uint64 len_bytes = 20 [(predefined).cel = { id: "string.len_bytes" expression: "uint(bytes(this).size()) != rules.len_bytes ? 'value length must be %s bytes'.format([rules.len_bytes]) : ''" }]; @@ -2702,7 +3148,7 @@ message StringRules { // } // // ``` - optional uint64 min_bytes = 4 [(priv.field).cel = { + optional uint64 min_bytes = 4 [(predefined).cel = { id: "string.min_bytes" expression: "uint(bytes(this).size()) < rules.min_bytes ? 'value length must be at least %s bytes'.format([rules.min_bytes]) : ''" }]; @@ -2717,7 +3163,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.max_bytes = 8]; // } // ``` - optional uint64 max_bytes = 5 [(priv.field).cel = { + optional uint64 max_bytes = 5 [(predefined).cel = { id: "string.max_bytes" expression: "uint(bytes(this).size()) > rules.max_bytes ? 'value length must be at most %s bytes'.format([rules.max_bytes]) : ''" }]; @@ -2733,7 +3179,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z]//$"]; // } // ``` - optional string pattern = 6 [(priv.field).cel = { + optional string pattern = 6 [(predefined).cel = { id: "string.pattern" expression: "!this.matches(rules.pattern) ? 'value does not match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -2749,7 +3195,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.prefix = "pre"]; // } // ``` - optional string prefix = 7 [(priv.field).cel = { + optional string prefix = 7 [(predefined).cel = { id: "string.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix `%s`'.format([rules.prefix]) : ''" }]; @@ -2764,7 +3210,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.suffix = "post"]; // } // ``` - optional string suffix = 8 [(priv.field).cel = { + optional string suffix = 8 [(predefined).cel = { id: "string.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix `%s`'.format([rules.suffix]) : ''" }]; @@ -2779,7 +3225,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.contains = "inside"]; // } // ``` - optional string contains = 9 [(priv.field).cel = { + optional string contains = 9 [(predefined).cel = { id: "string.contains" expression: "!this.contains(rules.contains) ? 'value does not contain substring `%s`'.format([rules.contains]) : ''" }]; @@ -2794,7 +3240,7 @@ message StringRules { // string value = 1 [(buf.validate.field).string.not_contains = "inside"]; // } // ``` - optional string not_contains = 23 [(priv.field).cel = { + optional string not_contains = 23 [(predefined).cel = { id: "string.not_contains" expression: "this.contains(rules.not_contains) ? 'value contains substring `%s`'.format([rules.not_contains]) : ''" }]; @@ -2809,7 +3255,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.in = "apple", (buf.validate.field).string.in = "banana"]; // } // ``` - repeated string in = 10 [(priv.field).cel = { + repeated string in = 10 [(predefined).cel = { id: "string.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -2823,7 +3269,7 @@ message StringRules { // repeated string value = 1 [(buf.validate.field).string.not_in = "orange", (buf.validate.field).string.not_in = "grape"]; // } // ``` - repeated string not_in = 11 [(priv.field).cel = { + repeated string not_in = 11 [(predefined).cel = { id: "string.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -2842,15 +3288,15 @@ message StringRules { // } // ``` bool email = 12 [ - (priv.field).cel = { + (predefined).cel = { id: "string.email" message: "value must be a valid email address" - expression: "this == '' || this.isEmail()" + expression: "!rules.email || this == '' || this.isEmail()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.email_empty" message: "value is empty, which is not a valid email address" - expression: "this != ''" + expression: "!rules.email || this != ''" } ]; @@ -2866,15 +3312,15 @@ message StringRules { // } // ``` bool hostname = 13 [ - (priv.field).cel = { + (predefined).cel = { id: "string.hostname" message: "value must be a valid hostname" - expression: "this == '' || this.isHostname()" + expression: "!rules.hostname || this == '' || this.isHostname()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.hostname_empty" message: "value is empty, which is not a valid hostname" - expression: "this != ''" + expression: "!rules.hostname || this != ''" } ]; @@ -2890,15 +3336,15 @@ message StringRules { // } // ``` bool ip = 14 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ip" message: "value must be a valid IP address" - expression: "this == '' || this.isIp()" + expression: "!rules.ip || this == '' || this.isIp()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ip_empty" message: "value is empty, which is not a valid IP address" - expression: "this != ''" + expression: "!rules.ip || this != ''" } ]; @@ -2913,15 +3359,15 @@ message StringRules { // } // ``` bool ipv4 = 15 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4" message: "value must be a valid IPv4 address" - expression: "this == '' || this.isIp(4)" + expression: "!rules.ipv4 || this == '' || this.isIp(4)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" - expression: "this != ''" + expression: "!rules.ipv4 || this != ''" } ]; @@ -2936,15 +3382,15 @@ message StringRules { // } // ``` bool ipv6 = 16 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6" message: "value must be a valid IPv6 address" - expression: "this == '' || this.isIp(6)" + expression: "!rules.ipv6 || this == '' || this.isIp(6)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" - expression: "this != ''" + expression: "!rules.ipv6 || this != ''" } ]; @@ -2959,15 +3405,15 @@ message StringRules { // } // ``` bool uri = 17 [ - (priv.field).cel = { + (predefined).cel = { id: "string.uri" message: "value must be a valid URI" - expression: "this == '' || this.isUri()" + expression: "!rules.uri || this == '' || this.isUri()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.uri_empty" message: "value is empty, which is not a valid URI" - expression: "this != ''" + expression: "!rules.uri || this != ''" } ]; @@ -2981,10 +3427,10 @@ message StringRules { // string value = 1 [(buf.validate.field).string.uri_ref = true]; // } // ``` - bool uri_ref = 18 [(priv.field).cel = { + bool uri_ref = 18 [(predefined).cel = { id: "string.uri_ref" message: "value must be a valid URI" - expression: "this.isUriRef()" + expression: "!rules.uri_ref || this.isUriRef()" }]; // `address` specifies that the field value must be either a valid hostname @@ -3000,15 +3446,15 @@ message StringRules { // } // ``` bool address = 21 [ - (priv.field).cel = { + (predefined).cel = { id: "string.address" message: "value must be a valid hostname, or ip address" - expression: "this == '' || this.isHostname() || this.isIp()" + expression: "!rules.address || this == '' || this.isHostname() || this.isIp()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.address_empty" message: "value is empty, which is not a valid hostname, or ip address" - expression: "this != ''" + expression: "!rules.address || this != ''" } ]; @@ -3023,15 +3469,15 @@ message StringRules { // } // ``` bool uuid = 22 [ - (priv.field).cel = { + (predefined).cel = { id: "string.uuid" message: "value must be a valid UUID" - expression: "this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" + expression: "!rules.uuid || this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" }, - (priv.field).cel = { + (predefined).cel = { id: "string.uuid_empty" message: "value is empty, which is not a valid UUID" - expression: "this != ''" + expression: "!rules.uuid || this != ''" } ]; @@ -3047,15 +3493,15 @@ message StringRules { // } // ``` bool tuuid = 33 [ - (priv.field).cel = { + (predefined).cel = { id: "string.tuuid" message: "value must be a valid trimmed UUID" - expression: "this == '' || this.matches('^[0-9a-fA-F]{32}$')" + expression: "!rules.tuuid || this == '' || this.matches('^[0-9a-fA-F]{32}$')" }, - (priv.field).cel = { + (predefined).cel = { id: "string.tuuid_empty" message: "value is empty, which is not a valid trimmed UUID" - expression: "this != ''" + expression: "!rules.tuuid || this != ''" } ]; @@ -3071,15 +3517,15 @@ message StringRules { // } // ``` bool ip_with_prefixlen = 26 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ip_with_prefixlen" message: "value must be a valid IP prefix" - expression: "this == '' || this.isIpPrefix()" + expression: "!rules.ip_with_prefixlen || this == '' || this.isIpPrefix()" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ip_with_prefixlen_empty" message: "value is empty, which is not a valid IP prefix" - expression: "this != ''" + expression: "!rules.ip_with_prefixlen || this != ''" } ]; @@ -3095,15 +3541,15 @@ message StringRules { // } // ``` bool ipv4_with_prefixlen = 27 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4_with_prefixlen" message: "value must be a valid IPv4 address with prefix length" - expression: "this == '' || this.isIpPrefix(4)" + expression: "!rules.ipv4_with_prefixlen || this == '' || this.isIpPrefix(4)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4_with_prefixlen_empty" message: "value is empty, which is not a valid IPv4 address with prefix length" - expression: "this != ''" + expression: "!rules.ipv4_with_prefixlen || this != ''" } ]; @@ -3119,15 +3565,15 @@ message StringRules { // } // ``` bool ipv6_with_prefixlen = 28 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6_with_prefixlen" message: "value must be a valid IPv6 address with prefix length" - expression: "this == '' || this.isIpPrefix(6)" + expression: "!rules.ipv6_with_prefixlen || this == '' || this.isIpPrefix(6)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6_with_prefixlen_empty" message: "value is empty, which is not a valid IPv6 address with prefix length" - expression: "this != ''" + expression: "!rules.ipv6_with_prefixlen || this != ''" } ]; @@ -3143,15 +3589,15 @@ message StringRules { // } // ``` bool ip_prefix = 29 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ip_prefix" message: "value must be a valid IP prefix" - expression: "this == '' || this.isIpPrefix(true)" + expression: "!rules.ip_prefix || this == '' || this.isIpPrefix(true)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ip_prefix_empty" message: "value is empty, which is not a valid IP prefix" - expression: "this != ''" + expression: "!rules.ip_prefix || this != ''" } ]; @@ -3167,15 +3613,15 @@ message StringRules { // } // ``` bool ipv4_prefix = 30 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4_prefix" message: "value must be a valid IPv4 prefix" - expression: "this == '' || this.isIpPrefix(4, true)" + expression: "!rules.ipv4_prefix || this == '' || this.isIpPrefix(4, true)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv4_prefix_empty" message: "value is empty, which is not a valid IPv4 prefix" - expression: "this != ''" + expression: "!rules.ipv4_prefix || this != ''" } ]; @@ -3191,15 +3637,15 @@ message StringRules { // } // ``` bool ipv6_prefix = 31 [ - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6_prefix" message: "value must be a valid IPv6 prefix" - expression: "this == '' || this.isIpPrefix(6, true)" + expression: "!rules.ipv6_prefix || this == '' || this.isIpPrefix(6, true)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.ipv6_prefix_empty" message: "value is empty, which is not a valid IPv6 prefix" - expression: "this != ''" + expression: "!rules.ipv6_prefix || this != ''" } ]; @@ -3208,15 +3654,15 @@ message StringRules { // must be in the range of 0-65535, inclusive. IPv6 addresses must be delimited // with square brackets (e.g., `[::1]:1234`). bool host_and_port = 32 [ - (priv.field).cel = { + (predefined).cel = { id: "string.host_and_port" message: "value must be a valid host (hostname or IP address) and port pair" - expression: "this == '' || this.isHostAndPort(true)" + expression: "!rules.host_and_port || this == '' || this.isHostAndPort(true)" }, - (priv.field).cel = { + (predefined).cel = { id: "string.host_and_port_empty" message: "value is empty, which is not a valid host and port pair" - expression: "this != ''" + expression: "!rules.host_and_port || this != ''" } ]; @@ -3241,7 +3687,7 @@ message StringRules { // | KNOWN_REGEX_HTTP_HEADER_NAME | 1 | HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2) | // | KNOWN_REGEX_HTTP_HEADER_VALUE | 2 | HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4) | KnownRegex well_known_regex = 24 [ - (priv.field).cel = { + (predefined).cel = { id: "string.well_known_regex.header_name" message: "value must be a valid HTTP header name" expression: @@ -3249,12 +3695,12 @@ message StringRules { "'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :" "'^[^\\u0000\\u000A\\u000D]+$')" }, - (priv.field).cel = { + (predefined).cel = { id: "string.well_known_regex.header_name_empty" message: "value is empty, which is not a valid HTTP header name" expression: "rules.well_known_regex != 1 || this != ''" }, - (priv.field).cel = { + (predefined).cel = { id: "string.well_known_regex.header_value" message: "value must be a valid HTTP header value" expression: @@ -3278,6 +3724,35 @@ message StringRules { // } // ``` optional bool strict = 25; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyString { + // string value = 1 [ + // (buf.validate.field).string.example = 1, + // (buf.validate.field).string.example = 2 + // ]; + // } + // ``` + repeated string example = 34 [(predefined).cel = { + id: "string.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // WellKnownRegex contain some well-known patterns. @@ -3303,7 +3778,7 @@ message BytesRules { // bytes value = 1 [(buf.validate.field).bytes.const = "\x01\x02\x03\x04"]; // } // ``` - optional bytes const = 1 [(priv.field).cel = { + optional bytes const = 1 [(predefined).cel = { id: "bytes.const" expression: "this != rules.const ? 'value must be %x'.format([rules.const]) : ''" }]; @@ -3317,7 +3792,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.len = 4]; // } // ``` - optional uint64 len = 13 [(priv.field).cel = { + optional uint64 len = 13 [(predefined).cel = { id: "bytes.len" expression: "uint(this.size()) != rules.len ? 'value length must be %s bytes'.format([rules.len]) : ''" }]; @@ -3332,7 +3807,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.min_len = 2]; // } // ``` - optional uint64 min_len = 2 [(priv.field).cel = { + optional uint64 min_len = 2 [(predefined).cel = { id: "bytes.min_len" expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s bytes'.format([rules.min_len]) : ''" }]; @@ -3347,7 +3822,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.max_len = 6]; // } // ``` - optional uint64 max_len = 3 [(priv.field).cel = { + optional uint64 max_len = 3 [(predefined).cel = { id: "bytes.max_len" expression: "uint(this.size()) > rules.max_len ? 'value must be at most %s bytes'.format([rules.max_len]) : ''" }]; @@ -3364,7 +3839,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]+$"]; // } // ``` - optional string pattern = 4 [(priv.field).cel = { + optional string pattern = 4 [(predefined).cel = { id: "bytes.pattern" expression: "!string(this).matches(rules.pattern) ? 'value must match regex pattern `%s`'.format([rules.pattern]) : ''" }]; @@ -3379,7 +3854,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.prefix = "\x01\x02"]; // } // ``` - optional bytes prefix = 5 [(priv.field).cel = { + optional bytes prefix = 5 [(predefined).cel = { id: "bytes.prefix" expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix %x'.format([rules.prefix]) : ''" }]; @@ -3394,7 +3869,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.suffix = "\x03\x04"]; // } // ``` - optional bytes suffix = 6 [(priv.field).cel = { + optional bytes suffix = 6 [(predefined).cel = { id: "bytes.suffix" expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix %x'.format([rules.suffix]) : ''" }]; @@ -3409,7 +3884,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; // } // ``` - optional bytes contains = 7 [(priv.field).cel = { + optional bytes contains = 7 [(predefined).cel = { id: "bytes.contains" expression: "!this.contains(rules.contains) ? 'value does not contain %x'.format([rules.contains]) : ''" }]; @@ -3424,7 +3899,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes in = 8 [(priv.field).cel = { + repeated bytes in = 8 [(predefined).cel = { id: "bytes.in" expression: "dyn(rules)['in'].size() > 0 && !(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3440,7 +3915,7 @@ message BytesRules { // optional bytes value = 1 [(buf.validate.field).bytes.not_in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; // } // ``` - repeated bytes not_in = 9 [(priv.field).cel = { + repeated bytes not_in = 9 [(predefined).cel = { id: "bytes.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; @@ -3458,15 +3933,15 @@ message BytesRules { // } // ``` bool ip = 10 [ - (priv.field).cel = { + (predefined).cel = { id: "bytes.ip" message: "value must be a valid IP address" - expression: "this.size() == 0 || this.size() == 4 || this.size() == 16" + expression: "!rules.ip || this.size() == 0 || this.size() == 4 || this.size() == 16" }, - (priv.field).cel = { + (predefined).cel = { id: "bytes.ip_empty" message: "value is empty, which is not a valid IP address" - expression: "this.size() != 0" + expression: "!rules.ip || this.size() != 0" } ]; @@ -3480,15 +3955,15 @@ message BytesRules { // } // ``` bool ipv4 = 11 [ - (priv.field).cel = { + (predefined).cel = { id: "bytes.ipv4" message: "value must be a valid IPv4 address" - expression: "this.size() == 0 || this.size() == 4" + expression: "!rules.ipv4 || this.size() == 0 || this.size() == 4" }, - (priv.field).cel = { + (predefined).cel = { id: "bytes.ipv4_empty" message: "value is empty, which is not a valid IPv4 address" - expression: "this.size() != 0" + expression: "!rules.ipv4 || this.size() != 0" } ]; @@ -3501,18 +3976,47 @@ message BytesRules { // } // ``` bool ipv6 = 12 [ - (priv.field).cel = { + (predefined).cel = { id: "bytes.ipv6" message: "value must be a valid IPv6 address" - expression: "this.size() == 0 || this.size() == 16" + expression: "!rules.ipv6 || this.size() == 0 || this.size() == 16" }, - (priv.field).cel = { + (predefined).cel = { id: "bytes.ipv6_empty" message: "value is empty, which is not a valid IPv6 address" - expression: "this.size() != 0" + expression: "!rules.ipv6 || this.size() != 0" } ]; } + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBytes { + // bytes value = 1 [ + // (buf.validate.field).bytes.example = "\x01\x02", + // (buf.validate.field).bytes.example = "\x02\x03" + // ]; + // } + // ``` + repeated bytes example = 14 [(predefined).cel = { + id: "bytes.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // EnumRules describe the constraints applied to `enum` values. @@ -3532,7 +4036,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum.const = 1]; // } // ``` - optional int32 const = 1 [(priv.field).cel = { + optional int32 const = 1 [(predefined).cel = { id: "enum.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3570,7 +4074,7 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { in: [1, 2]}]; // } // ``` - repeated int32 in = 3 [(priv.field).cel = { + repeated int32 in = 3 [(predefined).cel = { id: "enum.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3591,10 +4095,43 @@ message EnumRules { // MyEnum value = 1 [(buf.validate.field).enum = { not_in: [1, 2]}]; // } // ``` - repeated int32 not_in = 4 [(priv.field).cel = { + repeated int32 not_in = 4 [(predefined).cel = { id: "enum.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // (buf.validate.field).enum.example = 1, + // (buf.validate.field).enum.example = 2 + // } + // ``` + repeated int32 example = 5 [(predefined).cel = { + id: "enum.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // RepeatedRules describe the constraints applied to `repeated` values. @@ -3610,7 +4147,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.min_items = 2]; // } // ``` - optional uint64 min_items = 1 [(priv.field).cel = { + optional uint64 min_items = 1 [(predefined).cel = { id: "repeated.min_items" expression: "uint(this.size()) < rules.min_items ? 'value must contain at least %d item(s)'.format([rules.min_items]) : ''" }]; @@ -3626,7 +4163,7 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.max_items = 3]; // } // ``` - optional uint64 max_items = 2 [(priv.field).cel = { + optional uint64 max_items = 2 [(predefined).cel = { id: "repeated.max_items" expression: "uint(this.size()) > rules.max_items ? 'value must contain no more than %s item(s)'.format([rules.max_items]) : ''" }]; @@ -3641,10 +4178,10 @@ message RepeatedRules { // repeated string value = 1 [(buf.validate.field).repeated.unique = true]; // } // ``` - optional bool unique = 3 [(priv.field).cel = { + optional bool unique = 3 [(predefined).cel = { id: "repeated.unique" message: "repeated value must contain unique items" - expression: "this.unique()" + expression: "!rules.unique || this.unique()" }]; // `items` details the constraints to be applied to each item @@ -3663,6 +4200,18 @@ message RepeatedRules { // } // ``` optional FieldConstraints items = 4; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // MapRules describe the constraints applied to `map` values. @@ -3676,7 +4225,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.min_pairs = 2]; // } // ``` - optional uint64 min_pairs = 1 [(priv.field).cel = { + optional uint64 min_pairs = 1 [(predefined).cel = { id: "map.min_pairs" expression: "uint(this.size()) < rules.min_pairs ? 'map must be at least %d entries'.format([rules.min_pairs]) : ''" }]; @@ -3690,7 +4239,7 @@ message MapRules { // map value = 1 [(buf.validate.field).map.max_pairs = 3]; // } // ``` - optional uint64 max_pairs = 2 [(priv.field).cel = { + optional uint64 max_pairs = 2 [(predefined).cel = { id: "map.max_pairs" expression: "uint(this.size()) > rules.max_pairs ? 'map must be at most %d entries'.format([rules.max_pairs]) : ''" }]; @@ -3726,6 +4275,18 @@ message MapRules { // } // ``` optional FieldConstraints values = 5; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // AnyRules describe constraints applied exclusively to the `google.protobuf.Any` well-known type. @@ -3765,7 +4326,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.const = "5s"]; // } // ``` - optional google.protobuf.Duration const = 2 [(priv.field).cel = { + optional google.protobuf.Duration const = 2 [(predefined).cel = { id: "duration.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3780,7 +4341,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = "5s"]; // } // ``` - google.protobuf.Duration lt = 3 [(priv.field).cel = { + google.protobuf.Duration lt = 3 [(predefined).cel = { id: "duration.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -3797,7 +4358,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lte = "10s"]; // } // ``` - google.protobuf.Duration lte = 4 [(priv.field).cel = { + google.protobuf.Duration lte = 4 [(predefined).cel = { id: "duration.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -3824,31 +4385,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gt = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "duration.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -3875,31 +4436,31 @@ message DurationRules { // } // ``` google.protobuf.Duration gte = 6 [ - (priv.field).cel = { + (predefined).cel = { id: "duration.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "duration.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -3918,7 +4479,7 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration in = 7 [(priv.field).cel = { + repeated google.protobuf.Duration in = 7 [(predefined).cel = { id: "duration.in" expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" }]; @@ -3934,10 +4495,39 @@ message DurationRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.not_in = ["1s", "2s", "3s"]]; // } // ``` - repeated google.protobuf.Duration not_in = 8 [(priv.field).cel = { + repeated google.protobuf.Duration not_in = 8 [(predefined).cel = { id: "duration.not_in" expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDuration { + // google.protobuf.Duration value = 1 [ + // (buf.validate.field).duration.example = { seconds: 1 }, + // (buf.validate.field).duration.example = { seconds: 2 }, + // ]; + // } + // ``` + repeated google.protobuf.Duration example = 9 [(predefined).cel = { + id: "duration.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; } // TimestampRules describe the constraints applied exclusively to the `google.protobuf.Timestamp` well-known type. @@ -3950,7 +4540,7 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.const = {seconds: 1727998800}]; // } // ``` - optional google.protobuf.Timestamp const = 2 [(priv.field).cel = { + optional google.protobuf.Timestamp const = 2 [(predefined).cel = { id: "timestamp.const" expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" }]; @@ -3963,7 +4553,7 @@ message TimestampRules { // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = { seconds: 259200 }]; // } // ``` - google.protobuf.Timestamp lt = 3 [(priv.field).cel = { + google.protobuf.Timestamp lt = 3 [(predefined).cel = { id: "timestamp.lt" expression: "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" @@ -3978,7 +4568,7 @@ message TimestampRules { // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.lte = { seconds: 1678867200 }]; // } // ``` - google.protobuf.Timestamp lte = 4 [(priv.field).cel = { + google.protobuf.Timestamp lte = 4 [(predefined).cel = { id: "timestamp.lte" expression: "!has(rules.gte) && !has(rules.gt) && this > rules.lte" @@ -3993,9 +4583,9 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.lt_now = true]; // } // ``` - bool lt_now = 7 [(priv.field).cel = { + bool lt_now = 7 [(predefined).cel = { id: "timestamp.lt_now" - expression: "this > now ? 'value must be less than now' : ''" + expression: "(rules.lt_now && this > now) ? 'value must be less than now' : ''" }]; } oneof greater_than { @@ -4018,31 +4608,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gt = 5 [ - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gt" expression: "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" "? 'value must be greater than %s'.format([rules.gt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gt_lt" expression: "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gt_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gt_lte" expression: "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gt_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" @@ -4069,31 +4659,31 @@ message TimestampRules { // } // ``` google.protobuf.Timestamp gte = 6 [ - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gte" expression: "!has(rules.lt) && !has(rules.lte) && this < rules.gte" "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gte_lt" expression: "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gte_lt_exclusive" expression: "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gte_lte" expression: "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" }, - (priv.field).cel = { + (predefined).cel = { id: "timestamp.gte_lte_exclusive" expression: "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" @@ -4109,9 +4699,9 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.gt_now = true]; // } // ``` - bool gt_now = 8 [(priv.field).cel = { + bool gt_now = 8 [(predefined).cel = { id: "timestamp.gt_now" - expression: "this < now ? 'value must be greater than now' : ''" + expression: "(rules.gt_now && this < now) ? 'value must be greater than now' : ''" }]; } @@ -4123,8 +4713,75 @@ message TimestampRules { // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.within = {seconds: 3600}]; // } // ``` - optional google.protobuf.Duration within = 9 [(priv.field).cel = { + optional google.protobuf.Duration within = 9 [(predefined).cel = { id: "timestamp.within" expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''" }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyTimestamp { + // google.protobuf.Timestamp value = 1 [ + // (buf.validate.field).timestamp.example = { seconds: 1672444800 }, + // (buf.validate.field).timestamp.example = { seconds: 1672531200 }, + // ]; + // } + // ``` + + repeated google.protobuf.Timestamp example = 10 [(predefined).cel = { + id: "timestamp.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// `Violations` is a collection of `Violation` messages. This message type is returned by +// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. +// Each individual violation is represented by a `Violation` message. +message Violations { + // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. + repeated Violation violations = 1; +} + +// `Violation` represents a single instance where a validation rule, expressed +// as a `Constraint`, was not met. It provides information about the field that +// caused the violation, the specific constraint that wasn't fulfilled, and a +// human-readable error message. +// +// ```json +// { +// "fieldPath": "bar", +// "constraintId": "foo.bar", +// "message": "bar must be greater than 0" +// } +// ``` +message Violation { + // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. + // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. + optional string field_path = 1; + + // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. + // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. + optional string constraint_id = 2; + + // `message` is a human-readable error message that describes the nature of the violation. + // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. + optional string message = 3; + + // `for_key` indicates whether the violation was caused by a map key, rather than a value. + optional bool for_key = 4; } From bae26dd195484f33f890002fb469be2cd0bf627a Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Wed, 25 Sep 2024 18:22:18 -0400 Subject: [PATCH 19/30] Small refactors and address in-line TODOs --- .../internal/bufcheckserverhandle/lint.go | 168 +++++++++--------- .../buflintvalidate/buflintvalidate.go | 7 +- .../internal/buflintvalidate/field.go | 20 +-- .../buflintvalidate/predefined_rules.go | 52 ++++-- .../internal/buflintvalidate/util.go | 67 ++++++- 5 files changed, 192 insertions(+), 122 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index 491458c09c..8674021df2 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -936,91 +936,99 @@ func handleLintPackageVersionSuffix( } // HandleLintProtovalidate is a handle function. -var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler( - func( - ctx context.Context, - responseWriter bufcheckserverutil.ResponseWriter, - request bufcheckserverutil.Request, - ) error { - // TODO: refactor so that add func is no longer needed - addAnnotationFunc := func( - _ bufprotosource.Descriptor, - location bufprotosource.Location, - _ []bufprotosource.Location, - format string, - args ...interface{}, - ) { - responseWriter.AddProtosourceAnnotation( - location, - nil, - format, - args..., - ) - } - extensionTypesFromRequest := new(protoregistry.Types) - // This for-loop checks that shared rules' cel expressions compile and add - // those that compile to the extension types, as a side effect. These types - // will be useful later on when example values are checked. Therefore, this - // loop must run before field and messages are checked. - for _, file := range request.ProtosourceFiles() { - // Regardless whether its file is an import, we want to add a shared rule - // extension to the types registry, as long as the extension's cel - // expressions compile. - // TODO: add a test where a shared rule is defined in an import and a - // non-import uses this rule. - // TODO: pass a nop addFunc the file is an import, - if err := bufprotosource.ForEachMessage( - func(message bufprotosource.Message) error { - for _, extension := range message.Extensions() { - if err := buflintvalidate.CheckAndRegisterSharedRuleExtension( - addAnnotationFunc, - extension, - extensionTypesFromRequest, - ); err != nil { - return nil - } +var HandleLintProtovalidate = bufcheckserverutil.NewRuleHandler(handleLintProtovalidate) + +// handleLintProtovalidate runs checks all predefined rules, message rules, and field rules. +// +// NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint. +func handleLintProtovalidate( + ctx context.Context, + responseWriter bufcheckserverutil.ResponseWriter, + request bufcheckserverutil.Request, +) error { + // TODO: addAnnotationFunc is used to set add annotations to responseWriter. A follow-up + // will be made to refactor the code so we no longer need this. + addAnnotationFunc := func( + _ bufprotosource.Descriptor, + location bufprotosource.Location, + _ []bufprotosource.Location, + format string, + args ...interface{}, + ) { + responseWriter.AddProtosourceAnnotation( + location, + nil, + format, + args..., + ) + } + // Predefined rules are checked first because predefined rules from all files, are added + // to an extension resolver and used to resolve rules when checking fields. + extensionTypesFromRequest := new(protoregistry.Types) + // This for-loop checks that predefined rules have cel expressions that compile and adds + // the ones that compile to the extension resolver, as a side effect. These types are relied + // on to check the example values for fields. + for _, file := range request.ProtosourceFiles() { + // We check all predefined rules for all files and add them to the extension resolver + // if they compile, regardless if the file is an import or not. This is because a non-import + // file may use a predefined rule from an import file. + // However, we only add check annotations for non-import files. + if err := bufprotosource.ForEachMessage( + func(message bufprotosource.Message) error { + for _, extension := range message.Extensions() { + if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( + addAnnotationFunc, + extension, + extensionTypesFromRequest, + file.IsImport(), + ); err != nil { + return err } - return nil - }, - file, - ); err != nil { - return err - } - for _, extension := range file.Extensions() { - if err := buflintvalidate.CheckAndRegisterSharedRuleExtension( - addAnnotationFunc, - extension, - extensionTypesFromRequest, - ); err != nil { - return nil } - } - } - if err := bufcheckserverutil.NewLintMessageRuleHandler( - func( - _ bufcheckserverutil.ResponseWriter, - _ bufcheckserverutil.Request, - message bufprotosource.Message, - ) error { - return buflintvalidate.CheckMessage(addAnnotationFunc, message) - }).Handle(ctx, responseWriter, request); err != nil { + return nil + }, + file, + ); err != nil { return err } - // At this point the extension types are already populated. - if err := bufcheckserverutil.NewLintFieldRuleHandler( - func( - _ bufcheckserverutil.ResponseWriter, - _ bufcheckserverutil.Request, - field bufprotosource.Field, - ) error { - return buflintvalidate.CheckField(addAnnotationFunc, field, extensionTypesFromRequest) - }).Handle(ctx, responseWriter, request); err != nil { - return err + for _, extension := range file.Extensions() { + if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( + addAnnotationFunc, + extension, + extensionTypesFromRequest, + file.IsImport(), + ); err != nil { + return err + } } - // NOTE: Oneofs also have protovalidate support, but they only have a "required" field, so nothing to lint. - return nil - }, -) + } + if err := bufcheckserverutil.NewLintMessageRuleHandler( + func( + // The responseWriter is being passed in through the shared addAnnotationFunc, so we + // do not pass in responseWriter and request again. This should be addressed in a refactor. + _ bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.Request, + message bufprotosource.Message, + ) error { + return buflintvalidate.CheckMessage(addAnnotationFunc, message) + }).Handle(ctx, nil, nil); err != nil { + return err + } + // At this point the extension types are already populated. + if err := bufcheckserverutil.NewLintFieldRuleHandler( + func( + // The responseWriter is being passed in through the shared addAnnotationFunc, so we + // do not pass in responseWriter and request again. This should be addressed in a refactor. + _ bufcheckserverutil.ResponseWriter, + _ bufcheckserverutil.Request, + field bufprotosource.Field, + ) error { + return buflintvalidate.CheckField(addAnnotationFunc, field, extensionTypesFromRequest) + }).Handle(ctx, nil, nil); err != nil { + return err + } + return nil +} // HandleLintRPCNoClientStreaming is a handle function. var HandleLintRPCNoClientStreaming = bufcheckserverutil.NewLintMethodRuleHandler(handleLintRPCNoClientStreaming) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index e1ee8a9d08..9d97658de6 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -31,15 +31,16 @@ type ExtensionTypeResolver interface { FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) } -// CheckAndRegisterSharedRuleExtension checks whether an extension extending a protovalidate rule +// CheckAndRegisterPredefinedRuleExtension checks whether an extension extending a protovalidate rule // is valid, checking that all of its CEL expressionus compile. If so, the extension type is added to // the extension types passed in. -func CheckAndRegisterSharedRuleExtension( +func CheckAndRegisterPredefinedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, extensionTypesToPopulate *protoregistry.Types, + fileIsImport bool, ) error { - return checkAndRegisterSharedRuleExtension(addAnnotationFunc, field, extensionTypesToPopulate) + return checkAndRegisterPredefinedRuleExtension(addAnnotationFunc, field, extensionTypesToPopulate, fileIsImport) } // CheckMessage validates that all rules on the message are valid, and any CEL expressions compile. diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 0ef22ce042..3679b3ec11 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -98,6 +98,8 @@ const ( ltNowFieldNumberInTimestampRules = 7 gtNowFieldNumberInTimestampRules = 8 withInFieldNumberInTimestampRules = 9 + + exampleName = "example" ) var ( @@ -179,7 +181,6 @@ func checkField( func checkConstraintsForField( adder *adder, fieldConstraints *validate.FieldConstraints, - // TODO: fix wording // This is needed because recursive calls of this function still need the same // containing message. For example, checkConstraintsForField(.., fieldDescriptor, ...) // may call checkConstraintsForField(..., fieldDescriptor.MapKey(), ...), but the map @@ -241,8 +242,7 @@ func checkConstraintsForField( var exampleValues []protoreflect.Value var exampleFieldNumber int32 typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, value protoreflect.Value) bool { - // TODO: make "example" a const - if string(fd.Name()) == "example" { + if string(fd.Name()) == exampleName { exampleFieldNumber = int32(fd.Number()) // This assumed all *Rules.Example are repeated, otherwise it panics. list := value.List() @@ -770,10 +770,9 @@ func checkExampleValues( return err } hasConstraints := len(fieldConstraints.GetCel()) > 0 - // TODO: add a test where only shared rules and examples are specified if !hasConstraints { typeRulesMessage.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { - if string(fd.Name()) != "example" { + if string(fd.Name()) != exampleName { hasConstraints = true return false } @@ -811,13 +810,12 @@ func checkExampleValues( return err } // The shape of field path in a protovalidate.Violation depends on the type of the field descriptor. - // TODO: verify that this can be relied on. violationFilterFunc := func(violation *validate.Violation) bool { return violation.GetFieldPath() == string(fieldDescriptor.Name()) } switch { case fieldDescriptor.IsList(): - // Field path looks like repeate_field[10] + // Field path looks like repeated_field[10] violationFilterFunc = func(violation *validate.Violation) bool { prefix := fieldDescriptor.Name() + "[" suffix := "]" @@ -841,7 +839,6 @@ func checkExampleValues( return strings.HasPrefix(fieldPath, string(prefix)) && strings.HasSuffix(fieldPath, suffix) && !violation.GetForKey() } } - // TODO: test it for repeated min item for exampleValueIndex, exampleValue := range exampleValues { messageToValidate := dynamicpb.NewMessage(containingMessageDescriptor) switch { @@ -902,13 +899,6 @@ func checkExampleValues( } continue } - runtimeError := &protovalidate.RuntimeError{} - if errors.As(err, &runtimeError) { - // TODO: what to do here? return an error? - // TODO: see if i can test this with zero division - adder.addForPathf(append(pathToExampleValues, int32(exampleValueIndex)), "example fail at runtime: %s", runtimeError.Error()) - continue - } return fmt.Errorf("unexpected error from protovalidate: %s", err.Error()) } return nil diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index def7309e42..e1c2deb94c 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -19,16 +19,22 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/protovalidate-go/celext" "github.com/google/cel-go/cel" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/dynamicpb" ) -func checkAndRegisterSharedRuleExtension( +const ( + celFieldNumberPath = int32(1) +) + +func checkAndRegisterPredefinedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), extension bufprotosource.Field, extensionTypesToPopulate *protoregistry.Types, + fileIsImport bool, ) error { extensionDescriptor, err := extension.AsDescriptor() if err != nil { @@ -48,36 +54,37 @@ func checkAndRegisterSharedRuleExtension( if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { return nil } - sharedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined) - if sharedConstraints == nil { + predefinedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined) + if predefinedConstraints == nil { return nil } celEnv, err := celext.DefaultEnv(false) if err != nil { return err } - // Two keywords need type declaration, "this" and "rule", for the expression to compile. + // In order to evaluate whether the CEL expression for the rule compiles, we need to check + // the type declaration for two keywords, "this" and "rule". + // "this" refers to the type the rule is checking, e.g. StringRules would have type string. + // "rule" refers to the type of the rule extension, e.g. a rule that checks the length + // of a string has type int32 to represent the length. + // // In this example, an int32 field is added to extend string rules, and therefore, // "rule" has type int32 and "this" has type "string": // // extend buf.validate.StringRules { - // optional int32 my_max = 47892 [(buf.validate.shared_field).cel = { + // optional int32 my_max = 47892 [(buf.validate.predefined).cel = { // id: "mymax" // message: "at most max" // expression: "size(this) < rule" // }]; // } // - ruleType := celext.ProtoFieldToCELType(extensionDescriptor, false, false) // TODO: forItems should probably be false? - // This is a bit of hacky, relying on the fact that each *Rules has a const rule, - // and we take advantage of each buf.validate.Rules.const has type , which - // is the type "this" should have. - ruleConstFieldDescriptor := extendedStandardRuleDescriptor.Fields().ByName("const") - if ruleConstFieldDescriptor == nil { - // This isn't necessarily an error, it could be caused by a future buf.validate.*Rules without a const field. - return nil + ruleType := celext.ProtoFieldToCELType(extensionDescriptor, false, false) + // To check for the type of "this", we check the descriptor for the rule type we are extending. + thisType := celTypeForStandardRuleMessageDescriptor(extendedStandardRuleDescriptor) + if thisType == nil { + return syserror.Newf("extension for unexpected rule type %q found", extendedStandardRuleDescriptor.FullName()) } - thisType := celext.ProtoFieldToCELType(ruleConstFieldDescriptor, false, false) // TODO: forItems is probably false? celEnv, err = celEnv.Extend( append( celext.RequiredCELEnvOptions(extensionDescriptor), @@ -88,17 +95,26 @@ func checkAndRegisterSharedRuleExtension( if err != nil { return err } + // If the file is an import file, we only want to check that the CEL expression compiles, + // but we do not want to produce file annotations, so we set the addAnnotationFunc to a nop. + if fileIsImport { + addAnnotationFunc = func( + _ bufprotosource.Descriptor, + _ bufprotosource.Location, + _ []bufprotosource.Location, + _ string, _ ...interface{}) { + } + } allCELExpressionsCompile := checkCEL( celEnv, - sharedConstraints.GetCel(), + predefinedConstraints.GetCel(), "extension field", "Extension field", - "(buf.validate.shared_field).cel", + "(buf.validate.predefined).cel", func(index int, format string, args ...interface{}) { addAnnotationFunc( extension, - // TODO: move 1 to a const - extension.OptionExtensionLocation(validate.E_Predefined, 1, int32(index)), + extension.OptionExtensionLocation(validate.E_Predefined, celFieldNumberPath, int32(index)), nil, format, args..., diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index d750e5bd80..4d74d61ff9 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -17,6 +17,7 @@ package buflintvalidate import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/protovalidate-go" + "github.com/google/cel-go/cel" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protoreflect" ) @@ -43,11 +44,9 @@ func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protore return nil } -// TODO: this function is copied directly from protovalidate-go. -// We have 3 options: -// 1. Go to protovalidate-go and add DefaultResolver.ResolveSharedFieldConstraints. -// 2. Go to protovalidate-go and make this public. -// 3. Leave it as is. +// This function is copied directly from protovalidate-go. +// +// This resolves the given extension and returns the constraints for the extension. func resolveExt[C proto.Message]( options proto.Message, extType protoreflect.ExtensionType, @@ -75,13 +74,21 @@ func resolveExt[C proto.Message]( return constraints } -// TODO: this is copied from protovalidate-go, with the difference that types is passed as a parameter. +// This function is copied from protovalidate-go, with the only difference being that types +// are based as parameters. +// +// reparseUnrecognized checks if there are unknown extensions on the protoreflect message. +// If so, then it attempts to use the provided extension resolver to unmarshal the unknown +// extension. Setting proto.UnmarshalOptions.Merge to "true" does not reset reflectMessage.Interface() +// and allows us to unmarshal directly to reflectMessage.Interface() additively. func reparseUnrecognized( reflectMessage protoreflect.Message, extensionTypeResolver ExtensionTypeResolver, ) error { unknown := reflectMessage.GetUnknown() if len(unknown) > 0 { + // We can call reflectMessage.SetUnknown to nil optimistically because if we fail to + // unmarshal, we will simply return the error. reflectMessage.SetUnknown(nil) options := proto.UnmarshalOptions{ Resolver: extensionTypeResolver, @@ -93,3 +100,51 @@ func reparseUnrecognized( } return nil } + +func celTypeForStandardRuleMessageDescriptor( + ruleMessageDescriptor protoreflect.MessageDescriptor, +) *cel.Type { + switch ruleMessageDescriptor.FullName() { + case (&validate.AnyRules{}).ProtoReflect().Descriptor().FullName(): + return cel.AnyType + case (&validate.BoolRules{}).ProtoReflect().Descriptor().FullName(): + return cel.BoolType + case (&validate.BytesRules{}).ProtoReflect().Descriptor().FullName(): + return cel.BytesType + case (&validate.StringRules{}).ProtoReflect().Descriptor().FullName(): + return cel.StringType + case + (&validate.Int32Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.Int64Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.SInt32Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.SInt64Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.SFixed32Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.SFixed64Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.EnumRules{}).ProtoReflect().Descriptor().FullName(): + return cel.IntType + case + (&validate.UInt32Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.UInt64Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.Fixed32Rules{}).ProtoReflect().Descriptor().FullName(), + (&validate.Fixed64Rules{}).ProtoReflect().Descriptor().FullName(): + return cel.UintType + case + (&validate.DoubleRules{}).ProtoReflect().Descriptor().FullName(), + (&validate.FloatRules{}).ProtoReflect().Descriptor().FullName(): + return cel.DoubleType + case (&validate.MapRules{}).ProtoReflect().Descriptor().FullName(): + // The key and value constraints are handled separately as field constraints, so we use + // cel.DynType for key and value here. + return cel.MapType(cel.DynType, cel.DynType) + case (&validate.RepeatedRules{}).ProtoReflect().Descriptor().FullName(): + // The repeated type is handled separately as field constraints, so we use cel.DynType + // for the value type here. + return cel.ListType(cel.DynType) + case (&validate.DurationRules{}).ProtoReflect().Descriptor().FullName(): + return cel.DurationType + case (&validate.TimestampRules{}).ProtoReflect().Descriptor().FullName(): + return cel.TimestampType + } + // We default to returning nil if this does not match with one of the *Rule declarations. + return nil +} From 3ddf6c292afb7e3dba178c1b3a705a955a7c907c Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Thu, 26 Sep 2024 13:34:30 -0400 Subject: [PATCH 20/30] Remove the use of proto.{Marshal, Unmarshal} --- .../internal/bufcheckserverhandle/lint.go | 24 ++++++++---- .../buflintvalidate/buflintvalidate.go | 17 +++------ .../internal/buflintvalidate/field.go | 13 ++++--- .../buflintvalidate/predefined_rules.go | 14 ++----- .../internal/buflintvalidate/util.go | 37 ++++--------------- 5 files changed, 39 insertions(+), 66 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index 8674021df2..40aeeb0f2b 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -27,11 +27,12 @@ import ( "github.com/bufbuild/buf/private/bufpkg/bufcheck/internal/bufcheckopt" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" "github.com/bufbuild/buf/private/pkg/normalpath" + "github.com/bufbuild/buf/private/pkg/protodescriptor" + "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/buf/private/pkg/protoversion" "github.com/bufbuild/buf/private/pkg/slicesext" "github.com/bufbuild/buf/private/pkg/stringutil" "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/descriptorpb" ) @@ -962,9 +963,17 @@ func handleLintProtovalidate( args..., ) } - // Predefined rules are checked first because predefined rules from all files, are added - // to an extension resolver and used to resolve rules when checking fields. - extensionTypesFromRequest := new(protoregistry.Types) + extensionResolver, err := protoencoding.NewResolver( + slicesext.Map( + request.ProtosourceFiles(), + func(protosourceFile bufprotosource.File) protodescriptor.FileDescriptor { + return protosourceFile.FileDescriptor() + }, + )..., + ) + if err != nil { + return err + } // This for-loop checks that predefined rules have cel expressions that compile and adds // the ones that compile to the extension resolver, as a side effect. These types are relied // on to check the example values for fields. @@ -979,8 +988,8 @@ func handleLintProtovalidate( if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( addAnnotationFunc, extension, - extensionTypesFromRequest, file.IsImport(), + extensionResolver, ); err != nil { return err } @@ -995,8 +1004,8 @@ func handleLintProtovalidate( if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( addAnnotationFunc, extension, - extensionTypesFromRequest, file.IsImport(), + extensionResolver, ); err != nil { return err } @@ -1014,7 +1023,6 @@ func handleLintProtovalidate( }).Handle(ctx, nil, nil); err != nil { return err } - // At this point the extension types are already populated. if err := bufcheckserverutil.NewLintFieldRuleHandler( func( // The responseWriter is being passed in through the shared addAnnotationFunc, so we @@ -1023,7 +1031,7 @@ func handleLintProtovalidate( _ bufcheckserverutil.Request, field bufprotosource.Field, ) error { - return buflintvalidate.CheckField(addAnnotationFunc, field, extensionTypesFromRequest) + return buflintvalidate.CheckField(addAnnotationFunc, field, extensionResolver) }).Handle(ctx, nil, nil); err != nil { return err } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index 9d97658de6..d5512151c7 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -17,30 +17,23 @@ package buflintvalidate import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/protovalidate-go/resolver" - "google.golang.org/protobuf/reflect/protoreflect" - "google.golang.org/protobuf/reflect/protoregistry" ) // https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints const disabledFieldNumberInMesageConstraints = 1 -// ExtensionTypeResolver is an extension resolver, the same type as the Resolver in proto.UnmarshalOptions. -type ExtensionTypeResolver interface { - FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) - FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) -} - // CheckAndRegisterPredefinedRuleExtension checks whether an extension extending a protovalidate rule -// is valid, checking that all of its CEL expressionus compile. If so, the extension type is added to +// is valid, checking that all of its CEL expressionus compile. If so, the extension type is appended to // the extension types passed in. func CheckAndRegisterPredefinedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - extensionTypesToPopulate *protoregistry.Types, fileIsImport bool, + extensionResolver protoencoding.Resolver, ) error { - return checkAndRegisterPredefinedRuleExtension(addAnnotationFunc, field, extensionTypesToPopulate, fileIsImport) + return checkAndRegisterPredefinedRuleExtension(addAnnotationFunc, field, fileIsImport, extensionResolver) } // CheckMessage validates that all rules on the message are valid, and any CEL expressions compile. @@ -82,7 +75,7 @@ func CheckMessage( func CheckField( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { return checkField(addAnnotationFunc, field, extensionTypeResolver) } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 3679b3ec11..6df3ca7a62 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -23,6 +23,7 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/protovalidate-go" "github.com/bufbuild/protovalidate-go/resolver" @@ -156,7 +157,7 @@ var ( func checkField( add func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { fieldDescriptor, err := field.AsDescriptor() if err != nil { @@ -192,7 +193,7 @@ func checkConstraintsForField( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, expectRepeatedRule bool, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { if fieldConstraints == nil { return nil @@ -418,7 +419,7 @@ func checkRepeatedRules( repeatedRules *validate.RepeatedRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { if !fieldDescriptor.IsList() { baseAdder.addForPathf( @@ -469,7 +470,7 @@ func checkMapRules( mapRules *validate.MapRules, fieldDescriptor protoreflect.FieldDescriptor, containingMessageDescriptor protoreflect.MessageDescriptor, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { if !fieldDescriptor.IsMap() { baseAdder.addForPathf( @@ -764,9 +765,9 @@ func checkExampleValues( parentMapFieldDescriptor protoreflect.FieldDescriptor, fieldDescriptor protoreflect.FieldDescriptor, exampleValues []protoreflect.Value, - extensionTypeResolver ExtensionTypeResolver, + extensionTypeResolver protoencoding.Resolver, ) error { - if err := reparseUnrecognized(typeRulesMessage, extensionTypeResolver); err != nil { + if err := protoencoding.ReparseExtensions(extensionTypeResolver, typeRulesMessage); err != nil { return err } hasConstraints := len(fieldConstraints.GetCel()) > 0 diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index e1c2deb94c..4fb5b93013 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -19,11 +19,10 @@ import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/bufpkg/bufprotosource" + "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/buf/private/pkg/syserror" "github.com/bufbuild/protovalidate-go/celext" "github.com/google/cel-go/cel" - "google.golang.org/protobuf/reflect/protoregistry" - "google.golang.org/protobuf/types/dynamicpb" ) const ( @@ -33,8 +32,8 @@ const ( func checkAndRegisterPredefinedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), extension bufprotosource.Field, - extensionTypesToPopulate *protoregistry.Types, fileIsImport bool, + extensionResolver protoencoding.Resolver, ) error { extensionDescriptor, err := extension.AsDescriptor() if err != nil { @@ -54,7 +53,7 @@ func checkAndRegisterPredefinedRuleExtension( if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { return nil } - predefinedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined) + predefinedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined, extensionResolver) if predefinedConstraints == nil { return nil } @@ -105,7 +104,7 @@ func checkAndRegisterPredefinedRuleExtension( _ string, _ ...interface{}) { } } - allCELExpressionsCompile := checkCEL( + checkCEL( celEnv, predefinedConstraints.GetCel(), "extension field", @@ -121,10 +120,5 @@ func checkAndRegisterPredefinedRuleExtension( ) }, ) - if allCELExpressionsCompile { - if err := extensionTypesToPopulate.RegisterExtension(dynamicpb.NewExtensionType(extensionDescriptor)); err != nil { - return err - } - } return nil } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index 4d74d61ff9..00745f175c 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -16,6 +16,7 @@ package buflintvalidate import ( "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" + "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/protovalidate-go" "github.com/google/cel-go/cel" "google.golang.org/protobuf/proto" @@ -44,12 +45,14 @@ func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protore return nil } -// This function is copied directly from protovalidate-go. +// This function is copied directly from protovalidate-go, except refactored to use protoencoding +// for marshalling and unmarshalling. // // This resolves the given extension and returns the constraints for the extension. func resolveExt[C proto.Message]( options proto.Message, extType protoreflect.ExtensionType, + resolver protoencoding.Resolver, ) (constraints C) { num := extType.TypeDescriptor().Number() var msg proto.Message @@ -69,38 +72,12 @@ func resolveExt[C proto.Message]( } constraints, _ = constraints.ProtoReflect().New().Interface().(C) - b, _ := proto.Marshal(msg) - _ = proto.Unmarshal(b, constraints) + // TODO: handle marhsal/unmarshaling errors, wtf. + b, _ := protoencoding.NewWireMarshaler().Marshal(msg) + _ = protoencoding.NewWireUnmarshaler(resolver).Unmarshal(b, constraints) return constraints } -// This function is copied from protovalidate-go, with the only difference being that types -// are based as parameters. -// -// reparseUnrecognized checks if there are unknown extensions on the protoreflect message. -// If so, then it attempts to use the provided extension resolver to unmarshal the unknown -// extension. Setting proto.UnmarshalOptions.Merge to "true" does not reset reflectMessage.Interface() -// and allows us to unmarshal directly to reflectMessage.Interface() additively. -func reparseUnrecognized( - reflectMessage protoreflect.Message, - extensionTypeResolver ExtensionTypeResolver, -) error { - unknown := reflectMessage.GetUnknown() - if len(unknown) > 0 { - // We can call reflectMessage.SetUnknown to nil optimistically because if we fail to - // unmarshal, we will simply return the error. - reflectMessage.SetUnknown(nil) - options := proto.UnmarshalOptions{ - Resolver: extensionTypeResolver, - Merge: true, - } - if err := options.Unmarshal(unknown, reflectMessage.Interface()); err != nil { - return err - } - } - return nil -} - func celTypeForStandardRuleMessageDescriptor( ruleMessageDescriptor protoreflect.MessageDescriptor, ) *cel.Type { From 75898acfe6fd9633134786841d0f1df65c3aab3b Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Thu, 26 Sep 2024 16:41:44 -0400 Subject: [PATCH 21/30] Refactor again --- .../internal/bufcheckserverhandle/lint.go | 64 +++++-------------- .../buflintvalidate/buflintvalidate.go | 28 ++++---- .../buflintvalidate/predefined_rules.go | 13 +--- 3 files changed, 31 insertions(+), 74 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go index 40aeeb0f2b..361acdc030 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/bufcheckserverhandle/lint.go @@ -963,6 +963,9 @@ func handleLintProtovalidate( args..., ) } + // We create a new extension resolver using all of the files from the request, including + // import files. This is because there can be a case where a non-import file uses a predefined + // rule from an imported file. extensionResolver, err := protoencoding.NewResolver( slicesext.Map( request.ProtosourceFiles(), @@ -974,68 +977,35 @@ func handleLintProtovalidate( if err != nil { return err } - // This for-loop checks that predefined rules have cel expressions that compile and adds - // the ones that compile to the extension resolver, as a side effect. These types are relied - // on to check the example values for fields. - for _, file := range request.ProtosourceFiles() { - // We check all predefined rules for all files and add them to the extension resolver - // if they compile, regardless if the file is an import or not. This is because a non-import - // file may use a predefined rule from an import file. - // However, we only add check annotations for non-import files. - if err := bufprotosource.ForEachMessage( - func(message bufprotosource.Message) error { - for _, extension := range message.Extensions() { - if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( - addAnnotationFunc, - extension, - file.IsImport(), - extensionResolver, - ); err != nil { - return err - } - } - return nil - }, - file, - ); err != nil { - return err - } - for _, extension := range file.Extensions() { - if err := buflintvalidate.CheckAndRegisterPredefinedRuleExtension( - addAnnotationFunc, - extension, - file.IsImport(), - extensionResolver, - ); err != nil { - return err - } - } - } + // However, we only want to check non-import files, so we can use NewLintMessageRuleHandler + // and NewLintFieldRuleHandler utils to check messages and fields respectively. if err := bufcheckserverutil.NewLintMessageRuleHandler( func( - // The responseWriter is being passed in through the shared addAnnotationFunc, so we - // do not pass in responseWriter and request again. This should be addressed in a refactor. _ bufcheckserverutil.ResponseWriter, _ bufcheckserverutil.Request, message bufprotosource.Message, ) error { return buflintvalidate.CheckMessage(addAnnotationFunc, message) - }).Handle(ctx, nil, nil); err != nil { + }, + // The responseWriter is being passed in through the shared addAnnotationFunc, so we + // do not pass in responseWriter again. This should be addressed in a refactor. + ).Handle(ctx, nil, request); err != nil { return err } - if err := bufcheckserverutil.NewLintFieldRuleHandler( + return bufcheckserverutil.NewLintFieldRuleHandler( func( - // The responseWriter is being passed in through the shared addAnnotationFunc, so we - // do not pass in responseWriter and request again. This should be addressed in a refactor. _ bufcheckserverutil.ResponseWriter, _ bufcheckserverutil.Request, field bufprotosource.Field, ) error { + if err := buflintvalidate.CheckPredefinedRuleExtension(addAnnotationFunc, field, extensionResolver); err != nil { + return err + } return buflintvalidate.CheckField(addAnnotationFunc, field, extensionResolver) - }).Handle(ctx, nil, nil); err != nil { - return err - } - return nil + }, + // The responseWriter is being passed in through the shared addAnnotationFunc, so we + // do not pass in responseWriter again. This should be addressed in a refactor. + ).Handle(ctx, nil, request) } // HandleLintRPCNoClientStreaming is a handle function. diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index d5512151c7..73de4b26d8 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -24,22 +24,10 @@ import ( // https://buf.build/bufbuild/protovalidate/docs/v0.5.1:buf.validate#buf.validate.MessageConstraints const disabledFieldNumberInMesageConstraints = 1 -// CheckAndRegisterPredefinedRuleExtension checks whether an extension extending a protovalidate rule -// is valid, checking that all of its CEL expressionus compile. If so, the extension type is appended to -// the extension types passed in. -func CheckAndRegisterPredefinedRuleExtension( - addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), - field bufprotosource.Field, - fileIsImport bool, - extensionResolver protoencoding.Resolver, -) error { - return checkAndRegisterPredefinedRuleExtension(addAnnotationFunc, field, fileIsImport, extensionResolver) -} - // CheckMessage validates that all rules on the message are valid, and any CEL expressions compile. -// -// addAnnotationFunc adds an annotation with the descriptor and location for check results. +// It also checks all predefined rule extensions on the messages. func CheckMessage( + // addAnnotationFunc adds an annotation with the descriptor and location for check results. addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), message bufprotosource.Message, ) error { @@ -71,11 +59,21 @@ func CheckMessage( // 1. permit _some_ value and all example values, if any // 2. have a type compatible with the field it validates. // -// addAnnotationFunc adds an annotation with the descriptor and location for check results. +// This also checks all predefined rule extensions fields to ensure they compile. func CheckField( + // addAnnotationFunc adds an annotation with the descriptor and location for check results. addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), field bufprotosource.Field, extensionTypeResolver protoencoding.Resolver, ) error { return checkField(addAnnotationFunc, field, extensionTypeResolver) } + +func CheckPredefinedRuleExtension( + // addAnnotationFunc adds an annotation with the descriptor and location for check results. + addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), + field bufprotosource.Field, + extensionResolver protoencoding.Resolver, +) error { + return checkPredefinedRuleExtension(addAnnotationFunc, field, extensionResolver) +} diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index 4fb5b93013..5616391615 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -29,10 +29,9 @@ const ( celFieldNumberPath = int32(1) ) -func checkAndRegisterPredefinedRuleExtension( +func checkPredefinedRuleExtension( addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), extension bufprotosource.Field, - fileIsImport bool, extensionResolver protoencoding.Resolver, ) error { extensionDescriptor, err := extension.AsDescriptor() @@ -94,16 +93,6 @@ func checkAndRegisterPredefinedRuleExtension( if err != nil { return err } - // If the file is an import file, we only want to check that the CEL expression compiles, - // but we do not want to produce file annotations, so we set the addAnnotationFunc to a nop. - if fileIsImport { - addAnnotationFunc = func( - _ bufprotosource.Descriptor, - _ bufprotosource.Location, - _ []bufprotosource.Location, - _ string, _ ...interface{}) { - } - } checkCEL( celEnv, predefinedConstraints.GetCel(), From eae0426e37d36ae7300a0f0488f35217d657d696 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Fri, 27 Sep 2024 18:50:46 -0400 Subject: [PATCH 22/30] Add test cases for examples and fix bugs found --- .../buflintvalidate/buflintvalidate.go | 3 +- .../internal/buflintvalidate/field.go | 118 ++++++++++++-- private/bufpkg/bufcheck/lint_test.go | 23 +++ .../lint/protovalidate/proto/bool.proto | 8 + .../lint/protovalidate/proto/bytes.proto | 12 ++ .../lint/protovalidate/proto/duration.proto | 28 ++++ .../lint/protovalidate/proto/enum.proto | 8 + .../lint/protovalidate/proto/map.proto | 14 ++ .../lint/protovalidate/proto/number.proto | 152 +++++++++++++++++- .../lint/protovalidate/proto/repeated.proto | 11 ++ .../lint/protovalidate/proto/string.proto | 11 ++ .../lint/protovalidate/proto/timestamp.proto | 28 ++++ 12 files changed, 400 insertions(+), 16 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go index 73de4b26d8..5bd4398882 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/buflintvalidate.go @@ -58,8 +58,6 @@ func CheckMessage( // For a set of rules to be valid, it must // 1. permit _some_ value and all example values, if any // 2. have a type compatible with the field it validates. -// -// This also checks all predefined rule extensions fields to ensure they compile. func CheckField( // addAnnotationFunc adds an annotation with the descriptor and location for check results. addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), @@ -69,6 +67,7 @@ func CheckField( return checkField(addAnnotationFunc, field, extensionTypeResolver) } +// CheckPredefinedRuleExtension checks that a predefined extension is valid, and any CEL expressions compile. func CheckPredefinedRuleExtension( // addAnnotationFunc adds an annotation with the descriptor and location for check results. addAnnotationFunc func(bufprotosource.Descriptor, bufprotosource.Location, []bufprotosource.Location, string, ...interface{}), diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 6df3ca7a62..d739fe29b4 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -795,18 +795,28 @@ func checkExampleValues( // and validate this message instance with protovalidate and filter the structured // errors by field name to determine whether this example value fails rules defined // on the same field. - v, err := protovalidate.New( - protovalidate.WithStandardConstraintInterceptor( - func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { - // Pass a constraint resolver interceptor so that constraints on other - // fields are not looked at by the validator. - return &constraintsResolverForTargetField{ - StandardConstraintResolver: res, - targetField: fieldDescriptor, - } - }, - ), - ) + // + // Pass a constraint resolver interceptor so that constraints on other + // fields are not looked at by the validator. + constraintInterceptor := func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { + return &constraintsResolverForTargetField{ + StandardConstraintResolver: res, + targetField: fieldDescriptor, + } + } + // For map fields, we want to resolve the constraints on the parentMapFieldDescriptor rather + // than the MapEntry. + if parentMapFieldDescriptor != nil { + constraintInterceptor = func(res protovalidate.StandardConstraintResolver) protovalidate.StandardConstraintResolver { + // Pass a constraint resolver interceptor so that constraints on other + // fields are not looked at by the validator. + return &constraintsResolverForTargetField{ + StandardConstraintResolver: res, + targetField: parentMapFieldDescriptor, + } + } + } + validator, err := protovalidate.New(protovalidate.WithStandardConstraintInterceptor(constraintInterceptor)) if err != nil { return err } @@ -878,10 +888,92 @@ func checkExampleValues( return syserror.Newf("expected key or value as sythetic field name for map entry's field name, got %q", fieldDescriptor.Name()) } messageToValidate.Set(parentMapFieldDescriptor, protoreflect.ValueOfMap(mapEntry)) + case fieldDescriptor.Enum() != nil: + // We need to handle enum examples in a special way, since enum examples are set as + // int32, but we need to set it to the enum value to the field. + // So we cast exampleValue to an int32 and check that cast first before attempting + // to set it to the message field. This is because messageToValidate.Set will panic + // if an invalid type is attempted to be set. + exampleInt32, ok := exampleValue.Interface().(int32) + if !ok { + return syserror.Newf("expected enum example value to be int32 for field %q, got %T type instead", fieldDescriptor.FullName(), exampleValue) + } + messageToValidate.Set(fieldDescriptor, protoreflect.ValueOf(protoreflect.EnumNumber(exampleInt32))) + case fieldDescriptor.Message() != nil: + // We need to handle the case where the field is a wrapper type. We set the value directly base on the wrapper type. + switch string(fieldDescriptor.Message().FullName()) { + case string((&wrapperspb.FloatValue{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.FloatValue{Value: exampleValue.Interface().(float32)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.DoubleValue{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.DoubleValue{Value: exampleValue.Interface().(float64)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.Int32Value{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.Int32Value{Value: exampleValue.Interface().(int32)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.Int64Value{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.Int64Value{Value: exampleValue.Interface().(int64)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.UInt32Value{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.UInt32Value{Value: exampleValue.Interface().(uint32)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.UInt64Value{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.UInt64Value{Value: exampleValue.Interface().(uint64)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.BoolValue{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.BoolValue{Value: exampleValue.Interface().(bool)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.StringValue{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.StringValue{Value: exampleValue.Interface().(string)}).ProtoReflect(), + ), + ) + case string((&wrapperspb.BytesValue{}).ProtoReflect().Descriptor().FullName()): + messageToValidate.Set( + fieldDescriptor, + protoreflect.ValueOf( + (&wrapperspb.BytesValue{Value: exampleValue.Interface().([]byte)}).ProtoReflect(), + ), + ) + default: + // In the case where it is not a wrapper type (e.g. google.protobuf.Timestamp), we just set the example + // value directly. + messageToValidate.Set(fieldDescriptor, exampleValue) + } default: messageToValidate.Set(fieldDescriptor, exampleValue) } - err := v.Validate(messageToValidate) + err := validator.Validate(messageToValidate) if err == nil { continue } diff --git a/private/bufpkg/bufcheck/lint_test.go b/private/bufpkg/bufcheck/lint_test.go index 4e93324b0d..b94ec02254 100644 --- a/private/bufpkg/bufcheck/lint_test.go +++ b/private/bufpkg/bufcheck/lint_test.go @@ -597,9 +597,11 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "bool.proto", 18, 51, 18, 84, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "bool.proto", 19, 31, 19, 69, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "bool.proto", 20, 50, 20, 88, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "bool.proto", 27, 5, 27, 46, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "bytes.proto", 21, 5, 21, 48, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "bytes.proto", 26, 5, 26, 45, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "bytes.proto", 31, 5, 31, 45, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "bytes.proto", 43, 5, 43, 65, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "cel_field.proto", 10, 37, 14, 4, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "cel_field.proto", 17, 5, 21, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "cel_field.proto", 29, 5, 33, 6, "PROTOVALIDATE"), @@ -633,7 +635,9 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "duration.proto", 105, 5, 108, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "duration.proto", 122, 5, 125, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "duration.proto", 127, 5, 130, 6, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "duration.proto", 155, 5, 158, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "enum.proto", 28, 5, 28, 40, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "enum.proto", 36, 5, 36, 42, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "extension.proto", 25, 7, 25, 43, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "extension.proto", 30, 7, 30, 47, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "extension.proto", 40, 5, 40, 41, "PROTOVALIDATE"), @@ -650,6 +654,7 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "map.proto", 50, 5, 50, 57, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "map.proto", 53, 5, 53, 50, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "map.proto", 56, 41, 56, 80, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "map.proto", 69, 5, 69, 53, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "message.proto", 20, 3, 20, 49, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "message.proto", 27, 5, 27, 51, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "number.proto", 20, 5, 20, 42, "PROTOVALIDATE"), @@ -670,6 +675,21 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "number.proto", 134, 5, 134, 56, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "number.proto", 139, 5, 139, 50, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "number.proto", 142, 5, 142, 52, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 160, 5, 160, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 170, 5, 170, 45, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 180, 5, 180, 45, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 190, 5, 190, 43, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 200, 5, 200, 43, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 210, 5, 210, 46, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 220, 5, 220, 46, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 230, 5, 230, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 240, 5, 240, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 250, 5, 250, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 260, 5, 260, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 270, 5, 270, 43, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 280, 5, 280, 43, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 290, 5, 290, 44, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "number.proto", 300, 5, 300, 44, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "oneof.proto", 13, 7, 13, 43, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "oneof.proto", 19, 7, 19, 43, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "repeated.proto", 25, 5, 25, 48, "PROTOVALIDATE"), @@ -679,6 +699,7 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "repeated.proto", 51, 38, 51, 92, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "repeated.proto", 53, 26, 53, 74, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "repeated.proto", 55, 42, 55, 76, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "repeated.proto", 65, 5, 65, 62, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "string.proto", 31, 5, 31, 46, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "string.proto", 36, 5, 36, 44, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "string.proto", 41, 5, 41, 44, "PROTOVALIDATE"), @@ -705,6 +726,8 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "string.proto", 122, 5, 122, 47, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "string.proto", 130, 5, 130, 46, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "string.proto", 133, 5, 133, 45, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "string.proto", 152, 5, 152, 51, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "string.proto", 154, 5, 154, 49, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "timestamp.proto", 57, 5, 60, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "timestamp.proto", 61, 5, 64, 6, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "timestamp.proto", 68, 5, 71, 6, "PROTOVALIDATE"), diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bool.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bool.proto index da96edc6aa..d1e7cb1ea3 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bool.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bool.proto @@ -18,4 +18,12 @@ message BoolTest { google.protobuf.BoolValue mismatch_wrapper = 8 [(buf.validate.field).int32.lt = 1]; string string_mismatch = 9 [(buf.validate.field).bool.const = true]; google.protobuf.Int32Value wrong_wrapper = 10 [(buf.validate.field).bool.const = true]; + bool valid_example = 4 [ + (buf.validate.field).bool.const = true, + (buf.validate.field).bool.example = true + ]; + bool invalid_example = 5 [ + (buf.validate.field).bool.const = true, + (buf.validate.field).bool.example = false + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bytes.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bytes.proto index 365d038380..ed4e5721da 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bytes.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/bytes.proto @@ -30,4 +30,16 @@ message BytesTest { (buf.validate.field).bytes.len = 1, (buf.validate.field).bytes.pattern = "[" ]; + bytes valid_example = 9 [ + (buf.validate.field).bytes.prefix = "ÀÀÀÀÀ", + (buf.validate.field).bytes.example = "ÀÀÀÀÀÇÇÇÇÇÇÇÇÅÅÅÅÅÅÅÅ" + ]; + bytes valid_and_invalid_example = 10 [ + (buf.validate.field).bytes.prefix = "ÀÀÀÀÀ", + (buf.validate.field).bytes.max_len = 17, + // valid + (buf.validate.field).bytes.example = "ÀÀÀÀÀÀÀÀ", + // invalid, fails one of the rules + (buf.validate.field).bytes.example = "ÀÀÀÀÀÇÇÇÇÇÇÇÇÅÅÅÅÅÅÅÅ" + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/duration.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/duration.proto index ba765a292b..70463bdb59 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/duration.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/duration.proto @@ -129,4 +129,32 @@ message DurationTest { nanos: 854775429 } ]; + google.protobuf.Duration valid_example = 20 [ + (buf.validate.field).duration.lt = { + seconds: 17, + nanos: 25 + }, + (buf.validate.field).duration.gt = { + seconds: 5, + nanos: 1 + }, + (buf.validate.field).duration.example = { + seconds: 7, + nanos: 3 + } + ]; + google.protobuf.Duration invalid_example = 21 [ + (buf.validate.field).duration.lt = { + seconds: 17, + nanos: 25 + }, + (buf.validate.field).duration.gt = { + seconds: 5, + nanos: 1 + }, + (buf.validate.field).duration.example = { + seconds: 2, + nanos: 3 + } + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/enum.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/enum.proto index b2fe758281..5d5db786f7 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/enum.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/enum.proto @@ -27,4 +27,12 @@ message EnumTest { // const should be the only field (buf.validate.field).enum.const = 2 ]; + TestEnum valid_example = 6 [ + (buf.validate.field).enum.defined_only = true, + (buf.validate.field).enum.example = 1 + ]; + TestEnum invalid_example = 7 [ + (buf.validate.field).enum.defined_only = true, + (buf.validate.field).enum.example = 4 + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto index 21e69be261..a266598cc1 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto @@ -54,4 +54,18 @@ message MapTest { (buf.validate.field).map.values.int32.gt = 1 ]; map non_map_rule = 15 [(buf.validate.field).string.min_len = 1]; + map valid_keys_example = 11 [ + (buf.validate.field).map.keys.int64.gt = 1, + (buf.validate.field).map.keys.int64.lt = 10, + (buf.validate.field).map.values.string.min_len = 1, + (buf.validate.field).map.values.string.max_len = 10, + (buf.validate.field).map.keys.int64.example = 5 + ]; + map invalid_keys_example = 12 [ + (buf.validate.field).map.keys.int64.gt = 1, + (buf.validate.field).map.keys.int64.lt = 10, + (buf.validate.field).map.values.string.min_len = 1, + (buf.validate.field).map.values.string.max_len = 10, + (buf.validate.field).map.keys.int64.example = -1 + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/number.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/number.proto index 834dacd082..d22b19289b 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/number.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/number.proto @@ -7,7 +7,7 @@ import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -message Int32Test { +message IntTest { // valid int32 no_protovalidate_option = 1; // valid @@ -149,4 +149,154 @@ message Int32Test { (buf.validate.field).int32.lt = 9, (buf.validate.field).int32.gt = 10 ]; + // examples + double valid_double_example = 50 [ + (buf.validate.field).double.lt = 10, + (buf.validate.field).double.example = 9 + ]; + double invalid_double_example = 51 [ + (buf.validate.field).double.lt = 10, + (buf.validate.field).double.gt = 5, + (buf.validate.field).double.example = 1 + ]; + fixed32 valid_fixed32_example = 52 [ + (buf.validate.field).fixed32.lt = 10, + (buf.validate.field).fixed32.gt = 5, + (buf.validate.field).fixed32.example = 9 + ]; + fixed32 invalid_fixed32_example = 53 [ + (buf.validate.field).fixed32.lt = 10, + (buf.validate.field).fixed32.gt = 5, + (buf.validate.field).fixed32.example = 1 + ]; + fixed64 valid_fixed64_example = 54 [ + (buf.validate.field).fixed64.lt = 10, + (buf.validate.field).fixed64.gt = 5, + (buf.validate.field).fixed64.example = 9 + ]; + fixed64 invalid_fixed64_example = 55 [ + (buf.validate.field).fixed64.lt = 10, + (buf.validate.field).fixed64.gt = 5, + (buf.validate.field).fixed64.example = 1 + ]; + int32 valid_int32_example = 56 [ + (buf.validate.field).int32.lt = 10, + (buf.validate.field).int32.gt = 5, + (buf.validate.field).int32.example = 9 + ]; + int32 invalid_int32_example = 57 [ + (buf.validate.field).int32.lt = 10, + (buf.validate.field).int32.gt = 5, + (buf.validate.field).int32.example = 1 + ]; + int64 valid_int64_example = 58 [ + (buf.validate.field).int64.lt = 10, + (buf.validate.field).int64.gt = 5, + (buf.validate.field).int64.example = 9 + ]; + int64 invalid_int64_example = 59 [ + (buf.validate.field).int64.lt = 10, + (buf.validate.field).int64.gt = 5, + (buf.validate.field).int64.example = 1 + ]; + sfixed32 valid_sfixed32_example = 60 [ + (buf.validate.field).sfixed32.lt = 10, + (buf.validate.field).sfixed32.gt = 5, + (buf.validate.field).sfixed32.example = 9 + ]; + sfixed32 invalid_sfixed32_example = 61 [ + (buf.validate.field).sfixed32.lt = 10, + (buf.validate.field).sfixed32.gt = 5, + (buf.validate.field).sfixed32.example = 1 + ]; + sfixed64 valid_sfixed64_example = 64 [ + (buf.validate.field).sfixed64.lt = 10, + (buf.validate.field).sfixed64.gt = 5, + (buf.validate.field).sfixed64.example = 9 + ]; + sfixed64 invalid_sfixed64_example = 65 [ + (buf.validate.field).sfixed64.lt = 10, + (buf.validate.field).sfixed64.gt = 5, + (buf.validate.field).sfixed64.example = 1 + ]; + sint32 valid_sint32_example = 68 [ + (buf.validate.field).sint32.lt = 10, + (buf.validate.field).sint32.gt = 5, + (buf.validate.field).sint32.example = 9 + ]; + sint32 invalid_sint32_example = 69 [ + (buf.validate.field).sint32.lt = 10, + (buf.validate.field).sint32.gt = 5, + (buf.validate.field).sint32.example = 1 + ]; + sint64 valid_sint64_example = 72 [ + (buf.validate.field).sint64.lt = 10, + (buf.validate.field).sint64.gt = 5, + (buf.validate.field).sint64.example = 9 + ]; + sint64 invalid_sint64_example = 73 [ + (buf.validate.field).sint64.lt = 10, + (buf.validate.field).sint64.gt = 5, + (buf.validate.field).sint64.example = 1 + ]; + uint32 valid_uint32_example = 74 [ + (buf.validate.field).uint32.lt = 10, + (buf.validate.field).uint32.gt = 5, + (buf.validate.field).uint32.example = 9 + ]; + uint32 invalid_uint32_example = 75 [ + (buf.validate.field).uint32.lt = 10, + (buf.validate.field).uint32.gt = 5, + (buf.validate.field).uint32.example = 1 + ]; + uint64 valid_uint64_example = 76 [ + (buf.validate.field).uint64.lt = 10, + (buf.validate.field).uint64.gt = 5, + (buf.validate.field).uint64.example = 9 + ]; + uint64 invalid_uint64_example = 77 [ + (buf.validate.field).uint64.lt = 10, + (buf.validate.field).uint64.gt = 5, + (buf.validate.field).uint64.example = 1 + ]; + google.protobuf.Int64Value valid_wkt_int64_example = 66 [ + (buf.validate.field).int64.lt = 10, + (buf.validate.field).int64.gt = 5, + (buf.validate.field).int64.example = 9 + ]; + google.protobuf.Int64Value invalid_wkt_int64_example = 67 [ + (buf.validate.field).int64.lt = 10, + (buf.validate.field).int64.gt = 5, + (buf.validate.field).int64.example = 1 + ]; + google.protobuf.Int32Value valid_wkt_int32_example = 70 [ + (buf.validate.field).int32.lt = 10, + (buf.validate.field).int32.gt = 5, + (buf.validate.field).int32.example = 9 + ]; + google.protobuf.Int32Value invalid_wkt_int32_example = 71 [ + (buf.validate.field).int32.lt = 10, + (buf.validate.field).int32.gt = 5, + (buf.validate.field).int32.example = 1 + ]; + google.protobuf.UInt32Value valid_wkt_uint32_example = 62 [ + (buf.validate.field).uint32.lt = 10, + (buf.validate.field).uint32.gt = 5, + (buf.validate.field).uint32.example = 9 + ]; + google.protobuf.UInt32Value invalid_wkt_uint32_example = 63 [ + (buf.validate.field).uint32.lt = 10, + (buf.validate.field).uint32.gt = 5, + (buf.validate.field).uint32.example = 1 + ]; + google.protobuf.UInt64Value valid_wkt_uint64_example = 78 [ + (buf.validate.field).uint64.lt = 10, + (buf.validate.field).uint64.gt = 5, + (buf.validate.field).uint64.example = 9 + ]; + google.protobuf.UInt64Value invalid_wkt_uint64_example = 79 [ + (buf.validate.field).uint64.lt = 10, + (buf.validate.field).uint64.gt = 5, + (buf.validate.field).uint64.example = 1 + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/repeated.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/repeated.proto index 87a59d42d6..5137dfd9aa 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/repeated.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/repeated.proto @@ -53,4 +53,15 @@ message RepeatedTest { int32 wrong_type = 17 [(buf.validate.field).repeated.items.int64.lt = 1]; // non repeated repeated int32 non_repeated_rule = 18 [(buf.validate.field).int32.gt = 10]; + repeated string valid_example = 19 [ + (buf.validate.field).repeated.items.string.min_len = 1, + (buf.validate.field).repeated.items.string.max_len = 10, + (buf.validate.field).repeated.items.string.example = "proto" + ]; + repeated string invalid_example = 20 [ + (buf.validate.field).repeated.items.string.min_len = 3, + (buf.validate.field).repeated.items.string.max_len = 10, + (buf.validate.field).repeated.items.string.example = "proto", // valid + (buf.validate.field).repeated.items.string.example = "pr" + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/string.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/string.proto index 256ba47fa9..a2ce7aa55b 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/string.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/string.proto @@ -142,4 +142,15 @@ message StringTest { (buf.validate.field).string.not_contains = "bar_foo", (buf.validate.field).string.prefix = "foo" ]; + string examples = 32 [ + (buf.validate.field).string.max_len = 20, + (buf.validate.field).string.max_bytes = 32, + (buf.validate.field).string.prefix = "foo", + (buf.validate.field).string.not_in = "foo_1", + (buf.validate.field).string.not_in = "foo_2", + (buf.validate.field).string.example = "foo_8", // valid + (buf.validate.field).string.example = "afoo_8", // invalid + (buf.validate.field).string.example = "foofoofoo", // valid + (buf.validate.field).string.example = "bfoo" // invalid + ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/timestamp.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/timestamp.proto index 07c184b076..e7cd76c76d 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/timestamp.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/timestamp.proto @@ -163,6 +163,34 @@ message TimestampTest { nanos: 1 } ]; + google.protobuf.Timestamp valid_example = 19 [ + (buf.validate.field).timestamp.gt = { + seconds: 5, + nanos: 2 + }, + (buf.validate.field).timestamp.lte = { + seconds: 5, + nanos: 0 + }, + (buf.validate.field).timestamp.example = { + seconds: 4, + nanos: 1 + } + ]; + google.protobuf.Timestamp invalid_example = 20 [ + (buf.validate.field).timestamp.gt = { + seconds: 5, + nanos: 2 + }, + (buf.validate.field).timestamp.lte = { + seconds: 5, + nanos: 0 + }, + (buf.validate.field).timestamp.example = { + seconds: 5, + nanos: 17 + } + ]; } message NotTimestamp {} From e1bb066972eac8b7469a5745f1c9622b8cfe78f1 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Fri, 27 Sep 2024 19:00:25 -0400 Subject: [PATCH 23/30] Handle type casting --- .../internal/buflintvalidate/field.go | 54 +++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index d739fe29b4..86288ab320 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -903,66 +903,102 @@ func checkExampleValues( // We need to handle the case where the field is a wrapper type. We set the value directly base on the wrapper type. switch string(fieldDescriptor.Message().FullName()) { case string((&wrapperspb.FloatValue{}).ProtoReflect().Descriptor().FullName()): + exampleValueFloat, ok := exampleValue.Interface().(float32) + if !ok { + return syserror.Newf("unexpected type found for float wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.FloatValue{Value: exampleValue.Interface().(float32)}).ProtoReflect(), + (&wrapperspb.FloatValue{Value: exampleValueFloat}).ProtoReflect(), ), ) case string((&wrapperspb.DoubleValue{}).ProtoReflect().Descriptor().FullName()): + exampleValueDouble, ok := exampleValue.Interface().(float64) + if !ok { + return syserror.Newf("unexpected type found for double wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.DoubleValue{Value: exampleValue.Interface().(float64)}).ProtoReflect(), + (&wrapperspb.DoubleValue{Value: exampleValueDouble}).ProtoReflect(), ), ) case string((&wrapperspb.Int32Value{}).ProtoReflect().Descriptor().FullName()): + exampleValueInt32, ok := exampleValue.Interface().(int32) + if !ok { + return syserror.Newf("unexpected type found for int32 wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.Int32Value{Value: exampleValue.Interface().(int32)}).ProtoReflect(), + (&wrapperspb.Int32Value{Value: exampleValueInt32}).ProtoReflect(), ), ) case string((&wrapperspb.Int64Value{}).ProtoReflect().Descriptor().FullName()): + exampleValueInt64, ok := exampleValue.Interface().(int64) + if !ok { + return syserror.Newf("unexpected type found for int64 wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.Int64Value{Value: exampleValue.Interface().(int64)}).ProtoReflect(), + (&wrapperspb.Int64Value{Value: exampleValueInt64}).ProtoReflect(), ), ) case string((&wrapperspb.UInt32Value{}).ProtoReflect().Descriptor().FullName()): + exampleValueUInt32, ok := exampleValue.Interface().(uint32) + if !ok { + return syserror.Newf("unexpected type found for uint32 wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.UInt32Value{Value: exampleValue.Interface().(uint32)}).ProtoReflect(), + (&wrapperspb.UInt32Value{Value: exampleValueUInt32}).ProtoReflect(), ), ) case string((&wrapperspb.UInt64Value{}).ProtoReflect().Descriptor().FullName()): + exampleValueUInt64, ok := exampleValue.Interface().(uint64) + if !ok { + return syserror.Newf("unexpected type found for uint32 wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.UInt64Value{Value: exampleValue.Interface().(uint64)}).ProtoReflect(), + (&wrapperspb.UInt64Value{Value: exampleValueUInt64}).ProtoReflect(), ), ) case string((&wrapperspb.BoolValue{}).ProtoReflect().Descriptor().FullName()): + exampleValueBool, ok := exampleValue.Interface().(bool) + if !ok { + return syserror.Newf("unexpected type found for bool wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.BoolValue{Value: exampleValue.Interface().(bool)}).ProtoReflect(), + (&wrapperspb.BoolValue{Value: exampleValueBool}).ProtoReflect(), ), ) case string((&wrapperspb.StringValue{}).ProtoReflect().Descriptor().FullName()): + exampleValueString, ok := exampleValue.Interface().(string) + if !ok { + return syserror.Newf("unexpected type found for string wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.StringValue{Value: exampleValue.Interface().(string)}).ProtoReflect(), + (&wrapperspb.StringValue{Value: exampleValueString}).ProtoReflect(), ), ) case string((&wrapperspb.BytesValue{}).ProtoReflect().Descriptor().FullName()): + exampleValueBytes, ok := exampleValue.Interface().([]byte) + if !ok { + return syserror.Newf("unexpected type found for bytes wrapper type %T", exampleValue.Interface()) + } messageToValidate.Set( fieldDescriptor, protoreflect.ValueOf( - (&wrapperspb.BytesValue{Value: exampleValue.Interface().([]byte)}).ProtoReflect(), + (&wrapperspb.BytesValue{Value: exampleValueBytes}).ProtoReflect(), ), ) default: From 2bdb9ff8b582fd541ea43cce8d0da56134acfb3b Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Fri, 27 Sep 2024 19:07:24 -0400 Subject: [PATCH 24/30] Address last todo --- .../buflintvalidate/predefined_rules.go | 5 +++- .../internal/buflintvalidate/util.go | 26 ++++++++++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index 5616391615..ad8edf2be0 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -52,7 +52,10 @@ func checkPredefinedRuleExtension( if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { return nil } - predefinedConstraints := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined, extensionResolver) + predefinedConstraints, err := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined, extensionResolver) + if err != nil { + return err + } if predefinedConstraints == nil { return nil } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index 00745f175c..b506c6cd24 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -15,6 +15,8 @@ package buflintvalidate import ( + "fmt" + "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" "github.com/bufbuild/buf/private/pkg/protoencoding" "github.com/bufbuild/protovalidate-go" @@ -46,14 +48,14 @@ func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protore } // This function is copied directly from protovalidate-go, except refactored to use protoencoding -// for marshalling and unmarshalling. +// for marshalling and unmarshalling. We also added error handling for marshal/unmarshal. // // This resolves the given extension and returns the constraints for the extension. func resolveExt[C proto.Message]( options proto.Message, extType protoreflect.ExtensionType, resolver protoencoding.Resolver, -) (constraints C) { +) (constraints C, retErr error) { num := extType.TypeDescriptor().Number() var msg proto.Message @@ -66,16 +68,20 @@ func resolveExt[C proto.Message]( }) if msg == nil { - return constraints + return constraints, nil } else if m, ok := msg.(C); ok { - return m + return m, nil } - - constraints, _ = constraints.ProtoReflect().New().Interface().(C) - // TODO: handle marhsal/unmarshaling errors, wtf. - b, _ := protoencoding.NewWireMarshaler().Marshal(msg) - _ = protoencoding.NewWireUnmarshaler(resolver).Unmarshal(b, constraints) - return constraints + var ok bool + constraints, ok = constraints.ProtoReflect().New().Interface().(C) + if !ok { + return constraints, fmt.Errorf("unexpected type for constraints %T", constraints) + } + b, err := protoencoding.NewWireMarshaler().Marshal(msg) + if err != nil { + return constraints, err + } + return constraints, protoencoding.NewWireUnmarshaler(resolver).Unmarshal(b, constraints) } func celTypeForStandardRuleMessageDescriptor( From e3c766a3352d5c1eb8511c945acfda94445f0a27 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Mon, 30 Sep 2024 15:05:24 -0400 Subject: [PATCH 25/30] Add predefined rules tests (WIP) --- make/buf/all.mk | 3 + private/bufpkg/bufcheck/lint_test.go | 14 + .../lint/protovalidate_predefined/buf.yaml | 9 + .../import/import.proto | 26 + .../protovalidate_predefined/proto/test.proto | 63 + .../protovalidate/buf/validate/validate.proto | 4787 +++++++++++++++++ 6 files changed, 4902 insertions(+) create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/buf.yaml create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto create mode 100644 private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate/buf/validate/validate.proto diff --git a/make/buf/all.mk b/make/buf/all.mk index d22f9054ac..6b03a1dbd6 100644 --- a/make/buf/all.mk +++ b/make/buf/all.mk @@ -148,6 +148,9 @@ bufgeneratebuflinttestdata: $(BUF_BIN) export \ buf.build/bufbuild/protovalidate:$(PROTOVALIDATE_VERSION) \ --output private/bufpkg/bufcheck/testdata/lint/protovalidate/vendor/protovalidate + $(BUF_BIN) export \ + buf.build/bufbuild/protovalidate:$(PROTOVALIDATE_VERSION) \ + --output private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate bufgeneratesteps:: \ bufgeneratego \ diff --git a/private/bufpkg/bufcheck/lint_test.go b/private/bufpkg/bufcheck/lint_test.go index b94ec02254..a3d2853018 100644 --- a/private/bufpkg/bufcheck/lint_test.go +++ b/private/bufpkg/bufcheck/lint_test.go @@ -745,6 +745,20 @@ func TestRunProtovalidate(t *testing.T) { ) } +func TestRunProtovalidatePredefinedRules(t *testing.T) { + t.Parallel() + testLintWithOptions( + t, + "protovalidate_predefined", + "buf.testing/lint/proto", + nil, + bufanalysistesting.NewFileAnnotation(t, "test.proto", 14, 44, 18, 4, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "test.proto", 43, 5, 43, 57, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "test.proto", 43, 5, 43, 57, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "test.proto", 44, 5, 44, 64, "PROTOVALIDATE"), + ) +} + func TestRunRPCNoStreaming(t *testing.T) { t.Parallel() testLint( diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/buf.yaml b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/buf.yaml new file mode 100644 index 0000000000..c5e15323c7 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: proto + name: buf.testing/lint/proto + lint: + use: + - PROTOVALIDATE + - path: import + - path: vendor/protovalidate diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto new file mode 100644 index 0000000000..1b4955a460 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto @@ -0,0 +1,26 @@ +syntax = "proto2"; + +package custom; + +import "buf/validate/validate.proto"; + +extend buf.validate.StringRules { + optional string special_suffix = 1800 [(buf.validate.predefined).cel = { + id: "string.special_suffix" + expression: "!this.endsWith(rule + '_') ? \'value does not have suffix `%s`\'.format([rule + '_']) : \'\'" + }]; + + optional string bad_rule = 1900 [(buf.validate.predefined).cel = { + id: "string.bad_rule" + expression: "bad" + message: "this rule does not compile" + }]; +} + +extend buf.validate.Int32Rules { + repeated int32 abs_not_in = 1800 [(buf.validate.predefined).cel = { + id: "int32.abs_not_in" + expression: "this in rule || this in rule.map(n, -n)" + message: "value must be in absolute value of list" + }]; +} diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto new file mode 100644 index 0000000000..07f8e595f2 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto @@ -0,0 +1,63 @@ +syntax = "proto2"; + +package test; + +import "buf/validate/validate.proto"; +import "import.proto"; + +extend buf.validate.StringRules { + optional string special_prefix = 1801 [(buf.validate.predefined).cel = { + id: "string.special_prefix" + expression: "!this.startsWith('_' + rule) ? \'value does not have prefix `%s`\'.format(['_' + rule]) : \'\'" + }]; + + optional string another_bad_rule = 1902 [(buf.validate.predefined).cel = { + id: "string.another_bad_rule" + expression: "bad" + message: "this rule does not compile" + }]; +} + +message TestPredefinedStringRules { + // valid + optional string all_rules_valid = 1 [ + (buf.validate.field).string.max_len = 20, + // imported + (buf.validate.field).string.(custom.special_suffix) = "suffix", + (buf.validate.field).string.(special_prefix) = "prefix" + ]; + optional string all_rules_valid_with_all_valid_examples = 2 [ + (buf.validate.field).string.max_len = 50, + (buf.validate.field).string.(custom.special_suffix) = "suffix", + (buf.validate.field).string.(special_prefix) = "prefix", + (buf.validate.field).string.example = "_prefix_foo_foo_suffix_", + (buf.validate.field).string.example = "_prefixfoofoosuffix_" + ]; + optional string all_rules_valid_with_examples = 3 [ + (buf.validate.field).string.max_len = 50, + (buf.validate.field).string.(custom.special_suffix) = "suffix", + (buf.validate.field).string.(special_prefix) = "prefix", + // valid + (buf.validate.field).string.example = "_prefix_foo_foo_suffix_", + // invalid + (buf.validate.field).string.example = "fail_example", + (buf.validate.field).string.example = "_prefix_fail_suffix" + ]; + optional string invalid_predefined_rule = 4 [ + (buf.validate.field).string.max_len = 5, + (buf.validate.field).string.(custom.bad_rule) = "suffix", + (buf.validate.field).string.example = "ah", + (buf.validate.field).string.example = "too_long" + ]; +} + +// TODO: fixing +// message TestInt32Rules { +// optional int32 test = 1 [ +// (buf.validate.field).int32.lt = 5, +// (buf.validate.field).int32.(abs_not_in) = 1, +// (buf.validate.field).int32.(abs_not_in) = -2, +// (buf.validate.field).int32.example = 3, +// (buf.validate.field).int32.example = 2 +// ]; +// } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate/buf/validate/validate.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate/buf/validate/validate.proto new file mode 100644 index 0000000000..7236347aa3 --- /dev/null +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/vendor/protovalidate/buf/validate/validate.proto @@ -0,0 +1,4787 @@ +// Copyright 2023 Buf Technologies, Inc. +// +// 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. + +syntax = "proto2"; + +package buf.validate; + +import "google/protobuf/descriptor.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate"; +option java_multiple_files = true; +option java_outer_classname = "ValidateProto"; +option java_package = "build.buf.validate"; + +// MessageOptions is an extension to google.protobuf.MessageOptions. It allows +// the addition of validation rules at the message level. These rules can be +// applied to incoming messages to ensure they meet certain criteria before +// being processed. +extend google.protobuf.MessageOptions { + // Rules specify the validations to be performed on this message. By default, + // no validation is performed against a message. + optional MessageConstraints message = 1159; +} + +// OneofOptions is an extension to google.protobuf.OneofOptions. It allows +// the addition of validation rules on a oneof. These rules can be +// applied to incoming messages to ensure they meet certain criteria before +// being processed. +extend google.protobuf.OneofOptions { + // Rules specify the validations to be performed on this oneof. By default, + // no validation is performed against a oneof. + optional OneofConstraints oneof = 1159; +} + +// FieldOptions is an extension to google.protobuf.FieldOptions. It allows +// the addition of validation rules at the field level. These rules can be +// applied to incoming messages to ensure they meet certain criteria before +// being processed. +extend google.protobuf.FieldOptions { + // Rules specify the validations to be performed on this field. By default, + // no validation is performed against a field. + optional FieldConstraints field = 1159; + + // Specifies predefined rules. When extending a standard constraint message, + // this adds additional CEL expressions that apply when the extension is used. + // + // ```proto + // extend buf.validate.Int32Rules { + // bool is_zero [(buf.validate.predefined).cel = { + // id: "int32.is_zero", + // message: "value must be zero", + // expression: "!rule || this == 0", + // }]; + // } + // + // message Foo { + // int32 reserved = 1 [(buf.validate.field).int32.(is_zero) = true]; + // } + // ``` + optional PredefinedConstraints predefined = 1160; +} + +// `Constraint` represents a validation rule written in the Common Expression +// Language (CEL) syntax. Each Constraint includes a unique identifier, an +// optional error message, and the CEL expression to evaluate. For more +// information on CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). +// +// ```proto +// message Foo { +// option (buf.validate.message).cel = { +// id: "foo.bar" +// message: "bar must be greater than 0" +// expression: "this.bar > 0" +// }; +// int32 bar = 1; +// } +// ``` +message Constraint { + // `id` is a string that serves as a machine-readable name for this Constraint. + // It should be unique within its scope, which could be either a message or a field. + optional string id = 1; + + // `message` is an optional field that provides a human-readable error message + // for this Constraint when the CEL expression evaluates to false. If a + // non-empty message is provided, any strings resulting from the CEL + // expression evaluation are ignored. + optional string message = 2; + + // `expression` is the actual CEL expression that will be evaluated for + // validation. This string must resolve to either a boolean or a string + // value. If the expression evaluates to false or a non-empty string, the + // validation is considered failed, and the message is rejected. + optional string expression = 3; +} + +// MessageConstraints represents validation rules that are applied to the entire message. +// It includes disabling options and a list of Constraint messages representing Common Expression Language (CEL) validation rules. +message MessageConstraints { + // `disabled` is a boolean flag that, when set to true, nullifies any validation rules for this message. + // This includes any fields within the message that would otherwise support validation. + // + // ```proto + // message MyMessage { + // // validation will be bypassed for this message + // option (buf.validate.message).disabled = true; + // } + // ``` + optional bool disabled = 1; + + // `cel` is a repeated field of type Constraint. Each Constraint specifies a validation rule to be applied to this message. + // These constraints are written in Common Expression Language (CEL) syntax. For more information on + // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). + // + // + // ```proto + // message MyMessage { + // // The field `foo` must be greater than 42. + // option (buf.validate.message).cel = { + // id: "my_message.value", + // message: "value must be greater than 42", + // expression: "this.foo > 42", + // }; + // optional int32 foo = 1; + // } + // ``` + repeated Constraint cel = 3; +} + +// The `OneofConstraints` message type enables you to manage constraints for +// oneof fields in your protobuf messages. +message OneofConstraints { + // If `required` is true, exactly one field of the oneof must be present. A + // validation error is returned if no fields in the oneof are present. The + // field itself may still be a default value; further constraints + // should be placed on the fields themselves to ensure they are valid values, + // such as `min_len` or `gt`. + // + // ```proto + // message MyMessage { + // oneof value { + // // Either `a` or `b` must be set. If `a` is set, it must also be + // // non-empty; whereas if `b` is set, it can still be an empty string. + // option (buf.validate.oneof).required = true; + // string a = 1 [(buf.validate.field).string.min_len = 1]; + // string b = 2; + // } + // } + // ``` + optional bool required = 1; +} + +// FieldConstraints encapsulates the rules for each type of field. Depending on +// the field, the correct set should be used to ensure proper validations. +message FieldConstraints { + // `cel` is a repeated field used to represent a textual expression + // in the Common Expression Language (CEL) syntax. For more information on + // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). + // + // ```proto + // message MyMessage { + // // The field `value` must be greater than 42. + // optional int32 value = 1 [(buf.validate.field).cel = { + // id: "my_message.value", + // message: "value must be greater than 42", + // expression: "this > 42", + // }]; + // } + // ``` + repeated Constraint cel = 23; + // If `required` is true, the field must be populated. A populated field can be + // described as "serialized in the wire format," which includes: + // + // - the following "nullable" fields must be explicitly set to be considered populated: + // - singular message fields (whose fields may be unpopulated/default values) + // - member fields of a oneof (may be their default value) + // - proto3 optional fields (may be their default value) + // - proto2 scalar fields (both optional and required) + // - proto3 scalar fields must be non-zero to be considered populated + // - repeated and map fields must be non-empty to be considered populated + // + // ```proto + // message MyMessage { + // // The field `value` must be set to a non-null value. + // optional MyOtherMessage value = 1 [(buf.validate.field).required = true]; + // } + // ``` + optional bool required = 25; + // Skip validation on the field if its value matches the specified criteria. + // See Ignore enum for details. + // + // ```proto + // message UpdateRequest { + // // The uri rule only applies if the field is populated and not an empty + // // string. + // optional string url = 1 [ + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE, + // (buf.validate.field).string.uri = true, + // ]; + // } + // ``` + optional Ignore ignore = 27; + + oneof type { + // Scalar Field Types + FloatRules float = 1; + DoubleRules double = 2; + Int32Rules int32 = 3; + Int64Rules int64 = 4; + UInt32Rules uint32 = 5; + UInt64Rules uint64 = 6; + SInt32Rules sint32 = 7; + SInt64Rules sint64 = 8; + Fixed32Rules fixed32 = 9; + Fixed64Rules fixed64 = 10; + SFixed32Rules sfixed32 = 11; + SFixed64Rules sfixed64 = 12; + BoolRules bool = 13; + StringRules string = 14; + BytesRules bytes = 15; + + // Complex Field Types + EnumRules enum = 16; + RepeatedRules repeated = 18; + MapRules map = 19; + + // Well-Known Field Types + AnyRules any = 20; + DurationRules duration = 21; + TimestampRules timestamp = 22; + } + + // DEPRECATED: use ignore=IGNORE_ALWAYS instead. TODO: remove this field pre-v1. + optional bool skipped = 24 [deprecated = true]; + // DEPRECATED: use ignore=IGNORE_IF_UNPOPULATED instead. TODO: remove this field pre-v1. + optional bool ignore_empty = 26 [deprecated = true]; +} + +// PredefinedConstraints are custom constraints that can be re-used with +// multiple fields. +message PredefinedConstraints { + // `cel` is a repeated field used to represent a textual expression + // in the Common Expression Language (CEL) syntax. For more information on + // CEL, [see our documentation](https://github.com/bufbuild/protovalidate/blob/main/docs/cel.md). + // + // ```proto + // message MyMessage { + // // The field `value` must be greater than 42. + // optional int32 value = 1 [(buf.validate.predefined).cel = { + // id: "my_message.value", + // message: "value must be greater than 42", + // expression: "this > 42", + // }]; + // } + // ``` + repeated Constraint cel = 1; +} + +// Specifies how FieldConstraints.ignore behaves. See the documentation for +// FieldConstraints.required for definitions of "populated" and "nullable". +enum Ignore { + // buf:lint:ignore ENUM_NO_ALLOW_ALIAS // allowance for deprecations. TODO: remove pre-v1. + option allow_alias = true; + // Validation is only skipped if it's an unpopulated nullable fields. + // + // ```proto + // syntax="proto3"; + // + // message Request { + // // The uri rule applies to any value, including the empty string. + // string foo = 1 [ + // (buf.validate.field).string.uri = true + // ]; + // + // // The uri rule only applies if the field is set, including if it's + // // set to the empty string. + // optional string bar = 2 [ + // (buf.validate.field).string.uri = true + // ]; + // + // // The min_items rule always applies, even if the list is empty. + // repeated string baz = 3 [ + // (buf.validate.field).repeated.min_items = 3 + // ]; + // + // // The custom CEL rule applies only if the field is set, including if + // // it's the "zero" value of that message. + // SomeMessage quux = 4 [ + // (buf.validate.field).cel = {/* ... */} + // ]; + // } + // ``` + IGNORE_UNSPECIFIED = 0; + + // Validation is skipped if the field is unpopulated. This rule is redundant + // if the field is already nullable. This value is equivalent behavior to the + // deprecated ignore_empty rule. + // + // ```proto + // syntax="proto3 + // + // message Request { + // // The uri rule applies only if the value is not the empty string. + // string foo = 1 [ + // (buf.validate.field).string.uri = true, + // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED + // ]; + // + // // IGNORE_IF_UNPOPULATED is equivalent to IGNORE_UNSPECIFIED in this + // // case: the uri rule only applies if the field is set, including if + // // it's set to the empty string. + // optional string bar = 2 [ + // (buf.validate.field).string.uri = true, + // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED + // ]; + // + // // The min_items rule only applies if the list has at least one item. + // repeated string baz = 3 [ + // (buf.validate.field).repeated.min_items = 3, + // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED + // ]; + // + // // IGNORE_IF_UNPOPULATED is equivalent to IGNORE_UNSPECIFIED in this + // // case: the custom CEL rule applies only if the field is set, including + // // if it's the "zero" value of that message. + // SomeMessage quux = 4 [ + // (buf.validate.field).cel = {/* ... */}, + // (buf.validate.field).ignore = IGNORE_IF_UNPOPULATED + // ]; + // } + // ``` + IGNORE_IF_UNPOPULATED = 1; + + // Validation is skipped if the field is unpopulated or if it is a nullable + // field populated with its default value. This is typically the zero or + // empty value, but proto2 scalars support custom defaults. For messages, the + // default is a non-null message with all its fields unpopulated. + // + // ```proto + // syntax="proto3 + // + // message Request { + // // IGNORE_IF_DEFAULT_VALUE is equivalent to IGNORE_IF_UNPOPULATED in + // // this case; the uri rule applies only if the value is not the empty + // // string. + // string foo = 1 [ + // (buf.validate.field).string.uri = true, + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE + // ]; + // + // // The uri rule only applies if the field is set to a value other than + // // the empty string. + // optional string bar = 2 [ + // (buf.validate.field).string.uri = true, + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE + // ]; + // + // // IGNORE_IF_DEFAULT_VALUE is equivalent to IGNORE_IF_UNPOPULATED in + // // this case; the min_items rule only applies if the list has at least + // // one item. + // repeated string baz = 3 [ + // (buf.validate.field).repeated.min_items = 3, + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE + // ]; + // + // // The custom CEL rule only applies if the field is set to a value other + // // than an empty message (i.e., fields are unpopulated). + // SomeMessage quux = 4 [ + // (buf.validate.field).cel = {/* ... */}, + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE + // ]; + // } + // ``` + // + // This rule is affected by proto2 custom default values: + // + // ```proto + // syntax="proto2"; + // + // message Request { + // // The gt rule only applies if the field is set and it's value is not + // the default (i.e., not -42). The rule even applies if the field is set + // to zero since the default value differs. + // optional int32 value = 1 [ + // default = -42, + // (buf.validate.field).int32.gt = 0, + // (buf.validate.field).ignore = IGNORE_IF_DEFAULT_VALUE + // ]; + // } + IGNORE_IF_DEFAULT_VALUE = 2; + + // The validation rules of this field will be skipped and not evaluated. This + // is useful for situations that necessitate turning off the rules of a field + // containing a message that may not make sense in the current context, or to + // temporarily disable constraints during development. + // + // ```proto + // message MyMessage { + // // The field's rules will always be ignored, including any validation's + // // on value's fields. + // MyOtherMessage value = 1 [ + // (buf.validate.field).ignore = IGNORE_ALWAYS]; + // } + // ``` + IGNORE_ALWAYS = 3; + + // Deprecated: Use IGNORE_IF_UNPOPULATED instead. TODO: Remove this value pre-v1. + IGNORE_EMPTY = 1 [deprecated = true]; + // Deprecated: Use IGNORE_IF_DEFAULT_VALUE. TODO: Remove this value pre-v1. + IGNORE_DEFAULT = 2 [deprecated = true]; +} + +// FloatRules describes the constraints applied to `float` values. These +// rules may also be applied to the `google.protobuf.FloatValue` Well-Known-Type. +message FloatRules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyFloat { + // // value must equal 42.0 + // float value = 1 [(buf.validate.field).float.const = 42.0]; + // } + // ``` + optional float const = 1 [(predefined).cel = { + id: "float.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyFloat { + // // value must be less than 10.0 + // float value = 1 [(buf.validate.field).float.lt = 10.0]; + // } + // ``` + float lt = 2 [(predefined).cel = { + id: "float.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyFloat { + // // value must be less than or equal to 10.0 + // float value = 1 [(buf.validate.field).float.lte = 10.0]; + // } + // ``` + float lte = 3 [(predefined).cel = { + id: "float.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFloat { + // // value must be greater than 5.0 [float.gt] + // float value = 1 [(buf.validate.field).float.gt = 5.0]; + // + // // value must be greater than 5 and less than 10.0 [float.gt_lt] + // float other_value = 2 [(buf.validate.field).float = { gt: 5.0, lt: 10.0 }]; + // + // // value must be greater than 10 or less than 5.0 [float.gt_lt_exclusive] + // float another_value = 3 [(buf.validate.field).float = { gt: 10.0, lt: 5.0 }]; + // } + // ``` + float gt = 4 [ + (predefined).cel = { + id: "float.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "float.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "float.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "float.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "float.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFloat { + // // value must be greater than or equal to 5.0 [float.gte] + // float value = 1 [(buf.validate.field).float.gte = 5.0]; + // + // // value must be greater than or equal to 5.0 and less than 10.0 [float.gte_lt] + // float other_value = 2 [(buf.validate.field).float = { gte: 5.0, lt: 10.0 }]; + // + // // value must be greater than or equal to 10.0 or less than 5.0 [float.gte_lt_exclusive] + // float another_value = 3 [(buf.validate.field).float = { gte: 10.0, lt: 5.0 }]; + // } + // ``` + float gte = 5 [ + (predefined).cel = { + id: "float.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "float.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "float.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "float.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "float.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message + // is generated. + // + // ```proto + // message MyFloat { + // // value must be in list [1.0, 2.0, 3.0] + // repeated float value = 1 (buf.validate.field).float = { in: [1.0, 2.0, 3.0] }; + // } + // ``` + repeated float in = 6 [(predefined).cel = { + id: "float.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyFloat { + // // value must not be in list [1.0, 2.0, 3.0] + // repeated float value = 1 (buf.validate.field).float = { not_in: [1.0, 2.0, 3.0] }; + // } + // ``` + repeated float not_in = 7 [(predefined).cel = { + id: "float.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `finite` requires the field value to be finite. If the field value is + // infinite or NaN, an error message is generated. + optional bool finite = 8 [(predefined).cel = { + id: "float.finite" + expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFloat { + // float value = 1 [ + // (buf.validate.field).float.example = 1.0, + // (buf.validate.field).float.example = "Infinity" + // ]; + // } + // ``` + repeated float example = 9 [(predefined).cel = { + id: "float.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// DoubleRules describes the constraints applied to `double` values. These +// rules may also be applied to the `google.protobuf.DoubleValue` Well-Known-Type. +message DoubleRules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyDouble { + // // value must equal 42.0 + // double value = 1 [(buf.validate.field).double.const = 42.0]; + // } + // ``` + optional double const = 1 [(predefined).cel = { + id: "double.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyDouble { + // // value must be less than 10.0 + // double value = 1 [(buf.validate.field).double.lt = 10.0]; + // } + // ``` + double lt = 2 [(predefined).cel = { + id: "double.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this >= rules.lt)" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified value + // (field <= value). If the field value is greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyDouble { + // // value must be less than or equal to 10.0 + // double value = 1 [(buf.validate.field).double.lte = 10.0]; + // } + // ``` + double lte = 3 [(predefined).cel = { + id: "double.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && (this.isNan() || this > rules.lte)" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or `lte`, + // the range is reversed, and the field value must be outside the specified + // range. If the field value doesn't meet the required conditions, an error + // message is generated. + // + // ```proto + // message MyDouble { + // // value must be greater than 5.0 [double.gt] + // double value = 1 [(buf.validate.field).double.gt = 5.0]; + // + // // value must be greater than 5 and less than 10.0 [double.gt_lt] + // double other_value = 2 [(buf.validate.field).double = { gt: 5.0, lt: 10.0 }]; + // + // // value must be greater than 10 or less than 5.0 [double.gt_lt_exclusive] + // double another_value = 3 [(buf.validate.field).double = { gt: 10.0, lt: 5.0 }]; + // } + // ``` + double gt = 4 [ + (predefined).cel = { + id: "double.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this <= rules.gt)" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "double.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this.isNan() || this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "double.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (this.isNan() || (rules.lt <= this && this <= rules.gt))" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "double.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this.isNan() || this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "double.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (this.isNan() || (rules.lte < this && this <= rules.gt))" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyDouble { + // // value must be greater than or equal to 5.0 [double.gte] + // double value = 1 [(buf.validate.field).double.gte = 5.0]; + // + // // value must be greater than or equal to 5.0 and less than 10.0 [double.gte_lt] + // double other_value = 2 [(buf.validate.field).double = { gte: 5.0, lt: 10.0 }]; + // + // // value must be greater than or equal to 10.0 or less than 5.0 [double.gte_lt_exclusive] + // double another_value = 3 [(buf.validate.field).double = { gte: 10.0, lt: 5.0 }]; + // } + // ``` + double gte = 5 [ + (predefined).cel = { + id: "double.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && (this.isNan() || this < rules.gte)" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "double.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this.isNan() || this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "double.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (this.isNan() || (rules.lt <= this && this < rules.gte))" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "double.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this.isNan() || this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "double.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (this.isNan() || (rules.lte < this && this < rules.gte))" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyDouble { + // // value must be in list [1.0, 2.0, 3.0] + // repeated double value = 1 (buf.validate.field).double = { in: [1.0, 2.0, 3.0] }; + // } + // ``` + repeated double in = 6 [(predefined).cel = { + id: "double.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyDouble { + // // value must not be in list [1.0, 2.0, 3.0] + // repeated double value = 1 (buf.validate.field).double = { not_in: [1.0, 2.0, 3.0] }; + // } + // ``` + repeated double not_in = 7 [(predefined).cel = { + id: "double.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `finite` requires the field value to be finite. If the field value is + // infinite or NaN, an error message is generated. + optional bool finite = 8 [(predefined).cel = { + id: "double.finite" + expression: "rules.finite ? (this.isNan() || this.isInf() ? 'value must be finite' : '') : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDouble { + // double value = 1 [ + // (buf.validate.field).double.example = 1.0, + // (buf.validate.field).double.example = "Infinity" + // ]; + // } + // ``` + repeated double example = 9 [(predefined).cel = { + id: "double.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// Int32Rules describes the constraints applied to `int32` values. These +// rules may also be applied to the `google.protobuf.Int32Value` Well-Known-Type. +message Int32Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyInt32 { + // // value must equal 42 + // int32 value = 1 [(buf.validate.field).int32.const = 42]; + // } + // ``` + optional int32 const = 1 [(predefined).cel = { + id: "int32.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field + // < value). If the field value is equal to or greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyInt32 { + // // value must be less than 10 + // int32 value = 1 [(buf.validate.field).int32.lt = 10]; + // } + // ``` + int32 lt = 2 [(predefined).cel = { + id: "int32.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyInt32 { + // // value must be less than or equal to 10 + // int32 value = 1 [(buf.validate.field).int32.lte = 10]; + // } + // ``` + int32 lte = 3 [(predefined).cel = { + id: "int32.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyInt32 { + // // value must be greater than 5 [int32.gt] + // int32 value = 1 [(buf.validate.field).int32.gt = 5]; + // + // // value must be greater than 5 and less than 10 [int32.gt_lt] + // int32 other_value = 2 [(buf.validate.field).int32 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [int32.gt_lt_exclusive] + // int32 another_value = 3 [(buf.validate.field).int32 = { gt: 10, lt: 5 }]; + // } + // ``` + int32 gt = 4 [ + (predefined).cel = { + id: "int32.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "int32.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int32.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int32.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "int32.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified value + // (exclusive). If the value of `gte` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyInt32 { + // // value must be greater than or equal to 5 [int32.gte] + // int32 value = 1 [(buf.validate.field).int32.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [int32.gte_lt] + // int32 other_value = 2 [(buf.validate.field).int32 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [int32.gte_lt_exclusive] + // int32 another_value = 3 [(buf.validate.field).int32 = { gte: 10, lt: 5 }]; + // } + // ``` + int32 gte = 5 [ + (predefined).cel = { + id: "int32.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "int32.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int32.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int32.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "int32.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyInt32 { + // // value must be in list [1, 2, 3] + // repeated int32 value = 1 (buf.validate.field).int32 = { in: [1, 2, 3] }; + // } + // ``` + repeated int32 in = 6 [(predefined).cel = { + id: "int32.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error message + // is generated. + // + // ```proto + // message MyInt32 { + // // value must not be in list [1, 2, 3] + // repeated int32 value = 1 (buf.validate.field).int32 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated int32 not_in = 7 [(predefined).cel = { + id: "int32.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt32 { + // int32 value = 1 [ + // (buf.validate.field).int32.example = 1, + // (buf.validate.field).int32.example = -10 + // ]; + // } + // ``` + repeated int32 example = 8 [(predefined).cel = { + id: "int32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// Int64Rules describes the constraints applied to `int64` values. These +// rules may also be applied to the `google.protobuf.Int64Value` Well-Known-Type. +message Int64Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyInt64 { + // // value must equal 42 + // int64 value = 1 [(buf.validate.field).int64.const = 42]; + // } + // ``` + optional int64 const = 1 [(predefined).cel = { + id: "int64.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyInt64 { + // // value must be less than 10 + // int64 value = 1 [(buf.validate.field).int64.lt = 10]; + // } + // ``` + int64 lt = 2 [(predefined).cel = { + id: "int64.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyInt64 { + // // value must be less than or equal to 10 + // int64 value = 1 [(buf.validate.field).int64.lte = 10]; + // } + // ``` + int64 lte = 3 [(predefined).cel = { + id: "int64.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyInt64 { + // // value must be greater than 5 [int64.gt] + // int64 value = 1 [(buf.validate.field).int64.gt = 5]; + // + // // value must be greater than 5 and less than 10 [int64.gt_lt] + // int64 other_value = 2 [(buf.validate.field).int64 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [int64.gt_lt_exclusive] + // int64 another_value = 3 [(buf.validate.field).int64 = { gt: 10, lt: 5 }]; + // } + // ``` + int64 gt = 4 [ + (predefined).cel = { + id: "int64.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "int64.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int64.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int64.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "int64.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyInt64 { + // // value must be greater than or equal to 5 [int64.gte] + // int64 value = 1 [(buf.validate.field).int64.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [int64.gte_lt] + // int64 other_value = 2 [(buf.validate.field).int64 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [int64.gte_lt_exclusive] + // int64 another_value = 3 [(buf.validate.field).int64 = { gte: 10, lt: 5 }]; + // } + // ``` + int64 gte = 5 [ + (predefined).cel = { + id: "int64.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "int64.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int64.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "int64.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "int64.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyInt64 { + // // value must be in list [1, 2, 3] + // repeated int64 value = 1 (buf.validate.field).int64 = { in: [1, 2, 3] }; + // } + // ``` + repeated int64 in = 6 [(predefined).cel = { + id: "int64.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyInt64 { + // // value must not be in list [1, 2, 3] + // repeated int64 value = 1 (buf.validate.field).int64 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated int64 not_in = 7 [(predefined).cel = { + id: "int64.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyInt64 { + // int64 value = 1 [ + // (buf.validate.field).int64.example = 1, + // (buf.validate.field).int64.example = -10 + // ]; + // } + // ``` + repeated int64 example = 9 [(predefined).cel = { + id: "int64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// UInt32Rules describes the constraints applied to `uint32` values. These +// rules may also be applied to the `google.protobuf.UInt32Value` Well-Known-Type. +message UInt32Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyUInt32 { + // // value must equal 42 + // uint32 value = 1 [(buf.validate.field).uint32.const = 42]; + // } + // ``` + optional uint32 const = 1 [(predefined).cel = { + id: "uint32.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyUInt32 { + // // value must be less than 10 + // uint32 value = 1 [(buf.validate.field).uint32.lt = 10]; + // } + // ``` + uint32 lt = 2 [(predefined).cel = { + id: "uint32.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyUInt32 { + // // value must be less than or equal to 10 + // uint32 value = 1 [(buf.validate.field).uint32.lte = 10]; + // } + // ``` + uint32 lte = 3 [(predefined).cel = { + id: "uint32.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyUInt32 { + // // value must be greater than 5 [uint32.gt] + // uint32 value = 1 [(buf.validate.field).uint32.gt = 5]; + // + // // value must be greater than 5 and less than 10 [uint32.gt_lt] + // uint32 other_value = 2 [(buf.validate.field).uint32 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [uint32.gt_lt_exclusive] + // uint32 another_value = 3 [(buf.validate.field).uint32 = { gt: 10, lt: 5 }]; + // } + // ``` + uint32 gt = 4 [ + (predefined).cel = { + id: "uint32.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "uint32.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint32.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint32.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "uint32.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyUInt32 { + // // value must be greater than or equal to 5 [uint32.gte] + // uint32 value = 1 [(buf.validate.field).uint32.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [uint32.gte_lt] + // uint32 other_value = 2 [(buf.validate.field).uint32 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [uint32.gte_lt_exclusive] + // uint32 another_value = 3 [(buf.validate.field).uint32 = { gte: 10, lt: 5 }]; + // } + // ``` + uint32 gte = 5 [ + (predefined).cel = { + id: "uint32.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "uint32.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint32.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint32.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "uint32.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyUInt32 { + // // value must be in list [1, 2, 3] + // repeated uint32 value = 1 (buf.validate.field).uint32 = { in: [1, 2, 3] }; + // } + // ``` + repeated uint32 in = 6 [(predefined).cel = { + id: "uint32.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyUInt32 { + // // value must not be in list [1, 2, 3] + // repeated uint32 value = 1 (buf.validate.field).uint32 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated uint32 not_in = 7 [(predefined).cel = { + id: "uint32.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt32 { + // uint32 value = 1 [ + // (buf.validate.field).uint32.example = 1, + // (buf.validate.field).uint32.example = 10 + // ]; + // } + // ``` + repeated uint32 example = 8 [(predefined).cel = { + id: "uint32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// UInt64Rules describes the constraints applied to `uint64` values. These +// rules may also be applied to the `google.protobuf.UInt64Value` Well-Known-Type. +message UInt64Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyUInt64 { + // // value must equal 42 + // uint64 value = 1 [(buf.validate.field).uint64.const = 42]; + // } + // ``` + optional uint64 const = 1 [(predefined).cel = { + id: "uint64.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyUInt64 { + // // value must be less than 10 + // uint64 value = 1 [(buf.validate.field).uint64.lt = 10]; + // } + // ``` + uint64 lt = 2 [(predefined).cel = { + id: "uint64.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyUInt64 { + // // value must be less than or equal to 10 + // uint64 value = 1 [(buf.validate.field).uint64.lte = 10]; + // } + // ``` + uint64 lte = 3 [(predefined).cel = { + id: "uint64.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyUInt64 { + // // value must be greater than 5 [uint64.gt] + // uint64 value = 1 [(buf.validate.field).uint64.gt = 5]; + // + // // value must be greater than 5 and less than 10 [uint64.gt_lt] + // uint64 other_value = 2 [(buf.validate.field).uint64 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [uint64.gt_lt_exclusive] + // uint64 another_value = 3 [(buf.validate.field).uint64 = { gt: 10, lt: 5 }]; + // } + // ``` + uint64 gt = 4 [ + (predefined).cel = { + id: "uint64.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "uint64.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint64.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint64.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "uint64.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyUInt64 { + // // value must be greater than or equal to 5 [uint64.gte] + // uint64 value = 1 [(buf.validate.field).uint64.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [uint64.gte_lt] + // uint64 other_value = 2 [(buf.validate.field).uint64 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [uint64.gte_lt_exclusive] + // uint64 another_value = 3 [(buf.validate.field).uint64 = { gte: 10, lt: 5 }]; + // } + // ``` + uint64 gte = 5 [ + (predefined).cel = { + id: "uint64.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "uint64.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint64.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "uint64.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "uint64.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyUInt64 { + // // value must be in list [1, 2, 3] + // repeated uint64 value = 1 (buf.validate.field).uint64 = { in: [1, 2, 3] }; + // } + // ``` + repeated uint64 in = 6 [(predefined).cel = { + id: "uint64.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyUInt64 { + // // value must not be in list [1, 2, 3] + // repeated uint64 value = 1 (buf.validate.field).uint64 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated uint64 not_in = 7 [(predefined).cel = { + id: "uint64.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyUInt64 { + // uint64 value = 1 [ + // (buf.validate.field).uint64.example = 1, + // (buf.validate.field).uint64.example = -10 + // ]; + // } + // ``` + repeated uint64 example = 8 [(predefined).cel = { + id: "uint64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// SInt32Rules describes the constraints applied to `sint32` values. +message SInt32Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MySInt32 { + // // value must equal 42 + // sint32 value = 1 [(buf.validate.field).sint32.const = 42]; + // } + // ``` + optional sint32 const = 1 [(predefined).cel = { + id: "sint32.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field + // < value). If the field value is equal to or greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySInt32 { + // // value must be less than 10 + // sint32 value = 1 [(buf.validate.field).sint32.lt = 10]; + // } + // ``` + sint32 lt = 2 [(predefined).cel = { + id: "sint32.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySInt32 { + // // value must be less than or equal to 10 + // sint32 value = 1 [(buf.validate.field).sint32.lte = 10]; + // } + // ``` + sint32 lte = 3 [(predefined).cel = { + id: "sint32.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySInt32 { + // // value must be greater than 5 [sint32.gt] + // sint32 value = 1 [(buf.validate.field).sint32.gt = 5]; + // + // // value must be greater than 5 and less than 10 [sint32.gt_lt] + // sint32 other_value = 2 [(buf.validate.field).sint32 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [sint32.gt_lt_exclusive] + // sint32 another_value = 3 [(buf.validate.field).sint32 = { gt: 10, lt: 5 }]; + // } + // ``` + sint32 gt = 4 [ + (predefined).cel = { + id: "sint32.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "sint32.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint32.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint32.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sint32.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySInt32 { + // // value must be greater than or equal to 5 [sint32.gte] + // sint32 value = 1 [(buf.validate.field).sint32.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [sint32.gte_lt] + // sint32 other_value = 2 [(buf.validate.field).sint32 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [sint32.gte_lt_exclusive] + // sint32 another_value = 3 [(buf.validate.field).sint32 = { gte: 10, lt: 5 }]; + // } + // ``` + sint32 gte = 5 [ + (predefined).cel = { + id: "sint32.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "sint32.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint32.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint32.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sint32.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MySInt32 { + // // value must be in list [1, 2, 3] + // repeated sint32 value = 1 (buf.validate.field).sint32 = { in: [1, 2, 3] }; + // } + // ``` + repeated sint32 in = 6 [(predefined).cel = { + id: "sint32.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MySInt32 { + // // value must not be in list [1, 2, 3] + // repeated sint32 value = 1 (buf.validate.field).sint32 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated sint32 not_in = 7 [(predefined).cel = { + id: "sint32.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt32 { + // sint32 value = 1 [ + // (buf.validate.field).sint32.example = 1, + // (buf.validate.field).sint32.example = -10 + // ]; + // } + // ``` + repeated sint32 example = 8 [(predefined).cel = { + id: "sint32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// SInt64Rules describes the constraints applied to `sint64` values. +message SInt64Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MySInt64 { + // // value must equal 42 + // sint64 value = 1 [(buf.validate.field).sint64.const = 42]; + // } + // ``` + optional sint64 const = 1 [(predefined).cel = { + id: "sint64.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field + // < value). If the field value is equal to or greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySInt64 { + // // value must be less than 10 + // sint64 value = 1 [(buf.validate.field).sint64.lt = 10]; + // } + // ``` + sint64 lt = 2 [(predefined).cel = { + id: "sint64.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySInt64 { + // // value must be less than or equal to 10 + // sint64 value = 1 [(buf.validate.field).sint64.lte = 10]; + // } + // ``` + sint64 lte = 3 [(predefined).cel = { + id: "sint64.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySInt64 { + // // value must be greater than 5 [sint64.gt] + // sint64 value = 1 [(buf.validate.field).sint64.gt = 5]; + // + // // value must be greater than 5 and less than 10 [sint64.gt_lt] + // sint64 other_value = 2 [(buf.validate.field).sint64 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [sint64.gt_lt_exclusive] + // sint64 another_value = 3 [(buf.validate.field).sint64 = { gt: 10, lt: 5 }]; + // } + // ``` + sint64 gt = 4 [ + (predefined).cel = { + id: "sint64.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "sint64.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint64.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint64.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sint64.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySInt64 { + // // value must be greater than or equal to 5 [sint64.gte] + // sint64 value = 1 [(buf.validate.field).sint64.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [sint64.gte_lt] + // sint64 other_value = 2 [(buf.validate.field).sint64 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [sint64.gte_lt_exclusive] + // sint64 another_value = 3 [(buf.validate.field).sint64 = { gte: 10, lt: 5 }]; + // } + // ``` + sint64 gte = 5 [ + (predefined).cel = { + id: "sint64.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "sint64.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint64.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sint64.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sint64.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message + // is generated. + // + // ```proto + // message MySInt64 { + // // value must be in list [1, 2, 3] + // repeated sint64 value = 1 (buf.validate.field).sint64 = { in: [1, 2, 3] }; + // } + // ``` + repeated sint64 in = 6 [(predefined).cel = { + id: "sint64.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MySInt64 { + // // value must not be in list [1, 2, 3] + // repeated sint64 value = 1 (buf.validate.field).sint64 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated sint64 not_in = 7 [(predefined).cel = { + id: "sint64.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySInt64 { + // sint64 value = 1 [ + // (buf.validate.field).sint64.example = 1, + // (buf.validate.field).sint64.example = -10 + // ]; + // } + // ``` + repeated sint64 example = 8 [(predefined).cel = { + id: "sint64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// Fixed32Rules describes the constraints applied to `fixed32` values. +message Fixed32Rules { + // `const` requires the field value to exactly match the specified value. + // If the field value doesn't match, an error message is generated. + // + // ```proto + // message MyFixed32 { + // // value must equal 42 + // fixed32 value = 1 [(buf.validate.field).fixed32.const = 42]; + // } + // ``` + optional fixed32 const = 1 [(predefined).cel = { + id: "fixed32.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyFixed32 { + // // value must be less than 10 + // fixed32 value = 1 [(buf.validate.field).fixed32.lt = 10]; + // } + // ``` + fixed32 lt = 2 [(predefined).cel = { + id: "fixed32.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyFixed32 { + // // value must be less than or equal to 10 + // fixed32 value = 1 [(buf.validate.field).fixed32.lte = 10]; + // } + // ``` + fixed32 lte = 3 [(predefined).cel = { + id: "fixed32.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFixed32 { + // // value must be greater than 5 [fixed32.gt] + // fixed32 value = 1 [(buf.validate.field).fixed32.gt = 5]; + // + // // value must be greater than 5 and less than 10 [fixed32.gt_lt] + // fixed32 other_value = 2 [(buf.validate.field).fixed32 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [fixed32.gt_lt_exclusive] + // fixed32 another_value = 3 [(buf.validate.field).fixed32 = { gt: 10, lt: 5 }]; + // } + // ``` + fixed32 gt = 4 [ + (predefined).cel = { + id: "fixed32.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "fixed32.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed32.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed32.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "fixed32.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFixed32 { + // // value must be greater than or equal to 5 [fixed32.gte] + // fixed32 value = 1 [(buf.validate.field).fixed32.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [fixed32.gte_lt] + // fixed32 other_value = 2 [(buf.validate.field).fixed32 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [fixed32.gte_lt_exclusive] + // fixed32 another_value = 3 [(buf.validate.field).fixed32 = { gte: 10, lt: 5 }]; + // } + // ``` + fixed32 gte = 5 [ + (predefined).cel = { + id: "fixed32.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "fixed32.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed32.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed32.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "fixed32.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message + // is generated. + // + // ```proto + // message MyFixed32 { + // // value must be in list [1, 2, 3] + // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { in: [1, 2, 3] }; + // } + // ``` + repeated fixed32 in = 6 [(predefined).cel = { + id: "fixed32.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyFixed32 { + // // value must not be in list [1, 2, 3] + // repeated fixed32 value = 1 (buf.validate.field).fixed32 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated fixed32 not_in = 7 [(predefined).cel = { + id: "fixed32.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed32 { + // fixed32 value = 1 [ + // (buf.validate.field).fixed32.example = 1, + // (buf.validate.field).fixed32.example = 2 + // ]; + // } + // ``` + repeated fixed32 example = 8 [(predefined).cel = { + id: "fixed32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// Fixed64Rules describes the constraints applied to `fixed64` values. +message Fixed64Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyFixed64 { + // // value must equal 42 + // fixed64 value = 1 [(buf.validate.field).fixed64.const = 42]; + // } + // ``` + optional fixed64 const = 1 [(predefined).cel = { + id: "fixed64.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MyFixed64 { + // // value must be less than 10 + // fixed64 value = 1 [(buf.validate.field).fixed64.lt = 10]; + // } + // ``` + fixed64 lt = 2 [(predefined).cel = { + id: "fixed64.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MyFixed64 { + // // value must be less than or equal to 10 + // fixed64 value = 1 [(buf.validate.field).fixed64.lte = 10]; + // } + // ``` + fixed64 lte = 3 [(predefined).cel = { + id: "fixed64.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFixed64 { + // // value must be greater than 5 [fixed64.gt] + // fixed64 value = 1 [(buf.validate.field).fixed64.gt = 5]; + // + // // value must be greater than 5 and less than 10 [fixed64.gt_lt] + // fixed64 other_value = 2 [(buf.validate.field).fixed64 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [fixed64.gt_lt_exclusive] + // fixed64 another_value = 3 [(buf.validate.field).fixed64 = { gt: 10, lt: 5 }]; + // } + // ``` + fixed64 gt = 4 [ + (predefined).cel = { + id: "fixed64.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "fixed64.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed64.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed64.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "fixed64.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyFixed64 { + // // value must be greater than or equal to 5 [fixed64.gte] + // fixed64 value = 1 [(buf.validate.field).fixed64.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [fixed64.gte_lt] + // fixed64 other_value = 2 [(buf.validate.field).fixed64 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [fixed64.gte_lt_exclusive] + // fixed64 another_value = 3 [(buf.validate.field).fixed64 = { gte: 10, lt: 5 }]; + // } + // ``` + fixed64 gte = 5 [ + (predefined).cel = { + id: "fixed64.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "fixed64.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed64.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "fixed64.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "fixed64.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MyFixed64 { + // // value must be in list [1, 2, 3] + // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { in: [1, 2, 3] }; + // } + // ``` + repeated fixed64 in = 6 [(predefined).cel = { + id: "fixed64.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MyFixed64 { + // // value must not be in list [1, 2, 3] + // repeated fixed64 value = 1 (buf.validate.field).fixed64 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated fixed64 not_in = 7 [(predefined).cel = { + id: "fixed64.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyFixed64 { + // fixed64 value = 1 [ + // (buf.validate.field).fixed64.example = 1, + // (buf.validate.field).fixed64.example = 2 + // ]; + // } + // ``` + repeated fixed64 example = 8 [(predefined).cel = { + id: "fixed64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// SFixed32Rules describes the constraints applied to `fixed32` values. +message SFixed32Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MySFixed32 { + // // value must equal 42 + // sfixed32 value = 1 [(buf.validate.field).sfixed32.const = 42]; + // } + // ``` + optional sfixed32 const = 1 [(predefined).cel = { + id: "sfixed32.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MySFixed32 { + // // value must be less than 10 + // sfixed32 value = 1 [(buf.validate.field).sfixed32.lt = 10]; + // } + // ``` + sfixed32 lt = 2 [(predefined).cel = { + id: "sfixed32.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySFixed32 { + // // value must be less than or equal to 10 + // sfixed32 value = 1 [(buf.validate.field).sfixed32.lte = 10]; + // } + // ``` + sfixed32 lte = 3 [(predefined).cel = { + id: "sfixed32.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySFixed32 { + // // value must be greater than 5 [sfixed32.gt] + // sfixed32 value = 1 [(buf.validate.field).sfixed32.gt = 5]; + // + // // value must be greater than 5 and less than 10 [sfixed32.gt_lt] + // sfixed32 other_value = 2 [(buf.validate.field).sfixed32 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [sfixed32.gt_lt_exclusive] + // sfixed32 another_value = 3 [(buf.validate.field).sfixed32 = { gt: 10, lt: 5 }]; + // } + // ``` + sfixed32 gt = 4 [ + (predefined).cel = { + id: "sfixed32.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySFixed32 { + // // value must be greater than or equal to 5 [sfixed32.gte] + // sfixed32 value = 1 [(buf.validate.field).sfixed32.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [sfixed32.gte_lt] + // sfixed32 other_value = 2 [(buf.validate.field).sfixed32 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [sfixed32.gte_lt_exclusive] + // sfixed32 another_value = 3 [(buf.validate.field).sfixed32 = { gte: 10, lt: 5 }]; + // } + // ``` + sfixed32 gte = 5 [ + (predefined).cel = { + id: "sfixed32.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sfixed32.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MySFixed32 { + // // value must be in list [1, 2, 3] + // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { in: [1, 2, 3] }; + // } + // ``` + repeated sfixed32 in = 6 [(predefined).cel = { + id: "sfixed32.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MySFixed32 { + // // value must not be in list [1, 2, 3] + // repeated sfixed32 value = 1 (buf.validate.field).sfixed32 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated sfixed32 not_in = 7 [(predefined).cel = { + id: "sfixed32.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed32 { + // sfixed32 value = 1 [ + // (buf.validate.field).sfixed32.example = 1, + // (buf.validate.field).sfixed32.example = 2 + // ]; + // } + // ``` + repeated sfixed32 example = 8 [(predefined).cel = { + id: "sfixed32.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// SFixed64Rules describes the constraints applied to `fixed64` values. +message SFixed64Rules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MySFixed64 { + // // value must equal 42 + // sfixed64 value = 1 [(buf.validate.field).sfixed64.const = 42]; + // } + // ``` + optional sfixed64 const = 1 [(predefined).cel = { + id: "sfixed64.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` requires the field value to be less than the specified value (field < + // value). If the field value is equal to or greater than the specified value, + // an error message is generated. + // + // ```proto + // message MySFixed64 { + // // value must be less than 10 + // sfixed64 value = 1 [(buf.validate.field).sfixed64.lt = 10]; + // } + // ``` + sfixed64 lt = 2 [(predefined).cel = { + id: "sfixed64.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` requires the field value to be less than or equal to the specified + // value (field <= value). If the field value is greater than the specified + // value, an error message is generated. + // + // ```proto + // message MySFixed64 { + // // value must be less than or equal to 10 + // sfixed64 value = 1 [(buf.validate.field).sfixed64.lte = 10]; + // } + // ``` + sfixed64 lte = 3 [(predefined).cel = { + id: "sfixed64.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the field value to be greater than the specified value + // (exclusive). If the value of `gt` is larger than a specified `lt` or + // `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySFixed64 { + // // value must be greater than 5 [sfixed64.gt] + // sfixed64 value = 1 [(buf.validate.field).sfixed64.gt = 5]; + // + // // value must be greater than 5 and less than 10 [sfixed64.gt_lt] + // sfixed64 other_value = 2 [(buf.validate.field).sfixed64 = { gt: 5, lt: 10 }]; + // + // // value must be greater than 10 or less than 5 [sfixed64.gt_lt_exclusive] + // sfixed64 another_value = 3 [(buf.validate.field).sfixed64 = { gt: 10, lt: 5 }]; + // } + // ``` + sfixed64 gt = 4 [ + (predefined).cel = { + id: "sfixed64.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the field value to be greater than or equal to the specified + // value (exclusive). If the value of `gte` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MySFixed64 { + // // value must be greater than or equal to 5 [sfixed64.gte] + // sfixed64 value = 1 [(buf.validate.field).sfixed64.gte = 5]; + // + // // value must be greater than or equal to 5 and less than 10 [sfixed64.gte_lt] + // sfixed64 other_value = 2 [(buf.validate.field).sfixed64 = { gte: 5, lt: 10 }]; + // + // // value must be greater than or equal to 10 or less than 5 [sfixed64.gte_lt_exclusive] + // sfixed64 another_value = 3 [(buf.validate.field).sfixed64 = { gte: 10, lt: 5 }]; + // } + // ``` + sfixed64 gte = 5 [ + (predefined).cel = { + id: "sfixed64.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "sfixed64.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` requires the field value to be equal to one of the specified values. + // If the field value isn't one of the specified values, an error message is + // generated. + // + // ```proto + // message MySFixed64 { + // // value must be in list [1, 2, 3] + // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { in: [1, 2, 3] }; + // } + // ``` + repeated sfixed64 in = 6 [(predefined).cel = { + id: "sfixed64.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to not be equal to any of the specified + // values. If the field value is one of the specified values, an error + // message is generated. + // + // ```proto + // message MySFixed64 { + // // value must not be in list [1, 2, 3] + // repeated sfixed64 value = 1 (buf.validate.field).sfixed64 = { not_in: [1, 2, 3] }; + // } + // ``` + repeated sfixed64 not_in = 7 [(predefined).cel = { + id: "sfixed64.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MySFixed64 { + // sfixed64 value = 1 [ + // (buf.validate.field).sfixed64.example = 1, + // (buf.validate.field).sfixed64.example = 2 + // ]; + // } + // ``` + repeated sfixed64 example = 8 [(predefined).cel = { + id: "sfixed64.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// BoolRules describes the constraints applied to `bool` values. These rules +// may also be applied to the `google.protobuf.BoolValue` Well-Known-Type. +message BoolRules { + // `const` requires the field value to exactly match the specified boolean value. + // If the field value doesn't match, an error message is generated. + // + // ```proto + // message MyBool { + // // value must equal true + // bool value = 1 [(buf.validate.field).bool.const = true]; + // } + // ``` + optional bool const = 1 [(predefined).cel = { + id: "bool.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBool { + // bool value = 1 [ + // (buf.validate.field).bool.example = 1, + // (buf.validate.field).bool.example = 2 + // ]; + // } + // ``` + repeated bool example = 2 [(predefined).cel = { + id: "bool.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// StringRules describes the constraints applied to `string` values These +// rules may also be applied to the `google.protobuf.StringValue` Well-Known-Type. +message StringRules { + // `const` requires the field value to exactly match the specified value. If + // the field value doesn't match, an error message is generated. + // + // ```proto + // message MyString { + // // value must equal `hello` + // string value = 1 [(buf.validate.field).string.const = "hello"]; + // } + // ``` + optional string const = 1 [(predefined).cel = { + id: "string.const" + expression: "this != rules.const ? 'value must equal `%s`'.format([rules.const]) : ''" + }]; + + // `len` dictates that the field value must have the specified + // number of characters (Unicode code points), which may differ from the number + // of bytes in the string. If the field value does not meet the specified + // length, an error message will be generated. + // + // ```proto + // message MyString { + // // value length must be 5 characters + // string value = 1 [(buf.validate.field).string.len = 5]; + // } + // ``` + optional uint64 len = 19 [(predefined).cel = { + id: "string.len" + expression: "uint(this.size()) != rules.len ? 'value length must be %s characters'.format([rules.len]) : ''" + }]; + + // `min_len` specifies that the field value must have at least the specified + // number of characters (Unicode code points), which may differ from the number + // of bytes in the string. If the field value contains fewer characters, an error + // message will be generated. + // + // ```proto + // message MyString { + // // value length must be at least 3 characters + // string value = 1 [(buf.validate.field).string.min_len = 3]; + // } + // ``` + optional uint64 min_len = 2 [(predefined).cel = { + id: "string.min_len" + expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s characters'.format([rules.min_len]) : ''" + }]; + + // `max_len` specifies that the field value must have no more than the specified + // number of characters (Unicode code points), which may differ from the + // number of bytes in the string. If the field value contains more characters, + // an error message will be generated. + // + // ```proto + // message MyString { + // // value length must be at most 10 characters + // string value = 1 [(buf.validate.field).string.max_len = 10]; + // } + // ``` + optional uint64 max_len = 3 [(predefined).cel = { + id: "string.max_len" + expression: "uint(this.size()) > rules.max_len ? 'value length must be at most %s characters'.format([rules.max_len]) : ''" + }]; + + // `len_bytes` dictates that the field value must have the specified number of + // bytes. If the field value does not match the specified length in bytes, + // an error message will be generated. + // + // ```proto + // message MyString { + // // value length must be 6 bytes + // string value = 1 [(buf.validate.field).string.len_bytes = 6]; + // } + // ``` + optional uint64 len_bytes = 20 [(predefined).cel = { + id: "string.len_bytes" + expression: "uint(bytes(this).size()) != rules.len_bytes ? 'value length must be %s bytes'.format([rules.len_bytes]) : ''" + }]; + + // `min_bytes` specifies that the field value must have at least the specified + // number of bytes. If the field value contains fewer bytes, an error message + // will be generated. + // + // ```proto + // message MyString { + // // value length must be at least 4 bytes + // string value = 1 [(buf.validate.field).string.min_bytes = 4]; + // } + // + // ``` + optional uint64 min_bytes = 4 [(predefined).cel = { + id: "string.min_bytes" + expression: "uint(bytes(this).size()) < rules.min_bytes ? 'value length must be at least %s bytes'.format([rules.min_bytes]) : ''" + }]; + + // `max_bytes` specifies that the field value must have no more than the + //specified number of bytes. If the field value contains more bytes, an + // error message will be generated. + // + // ```proto + // message MyString { + // // value length must be at most 8 bytes + // string value = 1 [(buf.validate.field).string.max_bytes = 8]; + // } + // ``` + optional uint64 max_bytes = 5 [(predefined).cel = { + id: "string.max_bytes" + expression: "uint(bytes(this).size()) > rules.max_bytes ? 'value length must be at most %s bytes'.format([rules.max_bytes]) : ''" + }]; + + // `pattern` specifies that the field value must match the specified + // regular expression (RE2 syntax), with the expression provided without any + // delimiters. If the field value doesn't match the regular expression, an + // error message will be generated. + // + // ```proto + // message MyString { + // // value does not match regex pattern `^[a-zA-Z]//$` + // string value = 1 [(buf.validate.field).string.pattern = "^[a-zA-Z]//$"]; + // } + // ``` + optional string pattern = 6 [(predefined).cel = { + id: "string.pattern" + expression: "!this.matches(rules.pattern) ? 'value does not match regex pattern `%s`'.format([rules.pattern]) : ''" + }]; + + // `prefix` specifies that the field value must have the + //specified substring at the beginning of the string. If the field value + // doesn't start with the specified prefix, an error message will be + // generated. + // + // ```proto + // message MyString { + // // value does not have prefix `pre` + // string value = 1 [(buf.validate.field).string.prefix = "pre"]; + // } + // ``` + optional string prefix = 7 [(predefined).cel = { + id: "string.prefix" + expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix `%s`'.format([rules.prefix]) : ''" + }]; + + // `suffix` specifies that the field value must have the + //specified substring at the end of the string. If the field value doesn't + // end with the specified suffix, an error message will be generated. + // + // ```proto + // message MyString { + // // value does not have suffix `post` + // string value = 1 [(buf.validate.field).string.suffix = "post"]; + // } + // ``` + optional string suffix = 8 [(predefined).cel = { + id: "string.suffix" + expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix `%s`'.format([rules.suffix]) : ''" + }]; + + // `contains` specifies that the field value must have the + //specified substring anywhere in the string. If the field value doesn't + // contain the specified substring, an error message will be generated. + // + // ```proto + // message MyString { + // // value does not contain substring `inside`. + // string value = 1 [(buf.validate.field).string.contains = "inside"]; + // } + // ``` + optional string contains = 9 [(predefined).cel = { + id: "string.contains" + expression: "!this.contains(rules.contains) ? 'value does not contain substring `%s`'.format([rules.contains]) : ''" + }]; + + // `not_contains` specifies that the field value must not have the + //specified substring anywhere in the string. If the field value contains + // the specified substring, an error message will be generated. + // + // ```proto + // message MyString { + // // value contains substring `inside`. + // string value = 1 [(buf.validate.field).string.not_contains = "inside"]; + // } + // ``` + optional string not_contains = 23 [(predefined).cel = { + id: "string.not_contains" + expression: "this.contains(rules.not_contains) ? 'value contains substring `%s`'.format([rules.not_contains]) : ''" + }]; + + // `in` specifies that the field value must be equal to one of the specified + // values. If the field value isn't one of the specified values, an error + // message will be generated. + // + // ```proto + // message MyString { + // // value must be in list ["apple", "banana"] + // repeated string value = 1 [(buf.validate.field).string.in = "apple", (buf.validate.field).string.in = "banana"]; + // } + // ``` + repeated string in = 10 [(predefined).cel = { + id: "string.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` specifies that the field value cannot be equal to any + // of the specified values. If the field value is one of the specified values, + // an error message will be generated. + // ```proto + // message MyString { + // // value must not be in list ["orange", "grape"] + // repeated string value = 1 [(buf.validate.field).string.not_in = "orange", (buf.validate.field).string.not_in = "grape"]; + // } + // ``` + repeated string not_in = 11 [(predefined).cel = { + id: "string.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `WellKnown` rules provide advanced constraints against common string + // patterns + oneof well_known { + // `email` specifies that the field value must be a valid email address + // (addr-spec only) as defined by [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.4.1). + // If the field value isn't a valid email address, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid email address + // string value = 1 [(buf.validate.field).string.email = true]; + // } + // ``` + bool email = 12 [ + (predefined).cel = { + id: "string.email" + message: "value must be a valid email address" + expression: "!rules.email || this == '' || this.isEmail()" + }, + (predefined).cel = { + id: "string.email_empty" + message: "value is empty, which is not a valid email address" + expression: "!rules.email || this != ''" + } + ]; + + // `hostname` specifies that the field value must be a valid + // hostname as defined by [RFC 1034](https://tools.ietf.org/html/rfc1034#section-3.5). This constraint doesn't support + // internationalized domain names (IDNs). If the field value isn't a + // valid hostname, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid hostname + // string value = 1 [(buf.validate.field).string.hostname = true]; + // } + // ``` + bool hostname = 13 [ + (predefined).cel = { + id: "string.hostname" + message: "value must be a valid hostname" + expression: "!rules.hostname || this == '' || this.isHostname()" + }, + (predefined).cel = { + id: "string.hostname_empty" + message: "value is empty, which is not a valid hostname" + expression: "!rules.hostname || this != ''" + } + ]; + + // `ip` specifies that the field value must be a valid IP + // (v4 or v6) address, without surrounding square brackets for IPv6 addresses. + // If the field value isn't a valid IP address, an error message will be + // generated. + // + // ```proto + // message MyString { + // // value must be a valid IP address + // string value = 1 [(buf.validate.field).string.ip = true]; + // } + // ``` + bool ip = 14 [ + (predefined).cel = { + id: "string.ip" + message: "value must be a valid IP address" + expression: "!rules.ip || this == '' || this.isIp()" + }, + (predefined).cel = { + id: "string.ip_empty" + message: "value is empty, which is not a valid IP address" + expression: "!rules.ip || this != ''" + } + ]; + + // `ipv4` specifies that the field value must be a valid IPv4 + // address. If the field value isn't a valid IPv4 address, an error message + // will be generated. + // + // ```proto + // message MyString { + // // value must be a valid IPv4 address + // string value = 1 [(buf.validate.field).string.ipv4 = true]; + // } + // ``` + bool ipv4 = 15 [ + (predefined).cel = { + id: "string.ipv4" + message: "value must be a valid IPv4 address" + expression: "!rules.ipv4 || this == '' || this.isIp(4)" + }, + (predefined).cel = { + id: "string.ipv4_empty" + message: "value is empty, which is not a valid IPv4 address" + expression: "!rules.ipv4 || this != ''" + } + ]; + + // `ipv6` specifies that the field value must be a valid + // IPv6 address, without surrounding square brackets. If the field value is + // not a valid IPv6 address, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid IPv6 address + // string value = 1 [(buf.validate.field).string.ipv6 = true]; + // } + // ``` + bool ipv6 = 16 [ + (predefined).cel = { + id: "string.ipv6" + message: "value must be a valid IPv6 address" + expression: "!rules.ipv6 || this == '' || this.isIp(6)" + }, + (predefined).cel = { + id: "string.ipv6_empty" + message: "value is empty, which is not a valid IPv6 address" + expression: "!rules.ipv6 || this != ''" + } + ]; + + // `uri` specifies that the field value must be a valid, + // absolute URI as defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3). If the field value isn't a valid, + // absolute URI, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid URI + // string value = 1 [(buf.validate.field).string.uri = true]; + // } + // ``` + bool uri = 17 [ + (predefined).cel = { + id: "string.uri" + message: "value must be a valid URI" + expression: "!rules.uri || this == '' || this.isUri()" + }, + (predefined).cel = { + id: "string.uri_empty" + message: "value is empty, which is not a valid URI" + expression: "!rules.uri || this != ''" + } + ]; + + // `uri_ref` specifies that the field value must be a valid URI + // as defined by [RFC 3986](https://tools.ietf.org/html/rfc3986#section-3) and may be either relative or absolute. If the + // field value isn't a valid URI, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid URI + // string value = 1 [(buf.validate.field).string.uri_ref = true]; + // } + // ``` + bool uri_ref = 18 [(predefined).cel = { + id: "string.uri_ref" + message: "value must be a valid URI" + expression: "!rules.uri_ref || this.isUriRef()" + }]; + + // `address` specifies that the field value must be either a valid hostname + // as defined by [RFC 1034](https://tools.ietf.org/html/rfc1034#section-3.5) + // (which doesn't support internationalized domain names or IDNs) or a valid + // IP (v4 or v6). If the field value isn't a valid hostname or IP, an error + // message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid hostname, or ip address + // string value = 1 [(buf.validate.field).string.address = true]; + // } + // ``` + bool address = 21 [ + (predefined).cel = { + id: "string.address" + message: "value must be a valid hostname, or ip address" + expression: "!rules.address || this == '' || this.isHostname() || this.isIp()" + }, + (predefined).cel = { + id: "string.address_empty" + message: "value is empty, which is not a valid hostname, or ip address" + expression: "!rules.address || this != ''" + } + ]; + + // `uuid` specifies that the field value must be a valid UUID as defined by + // [RFC 4122](https://tools.ietf.org/html/rfc4122#section-4.1.2). If the + // field value isn't a valid UUID, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid UUID + // string value = 1 [(buf.validate.field).string.uuid = true]; + // } + // ``` + bool uuid = 22 [ + (predefined).cel = { + id: "string.uuid" + message: "value must be a valid UUID" + expression: "!rules.uuid || this == '' || this.matches('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')" + }, + (predefined).cel = { + id: "string.uuid_empty" + message: "value is empty, which is not a valid UUID" + expression: "!rules.uuid || this != ''" + } + ]; + + // `tuuid` (trimmed UUID) specifies that the field value must be a valid UUID as + // defined by [RFC 4122](https://tools.ietf.org/html/rfc4122#section-4.1.2) with all dashes + // omitted. If the field value isn't a valid UUID without dashes, an error message + // will be generated. + // + // ```proto + // message MyString { + // // value must be a valid trimmed UUID + // string value = 1 [(buf.validate.field).string.tuuid = true]; + // } + // ``` + bool tuuid = 33 [ + (predefined).cel = { + id: "string.tuuid" + message: "value must be a valid trimmed UUID" + expression: "!rules.tuuid || this == '' || this.matches('^[0-9a-fA-F]{32}$')" + }, + (predefined).cel = { + id: "string.tuuid_empty" + message: "value is empty, which is not a valid trimmed UUID" + expression: "!rules.tuuid || this != ''" + } + ]; + + // `ip_with_prefixlen` specifies that the field value must be a valid IP (v4 or v6) + // address with prefix length. If the field value isn't a valid IP with prefix + // length, an error message will be generated. + // + // + // ```proto + // message MyString { + // // value must be a valid IP with prefix length + // string value = 1 [(buf.validate.field).string.ip_with_prefixlen = true]; + // } + // ``` + bool ip_with_prefixlen = 26 [ + (predefined).cel = { + id: "string.ip_with_prefixlen" + message: "value must be a valid IP prefix" + expression: "!rules.ip_with_prefixlen || this == '' || this.isIpPrefix()" + }, + (predefined).cel = { + id: "string.ip_with_prefixlen_empty" + message: "value is empty, which is not a valid IP prefix" + expression: "!rules.ip_with_prefixlen || this != ''" + } + ]; + + // `ipv4_with_prefixlen` specifies that the field value must be a valid + // IPv4 address with prefix. + // If the field value isn't a valid IPv4 address with prefix length, + // an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid IPv4 address with prefix length + // string value = 1 [(buf.validate.field).string.ipv4_with_prefixlen = true]; + // } + // ``` + bool ipv4_with_prefixlen = 27 [ + (predefined).cel = { + id: "string.ipv4_with_prefixlen" + message: "value must be a valid IPv4 address with prefix length" + expression: "!rules.ipv4_with_prefixlen || this == '' || this.isIpPrefix(4)" + }, + (predefined).cel = { + id: "string.ipv4_with_prefixlen_empty" + message: "value is empty, which is not a valid IPv4 address with prefix length" + expression: "!rules.ipv4_with_prefixlen || this != ''" + } + ]; + + // `ipv6_with_prefixlen` specifies that the field value must be a valid + // IPv6 address with prefix length. + // If the field value is not a valid IPv6 address with prefix length, + // an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid IPv6 address prefix length + // string value = 1 [(buf.validate.field).string.ipv6_with_prefixlen = true]; + // } + // ``` + bool ipv6_with_prefixlen = 28 [ + (predefined).cel = { + id: "string.ipv6_with_prefixlen" + message: "value must be a valid IPv6 address with prefix length" + expression: "!rules.ipv6_with_prefixlen || this == '' || this.isIpPrefix(6)" + }, + (predefined).cel = { + id: "string.ipv6_with_prefixlen_empty" + message: "value is empty, which is not a valid IPv6 address with prefix length" + expression: "!rules.ipv6_with_prefixlen || this != ''" + } + ]; + + // `ip_prefix` specifies that the field value must be a valid IP (v4 or v6) prefix. + // If the field value isn't a valid IP prefix, an error message will be + // generated. The prefix must have all zeros for the masked bits of the prefix (e.g., + // `127.0.0.0/16`, not `127.0.0.1/16`). + // + // ```proto + // message MyString { + // // value must be a valid IP prefix + // string value = 1 [(buf.validate.field).string.ip_prefix = true]; + // } + // ``` + bool ip_prefix = 29 [ + (predefined).cel = { + id: "string.ip_prefix" + message: "value must be a valid IP prefix" + expression: "!rules.ip_prefix || this == '' || this.isIpPrefix(true)" + }, + (predefined).cel = { + id: "string.ip_prefix_empty" + message: "value is empty, which is not a valid IP prefix" + expression: "!rules.ip_prefix || this != ''" + } + ]; + + // `ipv4_prefix` specifies that the field value must be a valid IPv4 + // prefix. If the field value isn't a valid IPv4 prefix, an error message + // will be generated. The prefix must have all zeros for the masked bits of + // the prefix (e.g., `127.0.0.0/16`, not `127.0.0.1/16`). + // + // ```proto + // message MyString { + // // value must be a valid IPv4 prefix + // string value = 1 [(buf.validate.field).string.ipv4_prefix = true]; + // } + // ``` + bool ipv4_prefix = 30 [ + (predefined).cel = { + id: "string.ipv4_prefix" + message: "value must be a valid IPv4 prefix" + expression: "!rules.ipv4_prefix || this == '' || this.isIpPrefix(4, true)" + }, + (predefined).cel = { + id: "string.ipv4_prefix_empty" + message: "value is empty, which is not a valid IPv4 prefix" + expression: "!rules.ipv4_prefix || this != ''" + } + ]; + + // `ipv6_prefix` specifies that the field value must be a valid IPv6 prefix. + // If the field value is not a valid IPv6 prefix, an error message will be + // generated. The prefix must have all zeros for the masked bits of the prefix + // (e.g., `2001:db8::/48`, not `2001:db8::1/48`). + // + // ```proto + // message MyString { + // // value must be a valid IPv6 prefix + // string value = 1 [(buf.validate.field).string.ipv6_prefix = true]; + // } + // ``` + bool ipv6_prefix = 31 [ + (predefined).cel = { + id: "string.ipv6_prefix" + message: "value must be a valid IPv6 prefix" + expression: "!rules.ipv6_prefix || this == '' || this.isIpPrefix(6, true)" + }, + (predefined).cel = { + id: "string.ipv6_prefix_empty" + message: "value is empty, which is not a valid IPv6 prefix" + expression: "!rules.ipv6_prefix || this != ''" + } + ]; + + // `host_and_port` specifies the field value must be a valid host and port + // pair. The host must be a valid hostname or IP address while the port + // must be in the range of 0-65535, inclusive. IPv6 addresses must be delimited + // with square brackets (e.g., `[::1]:1234`). + bool host_and_port = 32 [ + (predefined).cel = { + id: "string.host_and_port" + message: "value must be a valid host (hostname or IP address) and port pair" + expression: "!rules.host_and_port || this == '' || this.isHostAndPort(true)" + }, + (predefined).cel = { + id: "string.host_and_port_empty" + message: "value is empty, which is not a valid host and port pair" + expression: "!rules.host_and_port || this != ''" + } + ]; + + // `well_known_regex` specifies a common well-known pattern + // defined as a regex. If the field value doesn't match the well-known + // regex, an error message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid HTTP header value + // string value = 1 [(buf.validate.field).string.well_known_regex = KNOWN_REGEX_HTTP_HEADER_VALUE]; + // } + // ``` + // + // #### KnownRegex + // + // `well_known_regex` contains some well-known patterns. + // + // | Name | Number | Description | + // |-------------------------------|--------|-------------------------------------------| + // | KNOWN_REGEX_UNSPECIFIED | 0 | | + // | KNOWN_REGEX_HTTP_HEADER_NAME | 1 | HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2) | + // | KNOWN_REGEX_HTTP_HEADER_VALUE | 2 | HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4) | + KnownRegex well_known_regex = 24 [ + (predefined).cel = { + id: "string.well_known_regex.header_name" + message: "value must be a valid HTTP header name" + expression: + "rules.well_known_regex != 1 || this == '' || this.matches(!has(rules.strict) || rules.strict ?" + "'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :" + "'^[^\\u0000\\u000A\\u000D]+$')" + }, + (predefined).cel = { + id: "string.well_known_regex.header_name_empty" + message: "value is empty, which is not a valid HTTP header name" + expression: "rules.well_known_regex != 1 || this != ''" + }, + (predefined).cel = { + id: "string.well_known_regex.header_value" + message: "value must be a valid HTTP header value" + expression: + "rules.well_known_regex != 2 || this.matches(!has(rules.strict) || rules.strict ?" + "'^[^\\u0000-\\u0008\\u000A-\\u001F\\u007F]*$' :" + "'^[^\\u0000\\u000A\\u000D]*$')" + } + ]; + } + + // This applies to regexes `HTTP_HEADER_NAME` and `HTTP_HEADER_VALUE` to + // enable strict header validation. By default, this is true, and HTTP header + // validations are [RFC-compliant](https://tools.ietf.org/html/rfc7230#section-3). Setting to false will enable looser + // validations that only disallow `\r\n\0` characters, which can be used to + // bypass header matching rules. + // + // ```proto + // message MyString { + // // The field `value` must have be a valid HTTP headers, but not enforced with strict rules. + // string value = 1 [(buf.validate.field).string.strict = false]; + // } + // ``` + optional bool strict = 25; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyString { + // string value = 1 [ + // (buf.validate.field).string.example = 1, + // (buf.validate.field).string.example = 2 + // ]; + // } + // ``` + repeated string example = 34 [(predefined).cel = { + id: "string.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// WellKnownRegex contain some well-known patterns. +enum KnownRegex { + KNOWN_REGEX_UNSPECIFIED = 0; + + // HTTP header name as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2). + KNOWN_REGEX_HTTP_HEADER_NAME = 1; + + // HTTP header value as defined by [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4). + KNOWN_REGEX_HTTP_HEADER_VALUE = 2; +} + +// BytesRules describe the constraints applied to `bytes` values. These rules +// may also be applied to the `google.protobuf.BytesValue` Well-Known-Type. +message BytesRules { + // `const` requires the field value to exactly match the specified bytes + // value. If the field value doesn't match, an error message is generated. + // + // ```proto + // message MyBytes { + // // value must be "\x01\x02\x03\x04" + // bytes value = 1 [(buf.validate.field).bytes.const = "\x01\x02\x03\x04"]; + // } + // ``` + optional bytes const = 1 [(predefined).cel = { + id: "bytes.const" + expression: "this != rules.const ? 'value must be %x'.format([rules.const]) : ''" + }]; + + // `len` requires the field value to have the specified length in bytes. + // If the field value doesn't match, an error message is generated. + // + // ```proto + // message MyBytes { + // // value length must be 4 bytes. + // optional bytes value = 1 [(buf.validate.field).bytes.len = 4]; + // } + // ``` + optional uint64 len = 13 [(predefined).cel = { + id: "bytes.len" + expression: "uint(this.size()) != rules.len ? 'value length must be %s bytes'.format([rules.len]) : ''" + }]; + + // `min_len` requires the field value to have at least the specified minimum + // length in bytes. + // If the field value doesn't meet the requirement, an error message is generated. + // + // ```proto + // message MyBytes { + // // value length must be at least 2 bytes. + // optional bytes value = 1 [(buf.validate.field).bytes.min_len = 2]; + // } + // ``` + optional uint64 min_len = 2 [(predefined).cel = { + id: "bytes.min_len" + expression: "uint(this.size()) < rules.min_len ? 'value length must be at least %s bytes'.format([rules.min_len]) : ''" + }]; + + // `max_len` requires the field value to have at most the specified maximum + // length in bytes. + // If the field value exceeds the requirement, an error message is generated. + // + // ```proto + // message MyBytes { + // // value must be at most 6 bytes. + // optional bytes value = 1 [(buf.validate.field).bytes.max_len = 6]; + // } + // ``` + optional uint64 max_len = 3 [(predefined).cel = { + id: "bytes.max_len" + expression: "uint(this.size()) > rules.max_len ? 'value must be at most %s bytes'.format([rules.max_len]) : ''" + }]; + + // `pattern` requires the field value to match the specified regular + // expression ([RE2 syntax](https://github.com/google/re2/wiki/Syntax)). + // The value of the field must be valid UTF-8 or validation will fail with a + // runtime error. + // If the field value doesn't match the pattern, an error message is generated. + // + // ```proto + // message MyBytes { + // // value must match regex pattern "^[a-zA-Z0-9]+$". + // optional bytes value = 1 [(buf.validate.field).bytes.pattern = "^[a-zA-Z0-9]+$"]; + // } + // ``` + optional string pattern = 4 [(predefined).cel = { + id: "bytes.pattern" + expression: "!string(this).matches(rules.pattern) ? 'value must match regex pattern `%s`'.format([rules.pattern]) : ''" + }]; + + // `prefix` requires the field value to have the specified bytes at the + // beginning of the string. + // If the field value doesn't meet the requirement, an error message is generated. + // + // ```proto + // message MyBytes { + // // value does not have prefix \x01\x02 + // optional bytes value = 1 [(buf.validate.field).bytes.prefix = "\x01\x02"]; + // } + // ``` + optional bytes prefix = 5 [(predefined).cel = { + id: "bytes.prefix" + expression: "!this.startsWith(rules.prefix) ? 'value does not have prefix %x'.format([rules.prefix]) : ''" + }]; + + // `suffix` requires the field value to have the specified bytes at the end + // of the string. + // If the field value doesn't meet the requirement, an error message is generated. + // + // ```proto + // message MyBytes { + // // value does not have suffix \x03\x04 + // optional bytes value = 1 [(buf.validate.field).bytes.suffix = "\x03\x04"]; + // } + // ``` + optional bytes suffix = 6 [(predefined).cel = { + id: "bytes.suffix" + expression: "!this.endsWith(rules.suffix) ? 'value does not have suffix %x'.format([rules.suffix]) : ''" + }]; + + // `contains` requires the field value to have the specified bytes anywhere in + // the string. + // If the field value doesn't meet the requirement, an error message is generated. + // + // ```protobuf + // message MyBytes { + // // value does not contain \x02\x03 + // optional bytes value = 1 [(buf.validate.field).bytes.contains = "\x02\x03"]; + // } + // ``` + optional bytes contains = 7 [(predefined).cel = { + id: "bytes.contains" + expression: "!this.contains(rules.contains) ? 'value does not contain %x'.format([rules.contains]) : ''" + }]; + + // `in` requires the field value to be equal to one of the specified + // values. If the field value doesn't match any of the specified values, an + // error message is generated. + // + // ```protobuf + // message MyBytes { + // // value must in ["\x01\x02", "\x02\x03", "\x03\x04"] + // optional bytes value = 1 [(buf.validate.field).bytes.in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; + // } + // ``` + repeated bytes in = 8 [(predefined).cel = { + id: "bytes.in" + expression: "dyn(rules)['in'].size() > 0 && !(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to be not equal to any of the specified + // values. + // If the field value matches any of the specified values, an error message is + // generated. + // + // ```proto + // message MyBytes { + // // value must not in ["\x01\x02", "\x02\x03", "\x03\x04"] + // optional bytes value = 1 [(buf.validate.field).bytes.not_in = {"\x01\x02", "\x02\x03", "\x03\x04"}]; + // } + // ``` + repeated bytes not_in = 9 [(predefined).cel = { + id: "bytes.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // WellKnown rules provide advanced constraints against common byte + // patterns + oneof well_known { + // `ip` ensures that the field `value` is a valid IP address (v4 or v6) in byte format. + // If the field value doesn't meet this constraint, an error message is generated. + // + // ```proto + // message MyBytes { + // // value must be a valid IP address + // optional bytes value = 1 [(buf.validate.field).bytes.ip = true]; + // } + // ``` + bool ip = 10 [ + (predefined).cel = { + id: "bytes.ip" + message: "value must be a valid IP address" + expression: "!rules.ip || this.size() == 0 || this.size() == 4 || this.size() == 16" + }, + (predefined).cel = { + id: "bytes.ip_empty" + message: "value is empty, which is not a valid IP address" + expression: "!rules.ip || this.size() != 0" + } + ]; + + // `ipv4` ensures that the field `value` is a valid IPv4 address in byte format. + // If the field value doesn't meet this constraint, an error message is generated. + // + // ```proto + // message MyBytes { + // // value must be a valid IPv4 address + // optional bytes value = 1 [(buf.validate.field).bytes.ipv4 = true]; + // } + // ``` + bool ipv4 = 11 [ + (predefined).cel = { + id: "bytes.ipv4" + message: "value must be a valid IPv4 address" + expression: "!rules.ipv4 || this.size() == 0 || this.size() == 4" + }, + (predefined).cel = { + id: "bytes.ipv4_empty" + message: "value is empty, which is not a valid IPv4 address" + expression: "!rules.ipv4 || this.size() != 0" + } + ]; + + // `ipv6` ensures that the field `value` is a valid IPv6 address in byte format. + // If the field value doesn't meet this constraint, an error message is generated. + // ```proto + // message MyBytes { + // // value must be a valid IPv6 address + // optional bytes value = 1 [(buf.validate.field).bytes.ipv6 = true]; + // } + // ``` + bool ipv6 = 12 [ + (predefined).cel = { + id: "bytes.ipv6" + message: "value must be a valid IPv6 address" + expression: "!rules.ipv6 || this.size() == 0 || this.size() == 16" + }, + (predefined).cel = { + id: "bytes.ipv6_empty" + message: "value is empty, which is not a valid IPv6 address" + expression: "!rules.ipv6 || this.size() != 0" + } + ]; + } + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyBytes { + // bytes value = 1 [ + // (buf.validate.field).bytes.example = "\x01\x02", + // (buf.validate.field).bytes.example = "\x02\x03" + // ]; + // } + // ``` + repeated bytes example = 14 [(predefined).cel = { + id: "bytes.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// EnumRules describe the constraints applied to `enum` values. +message EnumRules { + // `const` requires the field value to exactly match the specified enum value. + // If the field value doesn't match, an error message is generated. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // // The field `value` must be exactly MY_ENUM_VALUE1. + // MyEnum value = 1 [(buf.validate.field).enum.const = 1]; + // } + // ``` + optional int32 const = 1 [(predefined).cel = { + id: "enum.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + + // `defined_only` requires the field value to be one of the defined values for + // this enum, failing on any undefined value. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // // The field `value` must be a defined value of MyEnum. + // MyEnum value = 1 [(buf.validate.field).enum.defined_only = true]; + // } + // ``` + optional bool defined_only = 2; + + // `in` requires the field value to be equal to one of the + //specified enum values. If the field value doesn't match any of the + //specified values, an error message is generated. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // // The field `value` must be equal to one of the specified values. + // MyEnum value = 1 [(buf.validate.field).enum = { in: [1, 2]}]; + // } + // ``` + repeated int32 in = 3 [(predefined).cel = { + id: "enum.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` requires the field value to be not equal to any of the + //specified enum values. If the field value matches one of the specified + // values, an error message is generated. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // // The field `value` must not be equal to any of the specified values. + // MyEnum value = 1 [(buf.validate.field).enum = { not_in: [1, 2]}]; + // } + // ``` + repeated int32 not_in = 4 [(predefined).cel = { + id: "enum.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // enum MyEnum { + // MY_ENUM_UNSPECIFIED = 0; + // MY_ENUM_VALUE1 = 1; + // MY_ENUM_VALUE2 = 2; + // } + // + // message MyMessage { + // (buf.validate.field).enum.example = 1, + // (buf.validate.field).enum.example = 2 + // } + // ``` + repeated int32 example = 5 [(predefined).cel = { + id: "enum.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// RepeatedRules describe the constraints applied to `repeated` values. +message RepeatedRules { + // `min_items` requires that this field must contain at least the specified + // minimum number of items. + // + // Note that `min_items = 1` is equivalent to setting a field as `required`. + // + // ```proto + // message MyRepeated { + // // value must contain at least 2 items + // repeated string value = 1 [(buf.validate.field).repeated.min_items = 2]; + // } + // ``` + optional uint64 min_items = 1 [(predefined).cel = { + id: "repeated.min_items" + expression: "uint(this.size()) < rules.min_items ? 'value must contain at least %d item(s)'.format([rules.min_items]) : ''" + }]; + + // `max_items` denotes that this field must not exceed a + // certain number of items as the upper limit. If the field contains more + // items than specified, an error message will be generated, requiring the + // field to maintain no more than the specified number of items. + // + // ```proto + // message MyRepeated { + // // value must contain no more than 3 item(s) + // repeated string value = 1 [(buf.validate.field).repeated.max_items = 3]; + // } + // ``` + optional uint64 max_items = 2 [(predefined).cel = { + id: "repeated.max_items" + expression: "uint(this.size()) > rules.max_items ? 'value must contain no more than %s item(s)'.format([rules.max_items]) : ''" + }]; + + // `unique` indicates that all elements in this field must + // be unique. This constraint is strictly applicable to scalar and enum + // types, with message types not being supported. + // + // ```proto + // message MyRepeated { + // // repeated value must contain unique items + // repeated string value = 1 [(buf.validate.field).repeated.unique = true]; + // } + // ``` + optional bool unique = 3 [(predefined).cel = { + id: "repeated.unique" + message: "repeated value must contain unique items" + expression: "!rules.unique || this.unique()" + }]; + + // `items` details the constraints to be applied to each item + // in the field. Even for repeated message fields, validation is executed + // against each item unless skip is explicitly specified. + // + // ```proto + // message MyRepeated { + // // The items in the field `value` must follow the specified constraints. + // repeated string value = 1 [(buf.validate.field).repeated.items = { + // string: { + // min_len: 3 + // max_len: 10 + // } + // }]; + // } + // ``` + optional FieldConstraints items = 4; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// MapRules describe the constraints applied to `map` values. +message MapRules { + //Specifies the minimum number of key-value pairs allowed. If the field has + // fewer key-value pairs than specified, an error message is generated. + // + // ```proto + // message MyMap { + // // The field `value` must have at least 2 key-value pairs. + // map value = 1 [(buf.validate.field).map.min_pairs = 2]; + // } + // ``` + optional uint64 min_pairs = 1 [(predefined).cel = { + id: "map.min_pairs" + expression: "uint(this.size()) < rules.min_pairs ? 'map must be at least %d entries'.format([rules.min_pairs]) : ''" + }]; + + //Specifies the maximum number of key-value pairs allowed. If the field has + // more key-value pairs than specified, an error message is generated. + // + // ```proto + // message MyMap { + // // The field `value` must have at most 3 key-value pairs. + // map value = 1 [(buf.validate.field).map.max_pairs = 3]; + // } + // ``` + optional uint64 max_pairs = 2 [(predefined).cel = { + id: "map.max_pairs" + expression: "uint(this.size()) > rules.max_pairs ? 'map must be at most %d entries'.format([rules.max_pairs]) : ''" + }]; + + //Specifies the constraints to be applied to each key in the field. + // + // ```proto + // message MyMap { + // // The keys in the field `value` must follow the specified constraints. + // map value = 1 [(buf.validate.field).map.keys = { + // string: { + // min_len: 3 + // max_len: 10 + // } + // }]; + // } + // ``` + optional FieldConstraints keys = 4; + + //Specifies the constraints to be applied to the value of each key in the + // field. Message values will still have their validations evaluated unless + //skip is specified here. + // + // ```proto + // message MyMap { + // // The values in the field `value` must follow the specified constraints. + // map value = 1 [(buf.validate.field).map.values = { + // string: { + // min_len: 5 + // max_len: 20 + // } + // }]; + // } + // ``` + optional FieldConstraints values = 5; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// AnyRules describe constraints applied exclusively to the `google.protobuf.Any` well-known type. +message AnyRules { + // `in` requires the field's `type_url` to be equal to one of the + //specified values. If it doesn't match any of the specified values, an error + // message is generated. + // + // ```proto + // message MyAny { + // // The `value` field must have a `type_url` equal to one of the specified values. + // google.protobuf.Any value = 1 [(buf.validate.field).any.in = ["type.googleapis.com/MyType1", "type.googleapis.com/MyType2"]]; + // } + // ``` + repeated string in = 2; + + // requires the field's type_url to be not equal to any of the specified values. If it matches any of the specified values, an error message is generated. + // + // ```proto + // message MyAny { + // // The field `value` must not have a `type_url` equal to any of the specified values. + // google.protobuf.Any value = 1 [(buf.validate.field).any.not_in = ["type.googleapis.com/ForbiddenType1", "type.googleapis.com/ForbiddenType2"]]; + // } + // ``` + repeated string not_in = 3; +} + +// DurationRules describe the constraints applied exclusively to the `google.protobuf.Duration` well-known type. +message DurationRules { + // `const` dictates that the field must match the specified value of the `google.protobuf.Duration` type exactly. + // If the field's value deviates from the specified value, an error message + // will be generated. + // + // ```proto + // message MyDuration { + // // value must equal 5s + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.const = "5s"]; + // } + // ``` + optional google.protobuf.Duration const = 2 [(predefined).cel = { + id: "duration.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // `lt` stipulates that the field must be less than the specified value of the `google.protobuf.Duration` type, + // exclusive. If the field's value is greater than or equal to the specified + // value, an error message will be generated. + // + // ```proto + // message MyDuration { + // // value must be less than 5s + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = "5s"]; + // } + // ``` + google.protobuf.Duration lt = 3 [(predefined).cel = { + id: "duration.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // `lte` indicates that the field must be less than or equal to the specified + // value of the `google.protobuf.Duration` type, inclusive. If the field's value is greater than the specified value, + // an error message will be generated. + // + // ```proto + // message MyDuration { + // // value must be less than or equal to 10s + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lte = "10s"]; + // } + // ``` + google.protobuf.Duration lte = 4 [(predefined).cel = { + id: "duration.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + } + oneof greater_than { + // `gt` requires the duration field value to be greater than the specified + // value (exclusive). If the value of `gt` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyDuration { + // // duration must be greater than 5s [duration.gt] + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.gt = { seconds: 5 }]; + // + // // duration must be greater than 5s and less than 10s [duration.gt_lt] + // google.protobuf.Duration another_value = 2 [(buf.validate.field).duration = { gt: { seconds: 5 }, lt: { seconds: 10 } }]; + // + // // duration must be greater than 10s or less than 5s [duration.gt_lt_exclusive] + // google.protobuf.Duration other_value = 3 [(buf.validate.field).duration = { gt: { seconds: 10 }, lt: { seconds: 5 } }]; + // } + // ``` + google.protobuf.Duration gt = 5 [ + (predefined).cel = { + id: "duration.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "duration.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "duration.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "duration.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "duration.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the duration field value to be greater than or equal to the + // specified value (exclusive). If the value of `gte` is larger than a + // specified `lt` or `lte`, the range is reversed, and the field value must + // be outside the specified range. If the field value doesn't meet the + // required conditions, an error message is generated. + // + // ```proto + // message MyDuration { + // // duration must be greater than or equal to 5s [duration.gte] + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.gte = { seconds: 5 }]; + // + // // duration must be greater than or equal to 5s and less than 10s [duration.gte_lt] + // google.protobuf.Duration another_value = 2 [(buf.validate.field).duration = { gte: { seconds: 5 }, lt: { seconds: 10 } }]; + // + // // duration must be greater than or equal to 10s or less than 5s [duration.gte_lt_exclusive] + // google.protobuf.Duration other_value = 3 [(buf.validate.field).duration = { gte: { seconds: 10 }, lt: { seconds: 5 } }]; + // } + // ``` + google.protobuf.Duration gte = 6 [ + (predefined).cel = { + id: "duration.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "duration.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "duration.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "duration.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "duration.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + } + + // `in` asserts that the field must be equal to one of the specified values of the `google.protobuf.Duration` type. + // If the field's value doesn't correspond to any of the specified values, + // an error message will be generated. + // + // ```proto + // message MyDuration { + // // value must be in list [1s, 2s, 3s] + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.in = ["1s", "2s", "3s"]]; + // } + // ``` + repeated google.protobuf.Duration in = 7 [(predefined).cel = { + id: "duration.in" + expression: "!(this in dyn(rules)['in']) ? 'value must be in list %s'.format([dyn(rules)['in']]) : ''" + }]; + + // `not_in` denotes that the field must not be equal to + // any of the specified values of the `google.protobuf.Duration` type. + // If the field's value matches any of these values, an error message will be + // generated. + // + // ```proto + // message MyDuration { + // // value must not be in list [1s, 2s, 3s] + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.not_in = ["1s", "2s", "3s"]]; + // } + // ``` + repeated google.protobuf.Duration not_in = 8 [(predefined).cel = { + id: "duration.not_in" + expression: "this in rules.not_in ? 'value must not be in list %s'.format([rules.not_in]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyDuration { + // google.protobuf.Duration value = 1 [ + // (buf.validate.field).duration.example = { seconds: 1 }, + // (buf.validate.field).duration.example = { seconds: 2 }, + // ]; + // } + // ``` + repeated google.protobuf.Duration example = 9 [(predefined).cel = { + id: "duration.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// TimestampRules describe the constraints applied exclusively to the `google.protobuf.Timestamp` well-known type. +message TimestampRules { + // `const` dictates that this field, of the `google.protobuf.Timestamp` type, must exactly match the specified value. If the field value doesn't correspond to the specified timestamp, an error message will be generated. + // + // ```proto + // message MyTimestamp { + // // value must equal 2023-05-03T10:00:00Z + // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.const = {seconds: 1727998800}]; + // } + // ``` + optional google.protobuf.Timestamp const = 2 [(predefined).cel = { + id: "timestamp.const" + expression: "this != rules.const ? 'value must equal %s'.format([rules.const]) : ''" + }]; + oneof less_than { + // requires the duration field value to be less than the specified value (field < value). If the field value doesn't meet the required conditions, an error message is generated. + // + // ```proto + // message MyDuration { + // // duration must be less than 'P3D' [duration.lt] + // google.protobuf.Duration value = 1 [(buf.validate.field).duration.lt = { seconds: 259200 }]; + // } + // ``` + google.protobuf.Timestamp lt = 3 [(predefined).cel = { + id: "timestamp.lt" + expression: + "!has(rules.gte) && !has(rules.gt) && this >= rules.lt" + "? 'value must be less than %s'.format([rules.lt]) : ''" + }]; + + // requires the timestamp field value to be less than or equal to the specified value (field <= value). If the field value doesn't meet the required conditions, an error message is generated. + // + // ```proto + // message MyTimestamp { + // // timestamp must be less than or equal to '2023-05-14T00:00:00Z' [timestamp.lte] + // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.lte = { seconds: 1678867200 }]; + // } + // ``` + google.protobuf.Timestamp lte = 4 [(predefined).cel = { + id: "timestamp.lte" + expression: + "!has(rules.gte) && !has(rules.gt) && this > rules.lte" + "? 'value must be less than or equal to %s'.format([rules.lte]) : ''" + }]; + + // `lt_now` specifies that this field, of the `google.protobuf.Timestamp` type, must be less than the current time. `lt_now` can only be used with the `within` rule. + // + // ```proto + // message MyTimestamp { + // // value must be less than now + // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.lt_now = true]; + // } + // ``` + bool lt_now = 7 [(predefined).cel = { + id: "timestamp.lt_now" + expression: "(rules.lt_now && this > now) ? 'value must be less than now' : ''" + }]; + } + oneof greater_than { + // `gt` requires the timestamp field value to be greater than the specified + // value (exclusive). If the value of `gt` is larger than a specified `lt` + // or `lte`, the range is reversed, and the field value must be outside the + // specified range. If the field value doesn't meet the required conditions, + // an error message is generated. + // + // ```proto + // message MyTimestamp { + // // timestamp must be greater than '2023-01-01T00:00:00Z' [timestamp.gt] + // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.gt = { seconds: 1672444800 }]; + // + // // timestamp must be greater than '2023-01-01T00:00:00Z' and less than '2023-01-02T00:00:00Z' [timestamp.gt_lt] + // google.protobuf.Timestamp another_value = 2 [(buf.validate.field).timestamp = { gt: { seconds: 1672444800 }, lt: { seconds: 1672531200 } }]; + // + // // timestamp must be greater than '2023-01-02T00:00:00Z' or less than '2023-01-01T00:00:00Z' [timestamp.gt_lt_exclusive] + // google.protobuf.Timestamp other_value = 3 [(buf.validate.field).timestamp = { gt: { seconds: 1672531200 }, lt: { seconds: 1672444800 } }]; + // } + // ``` + google.protobuf.Timestamp gt = 5 [ + (predefined).cel = { + id: "timestamp.gt" + expression: + "!has(rules.lt) && !has(rules.lte) && this <= rules.gt" + "? 'value must be greater than %s'.format([rules.gt]) : ''" + }, + (predefined).cel = { + id: "timestamp.gt_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gt && (this >= rules.lt || this <= rules.gt)" + "? 'value must be greater than %s and less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "timestamp.gt_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gt && (rules.lt <= this && this <= rules.gt)" + "? 'value must be greater than %s or less than %s'.format([rules.gt, rules.lt]) : ''" + }, + (predefined).cel = { + id: "timestamp.gt_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gt && (this > rules.lte || this <= rules.gt)" + "? 'value must be greater than %s and less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + }, + (predefined).cel = { + id: "timestamp.gt_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gt && (rules.lte < this && this <= rules.gt)" + "? 'value must be greater than %s or less than or equal to %s'.format([rules.gt, rules.lte]) : ''" + } + ]; + + // `gte` requires the timestamp field value to be greater than or equal to the + // specified value (exclusive). If the value of `gte` is larger than a + // specified `lt` or `lte`, the range is reversed, and the field value + // must be outside the specified range. If the field value doesn't meet + // the required conditions, an error message is generated. + // + // ```proto + // message MyTimestamp { + // // timestamp must be greater than or equal to '2023-01-01T00:00:00Z' [timestamp.gte] + // google.protobuf.Timestamp value = 1 [(buf.validate.field).timestamp.gte = { seconds: 1672444800 }]; + // + // // timestamp must be greater than or equal to '2023-01-01T00:00:00Z' and less than '2023-01-02T00:00:00Z' [timestamp.gte_lt] + // google.protobuf.Timestamp another_value = 2 [(buf.validate.field).timestamp = { gte: { seconds: 1672444800 }, lt: { seconds: 1672531200 } }]; + // + // // timestamp must be greater than or equal to '2023-01-02T00:00:00Z' or less than '2023-01-01T00:00:00Z' [timestamp.gte_lt_exclusive] + // google.protobuf.Timestamp other_value = 3 [(buf.validate.field).timestamp = { gte: { seconds: 1672531200 }, lt: { seconds: 1672444800 } }]; + // } + // ``` + google.protobuf.Timestamp gte = 6 [ + (predefined).cel = { + id: "timestamp.gte" + expression: + "!has(rules.lt) && !has(rules.lte) && this < rules.gte" + "? 'value must be greater than or equal to %s'.format([rules.gte]) : ''" + }, + (predefined).cel = { + id: "timestamp.gte_lt" + expression: + "has(rules.lt) && rules.lt >= rules.gte && (this >= rules.lt || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "timestamp.gte_lt_exclusive" + expression: + "has(rules.lt) && rules.lt < rules.gte && (rules.lt <= this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than %s'.format([rules.gte, rules.lt]) : ''" + }, + (predefined).cel = { + id: "timestamp.gte_lte" + expression: + "has(rules.lte) && rules.lte >= rules.gte && (this > rules.lte || this < rules.gte)" + "? 'value must be greater than or equal to %s and less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + }, + (predefined).cel = { + id: "timestamp.gte_lte_exclusive" + expression: + "has(rules.lte) && rules.lte < rules.gte && (rules.lte < this && this < rules.gte)" + "? 'value must be greater than or equal to %s or less than or equal to %s'.format([rules.gte, rules.lte]) : ''" + } + ]; + + // `gt_now` specifies that this field, of the `google.protobuf.Timestamp` type, must be greater than the current time. `gt_now` can only be used with the `within` rule. + // + // ```proto + // message MyTimestamp { + // // value must be greater than now + // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.gt_now = true]; + // } + // ``` + bool gt_now = 8 [(predefined).cel = { + id: "timestamp.gt_now" + expression: "(rules.gt_now && this < now) ? 'value must be greater than now' : ''" + }]; + } + + // `within` specifies that this field, of the `google.protobuf.Timestamp` type, must be within the specified duration of the current time. If the field value isn't within the duration, an error message is generated. + // + // ```proto + // message MyTimestamp { + // // value must be within 1 hour of now + // google.protobuf.Timestamp created_at = 1 [(buf.validate.field).timestamp.within = {seconds: 3600}]; + // } + // ``` + optional google.protobuf.Duration within = 9 [(predefined).cel = { + id: "timestamp.within" + expression: "this < now-rules.within || this > now+rules.within ? 'value must be within %s of now'.format([rules.within]) : ''" + }]; + + // `example` specifies values that the field may have. These values SHOULD + // conform to other constraints. `example` values will not impact validation + // but may be used as helpful guidance on how to populate the given field. + // + // ```proto + // message MyTimestamp { + // google.protobuf.Timestamp value = 1 [ + // (buf.validate.field).timestamp.example = { seconds: 1672444800 }, + // (buf.validate.field).timestamp.example = { seconds: 1672531200 }, + // ]; + // } + // ``` + + repeated google.protobuf.Timestamp example = 10 [(predefined).cel = { + id: "timestamp.example" + expression: "true" + }]; + + // Extension fields in this range that have the (buf.validate.predefined) + // option set will be treated as predefined field constraints that can then be + // set on the field options of other fields to apply field constraints. + // Extension numbers 1000 to 99999 are reserved for extension numbers that are + // defined in the [Protobuf Global Extension Registry][1]. Extension numbers + // above this range are reserved for extension numbers that are not explicitly + // assigned. For rules defined in publicly-consumed schemas, use of extensions + // above 99999 is discouraged due to the risk of conflicts. + // + // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md + extensions 1000 to max; +} + +// `Violations` is a collection of `Violation` messages. This message type is returned by +// protovalidate when a proto message fails to meet the requirements set by the `Constraint` validation rules. +// Each individual violation is represented by a `Violation` message. +message Violations { + // `violations` is a repeated field that contains all the `Violation` messages corresponding to the violations detected. + repeated Violation violations = 1; +} + +// `Violation` represents a single instance where a validation rule, expressed +// as a `Constraint`, was not met. It provides information about the field that +// caused the violation, the specific constraint that wasn't fulfilled, and a +// human-readable error message. +// +// ```json +// { +// "fieldPath": "bar", +// "constraintId": "foo.bar", +// "message": "bar must be greater than 0" +// } +// ``` +message Violation { + // `field_path` is a machine-readable identifier that points to the specific field that failed the validation. + // This could be a nested field, in which case the path will include all the parent fields leading to the actual field that caused the violation. + optional string field_path = 1; + + // `constraint_id` is the unique identifier of the `Constraint` that was not fulfilled. + // This is the same `id` that was specified in the `Constraint` message, allowing easy tracing of which rule was violated. + optional string constraint_id = 2; + + // `message` is a human-readable error message that describes the nature of the violation. + // This can be the default error message from the violated `Constraint`, or it can be a custom message that gives more context about the violation. + optional string message = 3; + + // `for_key` indicates whether the violation was caused by a map key, rather than a value. + optional bool for_key = 4; +} From 97f260f974dd64035808fe848cd054b8ac8050b7 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 1 Oct 2024 11:26:20 -0400 Subject: [PATCH 26/30] Upgrade fix from protovalidate-go and update some tests --- go.mod | 2 +- go.sum | 2 ++ private/bufpkg/bufcheck/lint_test.go | 4 +++- .../lint/protovalidate/proto/map.proto | 10 ++++++---- .../import/import.proto | 2 +- .../protovalidate_predefined/proto/test.proto | 19 +++++++++---------- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/go.mod b/go.mod index 01d3ae1835..dcfbeb8083 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( connectrpc.com/otelconnect v0.7.1 github.com/bufbuild/protocompile v0.14.1 github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a - github.com/bufbuild/protovalidate-go v0.7.0 + github.com/bufbuild/protovalidate-go v0.7.1 github.com/docker/docker v27.3.1+incompatible github.com/go-chi/chi/v5 v5.1.0 github.com/gofrs/flock v0.12.1 diff --git a/go.sum b/go.sum index 633cb1b21d..93f114077e 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a h1:l3RhVoG0Rt github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ= github.com/bufbuild/protovalidate-go v0.7.0 h1:MYU9GSZM7TSsWNywvyXoEc8y3kc1MNqD3k5mddIBEL4= github.com/bufbuild/protovalidate-go v0.7.0/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= +github.com/bufbuild/protovalidate-go v0.7.1 h1:ac50NTO6+1+mKg5sP/GBPLlMkQFeI+OeaYGFdS1vu98= +github.com/bufbuild/protovalidate-go v0.7.1/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/private/bufpkg/bufcheck/lint_test.go b/private/bufpkg/bufcheck/lint_test.go index a3d2853018..1f0e0f4c23 100644 --- a/private/bufpkg/bufcheck/lint_test.go +++ b/private/bufpkg/bufcheck/lint_test.go @@ -654,7 +654,8 @@ func TestRunProtovalidate(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "map.proto", 50, 5, 50, 57, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "map.proto", 53, 5, 53, 50, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "map.proto", 56, 41, 56, 80, "PROTOVALIDATE"), - bufanalysistesting.NewFileAnnotation(t, "map.proto", 69, 5, 69, 53, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "map.proto", 70, 5, 70, 53, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "map.proto", 71, 5, 71, 77, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "message.proto", 20, 3, 20, 49, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "message.proto", 27, 5, 27, 51, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "number.proto", 20, 5, 20, 42, "PROTOVALIDATE"), @@ -756,6 +757,7 @@ func TestRunProtovalidatePredefinedRules(t *testing.T) { bufanalysistesting.NewFileAnnotation(t, "test.proto", 43, 5, 43, 57, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "test.proto", 43, 5, 43, 57, "PROTOVALIDATE"), bufanalysistesting.NewFileAnnotation(t, "test.proto", 44, 5, 44, 64, "PROTOVALIDATE"), + bufanalysistesting.NewFileAnnotation(t, "test.proto", 60, 5, 60, 43, "PROTOVALIDATE"), ) } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto index a266598cc1..124e730090 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate/proto/map.proto @@ -54,18 +54,20 @@ message MapTest { (buf.validate.field).map.values.int32.gt = 1 ]; map non_map_rule = 15 [(buf.validate.field).string.min_len = 1]; - map valid_keys_example = 11 [ + map valid_keys_values_example = 11 [ (buf.validate.field).map.keys.int64.gt = 1, (buf.validate.field).map.keys.int64.lt = 10, (buf.validate.field).map.values.string.min_len = 1, (buf.validate.field).map.values.string.max_len = 10, - (buf.validate.field).map.keys.int64.example = 5 + (buf.validate.field).map.keys.int64.example = 5, + (buf.validate.field).map.values.string.example = "good" ]; - map invalid_keys_example = 12 [ + map invalid_keys_values_example = 12 [ (buf.validate.field).map.keys.int64.gt = 1, (buf.validate.field).map.keys.int64.lt = 10, (buf.validate.field).map.values.string.min_len = 1, (buf.validate.field).map.values.string.max_len = 10, - (buf.validate.field).map.keys.int64.example = -1 + (buf.validate.field).map.keys.int64.example = -1, + (buf.validate.field).map.values.string.example = "this_is_a_long_string" ]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto index 1b4955a460..68e94c9b7b 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/import/import.proto @@ -20,7 +20,7 @@ extend buf.validate.StringRules { extend buf.validate.Int32Rules { repeated int32 abs_not_in = 1800 [(buf.validate.predefined).cel = { id: "int32.abs_not_in" - expression: "this in rule || this in rule.map(n, -n)" + expression: "!(this in rule || this in rule.map(n, -n))" message: "value must be in absolute value of list" }]; } diff --git a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto index 07f8e595f2..75f78b5390 100644 --- a/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto +++ b/private/bufpkg/bufcheck/testdata/lint/protovalidate_predefined/proto/test.proto @@ -51,13 +51,12 @@ message TestPredefinedStringRules { ]; } -// TODO: fixing -// message TestInt32Rules { -// optional int32 test = 1 [ -// (buf.validate.field).int32.lt = 5, -// (buf.validate.field).int32.(abs_not_in) = 1, -// (buf.validate.field).int32.(abs_not_in) = -2, -// (buf.validate.field).int32.example = 3, -// (buf.validate.field).int32.example = 2 -// ]; -// } +message TestInt32Rules { + optional int32 test = 1 [ + (buf.validate.field).int32.lt = 5, + (buf.validate.field).int32.(custom.abs_not_in) = 1, + (buf.validate.field).int32.(custom.abs_not_in) = -2, + (buf.validate.field).int32.example = 3, + (buf.validate.field).int32.example = 2 + ]; +} From 6019ed1488c7d29779342511f479756eb085b09c Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 1 Oct 2024 11:32:07 -0400 Subject: [PATCH 27/30] Fix CI --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index ca8803698d..1290e4fafe 100644 --- a/go.sum +++ b/go.sum @@ -34,8 +34,6 @@ github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/ github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a h1:l3RhVoG0RtC61h6TVWnkniGj4TgBebuyPQRdleFAmTg= github.com/bufbuild/protoplugin v0.0.0-20240911180120-7bb73e41a54a/go.mod h1:c5D8gWRIZ2HLWO3gXYTtUfw/hbJyD8xikv2ooPxnklQ= -github.com/bufbuild/protovalidate-go v0.7.0 h1:MYU9GSZM7TSsWNywvyXoEc8y3kc1MNqD3k5mddIBEL4= -github.com/bufbuild/protovalidate-go v0.7.0/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= github.com/bufbuild/protovalidate-go v0.7.1 h1:ac50NTO6+1+mKg5sP/GBPLlMkQFeI+OeaYGFdS1vu98= github.com/bufbuild/protovalidate-go v0.7.1/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= From 49da0055fe0751fcaae4e662a5f54327513e80fd Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Tue, 1 Oct 2024 19:04:43 -0400 Subject: [PATCH 28/30] Address comments --- .../internal/buflintvalidate/field.go | 3 +++ .../internal/buflintvalidate/predefined_rules.go | 2 +- .../internal/buflintvalidate/util.go | 16 ++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index 86288ab320..d1998a42a9 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -767,6 +767,9 @@ func checkExampleValues( exampleValues []protoreflect.Value, extensionTypeResolver protoencoding.Resolver, ) error { + // We need to reparse the extensions here to ensure that we are capturing constraints from + // predefined rules that may be from imported files here. This is to ensure that example + // values adhere to all constraints. if err := protoencoding.ReparseExtensions(extensionTypeResolver, typeRulesMessage); err != nil { return err } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go index ad8edf2be0..4ba62095f2 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/predefined_rules.go @@ -52,7 +52,7 @@ func checkPredefinedRuleExtension( if validate.File_buf_validate_validate_proto.Messages().ByName(extendedRuleFullName.Name()) == nil { return nil } - predefinedConstraints, err := resolveExt[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined, extensionResolver) + predefinedConstraints, err := resolveExtension[*validate.PredefinedConstraints](extensionDescriptor.Options(), validate.E_Predefined, extensionResolver) if err != nil { return err } diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go index b506c6cd24..be00ae82d5 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/util.go @@ -39,11 +39,11 @@ func (r *constraintsResolverForTargetField) ResolveFieldConstraints(desc protore return r.StandardConstraintResolver.ResolveFieldConstraints(desc) } -func (r *constraintsResolverForTargetField) ResolveMessageConstraints(desc protoreflect.MessageDescriptor) *validate.MessageConstraints { +func (*constraintsResolverForTargetField) ResolveMessageConstraints(protoreflect.MessageDescriptor) *validate.MessageConstraints { return nil } -func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protoreflect.OneofDescriptor) *validate.OneofConstraints { +func (*constraintsResolverForTargetField) ResolveOneofConstraints(protoreflect.OneofDescriptor) *validate.OneofConstraints { return nil } @@ -51,25 +51,25 @@ func (r *constraintsResolverForTargetField) ResolveOneofConstraints(desc protore // for marshalling and unmarshalling. We also added error handling for marshal/unmarshal. // // This resolves the given extension and returns the constraints for the extension. -func resolveExt[C proto.Message]( +func resolveExtension[C proto.Message]( options proto.Message, extType protoreflect.ExtensionType, resolver protoencoding.Resolver, ) (constraints C, retErr error) { num := extType.TypeDescriptor().Number() - var msg proto.Message + var message proto.Message proto.RangeExtensions(options, func(typ protoreflect.ExtensionType, i interface{}) bool { if num != typ.TypeDescriptor().Number() { return true } - msg, _ = i.(proto.Message) + message, _ = i.(proto.Message) return false }) - if msg == nil { + if message == nil { return constraints, nil - } else if m, ok := msg.(C); ok { + } else if m, ok := message.(C); ok { return m, nil } var ok bool @@ -77,7 +77,7 @@ func resolveExt[C proto.Message]( if !ok { return constraints, fmt.Errorf("unexpected type for constraints %T", constraints) } - b, err := protoencoding.NewWireMarshaler().Marshal(msg) + b, err := protoencoding.NewWireMarshaler().Marshal(message) if err != nil { return constraints, err } From 4f73316bc694a926616e6d11db02a92cfeec11d1 Mon Sep 17 00:00:00 2001 From: Doria Keung Date: Wed, 2 Oct 2024 12:43:31 -0400 Subject: [PATCH 29/30] Update comment --- .../bufcheckserver/internal/buflintvalidate/field.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go index d1998a42a9..7e9719d4df 100644 --- a/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go +++ b/private/bufpkg/bufcheck/bufcheckserver/internal/buflintvalidate/field.go @@ -767,9 +767,10 @@ func checkExampleValues( exampleValues []protoreflect.Value, extensionTypeResolver protoencoding.Resolver, ) error { - // We need to reparse the extensions here to ensure that we are capturing constraints from - // predefined rules that may be from imported files here. This is to ensure that example - // values adhere to all constraints. + // A rule on this field may be from a predefined rule from an imported file. In order to + // set example values on the message for validation and check against all rules on the field, + // we pass in an extensionTypeResolver that includes imported files and reparse all extensions + // for the message to ensure that we are able to resolve predefined rules. if err := protoencoding.ReparseExtensions(extensionTypeResolver, typeRulesMessage); err != nil { return err } From 02d8762ef1f0a4ec9c09bc0ee41f9e8da56214e6 Mon Sep 17 00:00:00 2001 From: bufdev Date: Thu, 3 Oct 2024 10:39:34 -0400 Subject: [PATCH 30/30] Update CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3145874e9f..8d55799bc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,10 @@ ## [Unreleased] -- No changes yet. +- Update the `PROTOVALIDATE` lint rule to check example field options. Examples will be checked that + they satisfy the field constraints, and are only present if constraints are present. +- Update the `PROTOVALIDATE` lint rule to check predefined rules. Predefined rules will be checked + that they compile. ## [v1.43.0] - 2024-09-30