Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ message StringNone {
message StringConst {
string val = 1 [(buf.validate.field).string.const = "foo"];
}
message StringDecimal {
string val = 1 [(buf.validate.field).string.decimal = {}];
}
message StringDecimalPrecision {
string val = 1 [(buf.validate.field).string.decimal.precision = 4];
}
message StringDecimalScale {
string val = 1 [(buf.validate.field).string.decimal.scale = 2];
}
message StringDecimalPrecisionScale {
string val = 1 [
(buf.validate.field).string.decimal.precision = 6,
(buf.validate.field).string.decimal.scale = 2
];
}
message StringIn {
string val = 1 [(buf.validate.field).string = {
in: [
Expand Down
95 changes: 95 additions & 0 deletions proto/protovalidate/buf/validate/validate.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3756,6 +3756,85 @@ message StringRules {
}
];

// `decimal` specifies that this string represents a decimal with specific precision and scale.
//
// ```proto
// message MyString {
// // Value must be a number with at most six digits, at most two digits after the decimal
// // point, and at most four digits before the decimal point.
// // "1000", "100.1", and "100" are valid, whereas "10000" and "1.111" are not.
// string value = 1 [
// (buf.validate.field).string.decimal.precision = 6,
// (buf.validate.field).string.decimal.scale = 2
// ];
// }
// ```
DecimalRules decimal = 36 [
(predefined).cel = {
id: "string.decimal.numerals"
message: "value must not have characters other than decimal digits and a decimal point"
expression: "!has(rules.decimal) || !this.matches('[^0-9.]')"
},
(predefined).cel = {
id: "string.decimal.multiple_points"
message: "value must not have multiple decimal points"
expression: "!has(rules.decimal) || !this.matches('\\..*\\.')"
},
(predefined).cel = {
id: "string.decimal.leading_point"
message: "value must not begin with a decimal point"
expression: "!has(rules.decimal) || !this.matches('^\\.')"
},
(predefined).cel = {
id: "string.decimal.trailing_point"
message: "value must not end with a decimal point"
expression: "!has(rules.decimal) || !this.matches('\\.$')"
},
(predefined).cel = {
id: "string.decimal.leading_zero"
message: "value must not have a leading zero"
expression: "!has(rules.decimal) || !this.matches('^0[0-9]')"
},
(predefined).cel = {
id: "string.decimal.empty"
message: "value must have at least one digit"
expression: "!has(rules.decimal) || this != ''"
},

(predefined).cel = {
id: "string.decimal.precision"
expression:
"has(rules.decimal.precision) &&"
"this.replace('.', '').size() > rules.decimal.precision"
" ? 'value must have no more than %d digits; got %d digits'"
" .format([rules.decimal.precision, this.replace('.', '').size()])"
" : ''"
},

(predefined).cel = {
id: "string.decimal.scale"
expression:
"has(rules.decimal.scale) && this.contains('.') &&"
"(this.size() - this.lastIndexOf('.') - 1) > rules.decimal.scale"
" ? 'value must have no more than %d digits after the decimal place; got %d digits'"
" .format([rules.decimal.scale, (this.size() - this.lastIndexOf('.') - 1)])"
" : ''"
},

(predefined).cel = {
id: "string.decimal.precision_scale"
expression:
"has(rules.decimal.precision) && has(rules.decimal.scale) &&"
"(this.contains('.') ? this.indexOf('.') : this.size()) > (rules.decimal.precision - rules.decimal.scale)"
" ? 'value must have no more than %d digits before the decimal place; got %d digits'"
" .format(["
" rules.decimal.precision - rules.decimal.scale,"
" (this.contains('.') ? this.indexOf('.') : this.size()),"
" ])"
" : ''"
}
];

// `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.
Expand Down Expand Up @@ -3845,6 +3924,22 @@ message StringRules {
extensions 1000 to max;
}

// Rules to describe a decimal represented as a string.
//
// TODO: Extend to the possible representations that google.type.Decimal allows?
Comment on lines +3928 to +3929
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not certain we'll ever want to do this, but if we do, I think we'll want to move parsing to the libraries which would increase the scope a lot. I suspect we won't want to do that now in any case.

message DecimalRules {
// The total number of digits allowed (before and after the decimal point).
optional uint32 precision = 1;
// The number of digits allowed after the decimal point; when subtracted from `precision`, the
// number of digits allowed before the decimal point.
//
// This can only be validated when `precision` is also set.
//
// It also must be less than `precision`. That is, at least one digit must be before the
// decimal point.
optional uint32 scale = 2;
}

// KnownRegex contains some well-known patterns.
enum KnownRegex {
KNOWN_REGEX_UNSPECIFIED = 0;
Expand Down
Loading
Loading