diff --git a/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto b/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto index 1283a7bf..04c20fa9 100644 --- a/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto +++ b/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto @@ -256,6 +256,14 @@ message StringHostAndOptionalPort { }]; } +message StringProtobufFQN { + string val = 1 [(buf.validate.field).string.protobuf_fqn = true]; +} + +message StringProtobufDotFQN { + string val = 1 [(buf.validate.field).string.protobuf_dot_fqn = true]; +} + message StringExample { string val = 1 [(buf.validate.field).string.example = "foo"]; } diff --git a/proto/protovalidate/buf/validate/validate.proto b/proto/protovalidate/buf/validate/validate.proto index 75ca8317..4a10f74f 100644 --- a/proto/protovalidate/buf/validate/validate.proto +++ b/proto/protovalidate/buf/validate/validate.proto @@ -3793,7 +3793,7 @@ message StringRules { (predefined).cel = { id: "string.protobuf_dot_fqn" message: "value must be a valid fully-qualified Protobuf name with a leading dot" - expression: "!rules.protobuf_dot_fqn || this == '' || this.matches('(\\\\.[A-Za-z_][A-Za-z_0-9]*)*$')" + expression: "!rules.protobuf_dot_fqn || this == '' || this.matches('^\\\\.[A-Za-z_][A-Za-z_0-9]*(\\\\.[A-Za-z_][A-Za-z_0-9]*)*$')" }, (predefined).cel = { id: "string.protobuf_dot_fqn_empty" diff --git a/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go b/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go index ac4884bf..bab78918 100644 --- a/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go +++ b/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go @@ -2830,6 +2830,94 @@ func (x *StringHostAndOptionalPort) GetVal() string { return "" } +type StringProtobufFQN struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val string `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StringProtobufFQN) Reset() { + *x = StringProtobufFQN{} + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringProtobufFQN) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringProtobufFQN) ProtoMessage() {} + +func (x *StringProtobufFQN) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringProtobufFQN.ProtoReflect.Descriptor instead. +func (*StringProtobufFQN) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{63} +} + +func (x *StringProtobufFQN) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + +type StringProtobufDotFQN struct { + state protoimpl.MessageState `protogen:"open.v1"` + Val string `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StringProtobufDotFQN) Reset() { + *x = StringProtobufDotFQN{} + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[64] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringProtobufDotFQN) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringProtobufDotFQN) ProtoMessage() {} + +func (x *StringProtobufDotFQN) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[64] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StringProtobufDotFQN.ProtoReflect.Descriptor instead. +func (*StringProtobufDotFQN) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{64} +} + +func (x *StringProtobufDotFQN) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + type StringExample struct { state protoimpl.MessageState `protogen:"open.v1"` Val string `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` @@ -2839,7 +2927,7 @@ type StringExample struct { func (x *StringExample) Reset() { *x = StringExample{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2851,7 +2939,7 @@ func (x *StringExample) String() string { func (*StringExample) ProtoMessage() {} func (x *StringExample) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2864,7 +2952,7 @@ func (x *StringExample) ProtoReflect() protoreflect.Message { // Deprecated: Use StringExample.ProtoReflect.Descriptor instead. func (*StringExample) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{63} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{65} } func (x *StringExample) GetVal() string { @@ -3018,7 +3106,11 @@ const file_buf_validate_conformance_cases_strings_proto_rawDesc = "" + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x80\x02\x01R\x03val\"\xa4\x01\n" + "\x19StringHostAndOptionalPort\x12\x86\x01\n" + "\x03val\x18\x01 \x01(\tBt\xbaHq\xba\x01n\n" + - "\"string.host_and_port.optional_port\x12-value must be a host and (optional) port pair\x1a\x19this.isHostAndPort(false)R\x03val\".\n" + + "\"string.host_and_port.optional_port\x12-value must be a host and (optional) port pair\x1a\x19this.isHostAndPort(false)R\x03val\"/\n" + + "\x11StringProtobufFQN\x12\x1a\n" + + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xa8\x02\x01R\x03val\"2\n" + + "\x14StringProtobufDotFQN\x12\x1a\n" + + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x02\x01R\x03val\".\n" + "\rStringExample\x12\x1d\n" + "\x03val\x18\x01 \x01(\tB\v\xbaH\br\x06\x92\x02\x03fooR\x03valB\xa3\x02\n" + "\"com.buf.validate.conformance.casesB\fStringsProtoP\x01ZSgithub.com/bufbuild/protovalidate/tools/internal/gen/buf/validate/conformance/cases\xa2\x02\x04BVCC\xaa\x02\x1eBuf.Validate.Conformance.Cases\xca\x02\x1eBuf\\Validate\\Conformance\\Cases\xe2\x02*Buf\\Validate\\Conformance\\Cases\\GPBMetadata\xea\x02!Buf::Validate::Conformance::Casesb\x06proto3" @@ -3035,7 +3127,7 @@ func file_buf_validate_conformance_cases_strings_proto_rawDescGZIP() []byte { return file_buf_validate_conformance_cases_strings_proto_rawDescData } -var file_buf_validate_conformance_cases_strings_proto_msgTypes = make([]protoimpl.MessageInfo, 64) +var file_buf_validate_conformance_cases_strings_proto_msgTypes = make([]protoimpl.MessageInfo, 66) var file_buf_validate_conformance_cases_strings_proto_goTypes = []any{ (*StringNone)(nil), // 0: buf.validate.conformance.cases.StringNone (*StringConst)(nil), // 1: buf.validate.conformance.cases.StringConst @@ -3100,7 +3192,9 @@ var file_buf_validate_conformance_cases_strings_proto_goTypes = []any{ (*StringInOneof)(nil), // 60: buf.validate.conformance.cases.StringInOneof (*StringHostAndPort)(nil), // 61: buf.validate.conformance.cases.StringHostAndPort (*StringHostAndOptionalPort)(nil), // 62: buf.validate.conformance.cases.StringHostAndOptionalPort - (*StringExample)(nil), // 63: buf.validate.conformance.cases.StringExample + (*StringProtobufFQN)(nil), // 63: buf.validate.conformance.cases.StringProtobufFQN + (*StringProtobufDotFQN)(nil), // 64: buf.validate.conformance.cases.StringProtobufDotFQN + (*StringExample)(nil), // 65: buf.validate.conformance.cases.StringExample } var file_buf_validate_conformance_cases_strings_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -3124,7 +3218,7 @@ func file_buf_validate_conformance_cases_strings_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_buf_validate_conformance_cases_strings_proto_rawDesc), len(file_buf_validate_conformance_cases_strings_proto_rawDesc)), NumEnums: 0, - NumMessages: 64, + NumMessages: 66, NumExtensions: 0, NumServices: 0, }, diff --git a/tools/internal/gen/buf/validate/validate.pb.go b/tools/internal/gen/buf/validate/validate.pb.go index 8736ee16..48931203 100644 --- a/tools/internal/gen/buf/validate/validate.pb.go +++ b/tools/internal/gen/buf/validate/validate.pb.go @@ -8455,7 +8455,7 @@ const file_buf_validate_validate_proto_rawDesc = "" + "bool.const\x1a`this != getField(rules, 'const') ? 'value must equal %s'.format([getField(rules, 'const')]) : ''R\x05const\x123\n" + "\aexample\x18\x02 \x03(\bB\x19\xc2H\x16\n" + "\x14\n" + - "\fbool.example\x1a\x04trueR\aexample*\t\b\xe8\a\x10\x80\x80\x80\x80\x02\"\xc9A\n" + + "\fbool.example\x1a\x04trueR\aexample*\t\b\xe8\a\x10\x80\x80\x80\x80\x02\"\xe3A\n" + "\vStringRules\x12\x8d\x01\n" + "\x05const\x18\x01 \x01(\tBw\xc2Ht\n" + "r\n" + @@ -8596,10 +8596,10 @@ const file_buf_validate_validate_proto_rawDesc = "" + "\xb5\x01\n" + "\x13string.protobuf_fqn\x123value must be a valid fully-qualified Protobuf name\x1ai!rules.protobuf_fqn || this == '' || this.matches('^[A-Za-z_][A-Za-z_0-9]*(\\\\.[A-Za-z_][A-Za-z_0-9]*)*$')\n" + "\x82\x01\n" + - "\x19string.protobuf_fqn_empty\x12Bvalue is empty, which is not a valid fully-qualified Protobuf name\x1a!!rules.protobuf_fqn || this != ''H\x00R\vprotobufFqn\x12\x8d\x03\n" + - "\x10protobuf_dot_fqn\x18& \x01(\bB\xe0\x02\xc2H\xdc\x02\n" + - "\xb9\x01\n" + - "\x17string.protobuf_dot_fqn\x12Fvalue must be a valid fully-qualified Protobuf name with a leading dot\x1aV!rules.protobuf_dot_fqn || this == '' || this.matches('(\\\\.[A-Za-z_][A-Za-z_0-9]*)*$')\n" + + "\x19string.protobuf_fqn_empty\x12Bvalue is empty, which is not a valid fully-qualified Protobuf name\x1a!!rules.protobuf_fqn || this != ''H\x00R\vprotobufFqn\x12\xa7\x03\n" + + "\x10protobuf_dot_fqn\x18& \x01(\bB\xfa\x02\xc2H\xf6\x02\n" + + "\xd3\x01\n" + + "\x17string.protobuf_dot_fqn\x12Fvalue must be a valid fully-qualified Protobuf name with a leading dot\x1ap!rules.protobuf_dot_fqn || this == '' || this.matches('^\\\\.[A-Za-z_][A-Za-z_0-9]*(\\\\.[A-Za-z_][A-Za-z_0-9]*)*$')\n" + "\x9d\x01\n" + "\x1dstring.protobuf_dot_fqn_empty\x12Uvalue is empty, which is not a valid fully-qualified Protobuf name with a leading dot\x1a%!rules.protobuf_dot_fqn || this != ''H\x00R\x0eprotobufDotFqn\x12\xb8\x05\n" + "\x10well_known_regex\x18\x18 \x01(\x0e2\x18.buf.validate.KnownRegexB\xf1\x04\xc2H\xed\x04\n" + diff --git a/tools/protovalidate-conformance/internal/cases/cases_strings.go b/tools/protovalidate-conformance/internal/cases/cases_strings.go index f70e7ce6..5e8eb7a0 100644 --- a/tools/protovalidate-conformance/internal/cases/cases_strings.go +++ b/tools/protovalidate-conformance/internal/cases/cases_strings.go @@ -1916,6 +1916,145 @@ func stringSuite() suites.Suite { }, ), }, + "protobuf_fqn/buf.validate/valid": { + Message: &cases.StringProtobufFQN{Val: "buf.validate"}, + Expected: results.Success(true), + }, + "protobuf_fqn/my_package.MyMessage/valid": { + Message: &cases.StringProtobufFQN{Val: "my_package.MyMessage"}, + Expected: results.Success(true), + }, + "protobuf_fqn/_any_Crazy_CASE_with_01234_numbers/valid": { + Message: &cases.StringProtobufFQN{Val: "_any_Crazy_CASE_with_01234_numbers"}, + Expected: results.Success(true), + }, + "protobuf_fqn/c3p0/valid": { + Message: &cases.StringProtobufFQN{Val: "c3p0"}, + Expected: results.Success(true), + }, + "protobuf_fqn/leading_dot/invalid": { + Message: &cases.StringProtobufFQN{Val: ".x"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn"), + }, + ), + }, + "protobuf_fqn/empty/invalid": { + Message: &cases.StringProtobufFQN{Val: ""}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn_empty"), + }, + ), + }, + "protobuf_fqn/trailing_dot/invalid": { + Message: &cases.StringProtobufFQN{Val: "x."}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn"), + }, + ), + }, + "protobuf_fqn/double_dot/invalid": { + Message: &cases.StringProtobufFQN{Val: "a..b"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn"), + }, + ), + }, + "protobuf_fqn/leading_digit/invalid": { + Message: &cases.StringProtobufFQN{Val: "1a"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn"), + }, + ), + }, + "protobuf_fqn/bad_char/invalid": { + Message: &cases.StringProtobufFQN{Val: "a$"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_fqn"), + RuleId: proto.String("string.protobuf_fqn"), + }, + ), + }, + "protobuf_dot_fqn/.buf.validate/valid": { + Message: &cases.StringProtobufDotFQN{Val: ".buf.validate"}, + Expected: results.Success(true), + }, + "protobuf_dot_fqn/.my_package.MyMessage/valid": { + Message: &cases.StringProtobufDotFQN{Val: ".my_package.MyMessage"}, + Expected: results.Success(true), + }, + "protobuf_dot_fqn/._any_Crazy_CASE_with_01234_numbers/valid": { + Message: &cases.StringProtobufDotFQN{Val: "._any_Crazy_CASE_with_01234_numbers"}, + Expected: results.Success(true), + }, + "protobuf_dot_fqn/empty/invalid": { + Message: &cases.StringProtobufDotFQN{Val: ""}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_dot_fqn"), + RuleId: proto.String("string.protobuf_dot_fqn_empty"), + }, + ), + }, + "protobuf_dot_fqn/trailing_dot/invalid": { + Message: &cases.StringProtobufDotFQN{Val: ".x."}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_dot_fqn"), + RuleId: proto.String("string.protobuf_dot_fqn"), + }, + ), + }, + "protobuf_dot_fqn/double_dot/invalid": { + Message: &cases.StringProtobufDotFQN{Val: ".a..b"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_dot_fqn"), + RuleId: proto.String("string.protobuf_dot_fqn"), + }, + ), + }, + "protobuf_dot_fqn/leading_digit/invalid": { + Message: &cases.StringProtobufDotFQN{Val: ".1a"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_dot_fqn"), + RuleId: proto.String("string.protobuf_dot_fqn"), + }, + ), + }, + "protobuf_dot_fqn/bad_char/invalid": { + Message: &cases.StringProtobufDotFQN{Val: ".a$"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.protobuf_dot_fqn"), + RuleId: proto.String("string.protobuf_dot_fqn"), + }, + ), + }, + "example/valid": { Message: &cases.StringExample{Val: "foobar"}, Expected: results.Success(true),