diff --git a/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto b/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto index f590c2de..a0fa9268 100644 --- a/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto +++ b/proto/protovalidate-testing/buf/validate/conformance/cases/strings.proto @@ -196,6 +196,12 @@ message StringTUUID { message StringNotTUUID { string val = 1 [(buf.validate.field).string.tuuid = false]; } +message StringULID { + string val = 1 [(buf.validate.field).string.ulid = true]; +} +message StringNotULID { + string val = 1 [(buf.validate.field).string.ulid = false]; +} message StringHttpHeaderName { string val = 1 [(buf.validate.field).string.well_known_regex = KNOWN_REGEX_HTTP_HEADER_NAME]; } @@ -223,6 +229,12 @@ message StringUUIDIgnore { (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE ]; } +message StringULIDIgnore { + string val = 1 [ + (buf.validate.field).string = {ulid: true}, + (buf.validate.field).ignore = IGNORE_IF_ZERO_VALUE + ]; +} message StringInOneof { oneof foo { string bar = 1 [(buf.validate.field).string = { diff --git a/proto/protovalidate/buf/validate/validate.proto b/proto/protovalidate/buf/validate/validate.proto index 519545dd..5faf6e73 100644 --- a/proto/protovalidate/buf/validate/validate.proto +++ b/proto/protovalidate/buf/validate/validate.proto @@ -3731,6 +3731,31 @@ message StringRules { } ]; + // `ulid` specifies that the field value must be a valid ULID (Universally Unique + // Lexicographically Sortable Identifier) as defined by the [ULID specification](https://github.com/ulid/spec). + // ULID is a 26-character Base32-encoded string using Crockford's Base32 alphabet + // (0-9, A-Z excluding I, L, O, U). If the field value isn't a valid ULID, an error + // message will be generated. + // + // ```proto + // message MyString { + // // value must be a valid ULID + // string value = 1 [(buf.validate.field).string.ulid = true]; + // } + // ``` + bool ulid = 35 [ + (predefined).cel = { + id: "string.ulid" + message: "value must be a valid ULID" + expression: "!rules.ulid || this == '' || this.matches('^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$')" + }, + (predefined).cel = { + id: "string.ulid_empty" + message: "value is empty, which is not a valid ULID" + expression: "!rules.ulid || 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. 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 6a9bfff1..e0a83778 100644 --- a/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go +++ b/tools/internal/gen/buf/validate/conformance/cases/strings.pb.go @@ -2324,6 +2324,94 @@ func (x *StringNotTUUID) GetVal() string { return "" } +type StringULID 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 *StringULID) Reset() { + *x = StringULID{} + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringULID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringULID) ProtoMessage() {} + +func (x *StringULID) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[52] + 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 StringULID.ProtoReflect.Descriptor instead. +func (*StringULID) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{52} +} + +func (x *StringULID) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + +type StringNotULID 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 *StringNotULID) Reset() { + *x = StringNotULID{} + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringNotULID) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringNotULID) ProtoMessage() {} + +func (x *StringNotULID) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[53] + 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 StringNotULID.ProtoReflect.Descriptor instead. +func (*StringNotULID) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{53} +} + +func (x *StringNotULID) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + type StringHttpHeaderName struct { state protoimpl.MessageState `protogen:"open.v1"` Val string `protobuf:"bytes,1,opt,name=val,proto3" json:"val,omitempty"` @@ -2333,7 +2421,7 @@ type StringHttpHeaderName struct { func (x *StringHttpHeaderName) Reset() { *x = StringHttpHeaderName{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[52] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2345,7 +2433,7 @@ func (x *StringHttpHeaderName) String() string { func (*StringHttpHeaderName) ProtoMessage() {} func (x *StringHttpHeaderName) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[52] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2358,7 +2446,7 @@ func (x *StringHttpHeaderName) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHttpHeaderName.ProtoReflect.Descriptor instead. func (*StringHttpHeaderName) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{52} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{54} } func (x *StringHttpHeaderName) GetVal() string { @@ -2377,7 +2465,7 @@ type StringHttpHeaderValue struct { func (x *StringHttpHeaderValue) Reset() { *x = StringHttpHeaderValue{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[53] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2389,7 +2477,7 @@ func (x *StringHttpHeaderValue) String() string { func (*StringHttpHeaderValue) ProtoMessage() {} func (x *StringHttpHeaderValue) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[53] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2402,7 +2490,7 @@ func (x *StringHttpHeaderValue) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHttpHeaderValue.ProtoReflect.Descriptor instead. func (*StringHttpHeaderValue) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{53} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{55} } func (x *StringHttpHeaderValue) GetVal() string { @@ -2421,7 +2509,7 @@ type StringHttpHeaderNameLoose struct { func (x *StringHttpHeaderNameLoose) Reset() { *x = StringHttpHeaderNameLoose{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[54] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2433,7 +2521,7 @@ func (x *StringHttpHeaderNameLoose) String() string { func (*StringHttpHeaderNameLoose) ProtoMessage() {} func (x *StringHttpHeaderNameLoose) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[54] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2446,7 +2534,7 @@ func (x *StringHttpHeaderNameLoose) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHttpHeaderNameLoose.ProtoReflect.Descriptor instead. func (*StringHttpHeaderNameLoose) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{54} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{56} } func (x *StringHttpHeaderNameLoose) GetVal() string { @@ -2465,7 +2553,7 @@ type StringHttpHeaderValueLoose struct { func (x *StringHttpHeaderValueLoose) Reset() { *x = StringHttpHeaderValueLoose{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[55] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2477,7 +2565,7 @@ func (x *StringHttpHeaderValueLoose) String() string { func (*StringHttpHeaderValueLoose) ProtoMessage() {} func (x *StringHttpHeaderValueLoose) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[55] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2490,7 +2578,7 @@ func (x *StringHttpHeaderValueLoose) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHttpHeaderValueLoose.ProtoReflect.Descriptor instead. func (*StringHttpHeaderValueLoose) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{55} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{57} } func (x *StringHttpHeaderValueLoose) GetVal() string { @@ -2509,7 +2597,7 @@ type StringUUIDIgnore struct { func (x *StringUUIDIgnore) Reset() { *x = StringUUIDIgnore{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[56] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2521,7 +2609,7 @@ func (x *StringUUIDIgnore) String() string { func (*StringUUIDIgnore) ProtoMessage() {} func (x *StringUUIDIgnore) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[56] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2534,7 +2622,7 @@ func (x *StringUUIDIgnore) ProtoReflect() protoreflect.Message { // Deprecated: Use StringUUIDIgnore.ProtoReflect.Descriptor instead. func (*StringUUIDIgnore) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{56} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{58} } func (x *StringUUIDIgnore) GetVal() string { @@ -2544,6 +2632,50 @@ func (x *StringUUIDIgnore) GetVal() string { return "" } +type StringULIDIgnore 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 *StringULIDIgnore) Reset() { + *x = StringULIDIgnore{} + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StringULIDIgnore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StringULIDIgnore) ProtoMessage() {} + +func (x *StringULIDIgnore) ProtoReflect() protoreflect.Message { + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[59] + 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 StringULIDIgnore.ProtoReflect.Descriptor instead. +func (*StringULIDIgnore) Descriptor() ([]byte, []int) { + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{59} +} + +func (x *StringULIDIgnore) GetVal() string { + if x != nil { + return x.Val + } + return "" +} + type StringInOneof struct { state protoimpl.MessageState `protogen:"open.v1"` // Types that are valid to be assigned to Foo: @@ -2556,7 +2688,7 @@ type StringInOneof struct { func (x *StringInOneof) Reset() { *x = StringInOneof{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[57] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2568,7 +2700,7 @@ func (x *StringInOneof) String() string { func (*StringInOneof) ProtoMessage() {} func (x *StringInOneof) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[57] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2581,7 +2713,7 @@ func (x *StringInOneof) ProtoReflect() protoreflect.Message { // Deprecated: Use StringInOneof.ProtoReflect.Descriptor instead. func (*StringInOneof) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{57} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{60} } func (x *StringInOneof) GetFoo() isStringInOneof_Foo { @@ -2619,7 +2751,7 @@ type StringHostAndPort struct { func (x *StringHostAndPort) Reset() { *x = StringHostAndPort{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[58] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2631,7 +2763,7 @@ func (x *StringHostAndPort) String() string { func (*StringHostAndPort) ProtoMessage() {} func (x *StringHostAndPort) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[58] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2644,7 +2776,7 @@ func (x *StringHostAndPort) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHostAndPort.ProtoReflect.Descriptor instead. func (*StringHostAndPort) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{58} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{61} } func (x *StringHostAndPort) GetVal() string { @@ -2663,7 +2795,7 @@ type StringHostAndOptionalPort struct { func (x *StringHostAndOptionalPort) Reset() { *x = StringHostAndOptionalPort{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[59] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2675,7 +2807,7 @@ func (x *StringHostAndOptionalPort) String() string { func (*StringHostAndOptionalPort) ProtoMessage() {} func (x *StringHostAndOptionalPort) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[59] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2688,7 +2820,7 @@ func (x *StringHostAndOptionalPort) ProtoReflect() protoreflect.Message { // Deprecated: Use StringHostAndOptionalPort.ProtoReflect.Descriptor instead. func (*StringHostAndOptionalPort) Descriptor() ([]byte, []int) { - return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{59} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{62} } func (x *StringHostAndOptionalPort) GetVal() string { @@ -2707,7 +2839,7 @@ type StringExample struct { func (x *StringExample) Reset() { *x = StringExample{} - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[60] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2719,7 +2851,7 @@ func (x *StringExample) String() string { func (*StringExample) ProtoMessage() {} func (x *StringExample) ProtoReflect() protoreflect.Message { - mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[60] + mi := &file_buf_validate_conformance_cases_strings_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2732,7 +2864,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{60} + return file_buf_validate_conformance_cases_strings_proto_rawDescGZIP(), []int{63} } func (x *StringExample) GetVal() string { @@ -2861,7 +2993,12 @@ const file_buf_validate_conformance_cases_strings_proto_rawDesc = "" + "\vStringTUUID\x12\x1a\n" + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x88\x02\x01R\x03val\",\n" + "\x0eStringNotTUUID\x12\x1a\n" + - "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x88\x02\x00R\x03val\"2\n" + + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x88\x02\x00R\x03val\"(\n" + + "\n" + + "StringULID\x12\x1a\n" + + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x98\x02\x01R\x03val\"+\n" + + "\rStringNotULID\x12\x1a\n" + + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x98\x02\x00R\x03val\"2\n" + "\x14StringHttpHeaderName\x12\x1a\n" + "\x03val\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xc0\x01\x01R\x03val\"3\n" + "\x15StringHttpHeaderValue\x12\x1a\n" + @@ -2871,7 +3008,9 @@ const file_buf_validate_conformance_cases_strings_proto_rawDesc = "" + "\x1aStringHttpHeaderValueLoose\x12\x1d\n" + "\x03val\x18\x01 \x01(\tB\v\xbaH\br\x06\xc8\x01\x00\xc0\x01\x02R\x03val\"1\n" + "\x10StringUUIDIgnore\x12\x1d\n" + - "\x03val\x18\x01 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x03val\"7\n" + + "\x03val\x18\x01 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\xb0\x01\x01R\x03val\"1\n" + + "\x10StringULIDIgnore\x12\x1d\n" + + "\x03val\x18\x01 \x01(\tB\v\xbaH\b\xd8\x01\x01r\x03\x98\x02\x01R\x03val\"7\n" + "\rStringInOneof\x12\x1f\n" + "\x03bar\x18\x01 \x01(\tB\v\xbaH\br\x06R\x01aR\x01bH\x00R\x03barB\x05\n" + "\x03foo\"/\n" + @@ -2896,7 +3035,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, 61) +var file_buf_validate_conformance_cases_strings_proto_msgTypes = make([]protoimpl.MessageInfo, 64) 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 @@ -2950,15 +3089,18 @@ var file_buf_validate_conformance_cases_strings_proto_goTypes = []any{ (*StringNotUUID)(nil), // 49: buf.validate.conformance.cases.StringNotUUID (*StringTUUID)(nil), // 50: buf.validate.conformance.cases.StringTUUID (*StringNotTUUID)(nil), // 51: buf.validate.conformance.cases.StringNotTUUID - (*StringHttpHeaderName)(nil), // 52: buf.validate.conformance.cases.StringHttpHeaderName - (*StringHttpHeaderValue)(nil), // 53: buf.validate.conformance.cases.StringHttpHeaderValue - (*StringHttpHeaderNameLoose)(nil), // 54: buf.validate.conformance.cases.StringHttpHeaderNameLoose - (*StringHttpHeaderValueLoose)(nil), // 55: buf.validate.conformance.cases.StringHttpHeaderValueLoose - (*StringUUIDIgnore)(nil), // 56: buf.validate.conformance.cases.StringUUIDIgnore - (*StringInOneof)(nil), // 57: buf.validate.conformance.cases.StringInOneof - (*StringHostAndPort)(nil), // 58: buf.validate.conformance.cases.StringHostAndPort - (*StringHostAndOptionalPort)(nil), // 59: buf.validate.conformance.cases.StringHostAndOptionalPort - (*StringExample)(nil), // 60: buf.validate.conformance.cases.StringExample + (*StringULID)(nil), // 52: buf.validate.conformance.cases.StringULID + (*StringNotULID)(nil), // 53: buf.validate.conformance.cases.StringNotULID + (*StringHttpHeaderName)(nil), // 54: buf.validate.conformance.cases.StringHttpHeaderName + (*StringHttpHeaderValue)(nil), // 55: buf.validate.conformance.cases.StringHttpHeaderValue + (*StringHttpHeaderNameLoose)(nil), // 56: buf.validate.conformance.cases.StringHttpHeaderNameLoose + (*StringHttpHeaderValueLoose)(nil), // 57: buf.validate.conformance.cases.StringHttpHeaderValueLoose + (*StringUUIDIgnore)(nil), // 58: buf.validate.conformance.cases.StringUUIDIgnore + (*StringULIDIgnore)(nil), // 59: buf.validate.conformance.cases.StringULIDIgnore + (*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 } var file_buf_validate_conformance_cases_strings_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -2973,7 +3115,7 @@ func file_buf_validate_conformance_cases_strings_proto_init() { if File_buf_validate_conformance_cases_strings_proto != nil { return } - file_buf_validate_conformance_cases_strings_proto_msgTypes[57].OneofWrappers = []any{ + file_buf_validate_conformance_cases_strings_proto_msgTypes[60].OneofWrappers = []any{ (*StringInOneof_Bar)(nil), } type x struct{} @@ -2982,7 +3124,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: 61, + NumMessages: 64, 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 9611d098..7e026070 100644 --- a/tools/internal/gen/buf/validate/validate.pb.go +++ b/tools/internal/gen/buf/validate/validate.pb.go @@ -4740,6 +4740,7 @@ type StringRules struct { // *StringRules_Ipv4Prefix // *StringRules_Ipv6Prefix // *StringRules_HostAndPort + // *StringRules_Ulid // *StringRules_WellKnownRegex WellKnown isStringRules_WellKnown `protobuf_oneof:"well_known"` // This applies to regexes `HTTP_HEADER_NAME` and `HTTP_HEADER_VALUE` to @@ -5065,6 +5066,15 @@ func (x *StringRules) GetHostAndPort() bool { return false } +func (x *StringRules) GetUlid() bool { + if x != nil { + if x, ok := x.WellKnown.(*StringRules_Ulid); ok { + return x.Ulid + } + } + return false +} + func (x *StringRules) GetWellKnownRegex() KnownRegex { if x != nil { if x, ok := x.WellKnown.(*StringRules_WellKnownRegex); ok { @@ -5419,6 +5429,24 @@ type StringRules_HostAndPort struct { HostAndPort bool `protobuf:"varint,32,opt,name=host_and_port,json=hostAndPort,oneof"` } +type StringRules_Ulid struct { + // `ulid` specifies that the field value must be a valid ULID (Universally Unique + // Lexicographically Sortable Identifier) as defined by the [ULID specification](https://github.com/ulid/spec). + // ULID is a 26-character Base32-encoded string using Crockford's Base32 alphabet + // (0-9, A-Z excluding I, L, O, U). If the field value isn't a valid ULID, an error + // message will be generated. + // + // ```proto + // + // message MyString { + // // value must be a valid ULID + // string value = 1 [(buf.validate.field).string.ulid = true]; + // } + // + // ``` + Ulid bool `protobuf:"varint,35,opt,name=ulid,oneof"` +} + type StringRules_WellKnownRegex struct { // `well_known_regex` specifies a common well-known pattern // defined as a regex. If the field value doesn't match the well-known @@ -5479,6 +5507,8 @@ func (*StringRules_Ipv6Prefix) isStringRules_WellKnown() {} func (*StringRules_HostAndPort) isStringRules_WellKnown() {} +func (*StringRules_Ulid) isStringRules_WellKnown() {} + func (*StringRules_WellKnownRegex) isStringRules_WellKnown() {} // BytesRules describe the rules applied to `bytes` values. These rules @@ -8079,7 +8109,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\"\xd19\n" + + "\fbool.example\x1a\x04trueR\aexample*\t\b\xe8\a\x10\x80\x80\x80\x80\x02\"\xc9;\n" + "\vStringRules\x12\x8d\x01\n" + "\x05const\x18\x01 \x01(\tBw\xc2Ht\n" + "r\n" + @@ -8210,7 +8240,12 @@ const file_buf_validate_validate_proto_rawDesc = "" + "\x99\x01\n" + "\x14string.host_and_port\x12Avalue must be a valid host (hostname or IP address) and port pair\x1a>!rules.host_and_port || this == '' || this.isHostAndPort(true)\n" + "y\n" + - "\x1astring.host_and_port_empty\x127value is empty, which is not a valid host and port pair\x1a\"!rules.host_and_port || this != ''H\x00R\vhostAndPort\x12\xb8\x05\n" + + "\x1astring.host_and_port_empty\x127value is empty, which is not a valid host and port pair\x1a\"!rules.host_and_port || this != ''H\x00R\vhostAndPort\x12\xf5\x01\n" + + "\x04ulid\x18# \x01(\bB\xde\x01\xc2H\xda\x01\n" + + "}\n" + + "\vstring.ulid\x12\x1avalue must be a valid ULID\x1aR!rules.ulid || this == '' || this.matches('^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$')\n" + + "Y\n" + + "\x11string.ulid_empty\x12)value is empty, which is not a valid ULID\x1a\x19!rules.ulid || this != ''H\x00R\x04ulid\x12\xb8\x05\n" + "\x10well_known_regex\x18\x18 \x01(\x0e2\x18.buf.validate.KnownRegexB\xf1\x04\xc2H\xed\x04\n" + "\xf0\x01\n" + "#string.well_known_regex.header_name\x12&value must be a valid HTTP header name\x1a\xa0\x01rules.well_known_regex != 1 || this == '' || this.matches(!has(rules.strict) || rules.strict ?'^:?[0-9a-zA-Z!#$%&\\'*+-.^_|~\\x60]+$' :'^[^\\u0000\\u000A\\u000D]+$')\n" + @@ -8696,6 +8731,7 @@ func file_buf_validate_validate_proto_init() { (*StringRules_Ipv4Prefix)(nil), (*StringRules_Ipv6Prefix)(nil), (*StringRules_HostAndPort)(nil), + (*StringRules_Ulid)(nil), (*StringRules_WellKnownRegex)(nil), } file_buf_validate_validate_proto_msgTypes[20].OneofWrappers = []any{ diff --git a/tools/protovalidate-conformance/internal/cases/cases_strings.go b/tools/protovalidate-conformance/internal/cases/cases_strings.go index 5a1418f2..3c242ec6 100644 --- a/tools/protovalidate-conformance/internal/cases/cases_strings.go +++ b/tools/protovalidate-conformance/internal/cases/cases_strings.go @@ -1402,6 +1402,110 @@ func stringSuite() suites.Suite { }, ), }, + "ulid/valid/lowercase": { + Message: &cases.StringULID{Val: "01arz3ndektsv4rrffq69g5fav"}, + Expected: results.Success(true), + }, + "ulid/valid/uppercase": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAV"}, + Expected: results.Success(true), + }, + "ulid/valid/mixed_case": { + Message: &cases.StringULID{Val: "01ArZ3NdEkTsV4RrFfQ69G5FaV"}, + Expected: results.Success(true), + }, + "ulid/valid/nil": { + Message: &cases.StringULID{Val: "00000000000000000000000000"}, + Expected: results.Success(true), + }, + "ulid/invalid/empty": { + Message: &cases.StringULID{Val: ""}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid_empty"), + }, + ), + }, + "ulid/invalid/not_checked/empty": { + Message: &cases.StringNotULID{Val: ""}, + Expected: results.Success(true), + }, + "ulid/invalid/not_checked/malformed": { + Message: &cases.StringNotULID{Val: "foobar"}, + Expected: results.Success(true), + }, + "ulid/invalid/malformed": { + Message: &cases.StringULID{Val: "foobar"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/too_short": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FA"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/too_long": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAVX"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/invalid_characters/i": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAI"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/invalid_characters/l": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAL"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/invalid_characters/o": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAO"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, + "ulid/invalid/invalid_characters/u": { + Message: &cases.StringULID{Val: "01ARZ3NDEKTSV4RRFFQ69G5FAU"}, + Expected: results.Violations( + &validate.Violation{ + Field: results.FieldPath("val"), + Rule: results.FieldPath("string.ulid"), + RuleId: proto.String("string.ulid"), + }, + ), + }, "well_known_regex/header_name/strict/valid/header": { Message: &cases.StringHttpHeaderName{Val: "clustername"}, Expected: results.Success(true),