Skip to content

Violations messages#

This page describes why Protovalidate uses Protobuf messages to represent rule violations and the messages used.

Background#

Because Protovalidate is built on and for Protobuf, it's only natural that it uses Protobuf messages to represent rule violations. This allows systems using different implementations of Protovalidate to reliably exchange validation state.

For example, an RPC using Connect and Go can use ConnectRPC's Protovalidate interceptor, which returns errors as a Violations message in error details:

// Convert a Go ValidationError into its Protobuf form and return it with
// a Connect error.
if detail, err := connect.NewErrorDetail(validationErr.ToProto()); err == nil {
    connectErr.AddDetail(detail)
}

A Java client written with protovalidate-java can then handle errors as Violations instances, even if it's using gRPC:

assertTrue(
    StatusProto.fromThrowable(statusRuntimeException)
        .getDetailsList()
        .stream()
        .allMatch( it -> it.is(Violations.class) )
);

Violations Message#

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 Rule validation rules. Each individual violation is represented by a Violation message.

violations#

violations is a repeated field that contains all the Violation messages corresponding to the violations detected.

Violation Message#

Violation represents a single instance where a validation rule, expressed as a Rule, was not met. It provides information about the field that caused the violation, the specific rule that wasn't fulfilled, and a human-readable error message.

For example, consider the following message:

message User {
    int32 age = 1 [(buf.validate.field).cel = {
        id: "user.age",
        expression: "this < 18 ? 'User must be at least 18 years old' : ''",
    }];
}

It could produce the following violation:

{
  "ruleId": "user.age",
  "message": "User must be at least 18 years old",
  "field": {
    "elements": [
      {
        "fieldNumber": 1,
        "fieldName": "age",
        "fieldType": "TYPE_INT32"
      }
    ]
  },
  "rule": {
    "elements": [
      {
        "fieldNumber": 23,
        "fieldName": "cel",
        "fieldType": "TYPE_MESSAGE",
        "index": "0"
      }
    ]
  }
}

field#

field is a machine-readable path to the field that failed 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.

For example, consider the following message:

message Message {
  bool a = 1 [(buf.validate.field).required = true];
}

It could produce the following violation:

violation {
  field { element { field_number: 1, field_name: "a", field_type: 8 } }
  ...
}

rule#

rule is a machine-readable path that points to the specific rule that failed validation. This will be a nested field starting from the FieldRules of the field that failed validation. For custom rules, this will provide the path of the rule, e.g. cel[0].

For example, consider the following message:

message Message {
  bool a = 1 [(buf.validate.field).required = true];
  bool b = 2 [(buf.validate.field).cel = {
    id: "custom_rule",
    expression: "!this ? 'b must be true': ''"
  }]
}

It could produce the following violations:

violation {
  rule { element { field_number: 25, field_name: "required", field_type: 8 } }
  ...
}
violation {
  rule { element { field_number: 23, field_name: "cel", field_type: 11, index: 0 } }
  ...
}

rule_id#

rule_id is the unique identifier of the Rule that was not fulfilled. This is the same id that was specified in the Rule message, allowing easy tracing of which rule was violated.

message#

message is a human-readable error message that describes the nature of the violation. This can be the default error message from the violated Rule, or it can be a custom message that gives more context about the violation.

for_key#

for_key indicates whether the violation was caused by a map key, rather than a value.

FieldPath Message#

FieldPath provides a path to a nested protobuf field.

This message provides enough information to render a dotted field path even without protobuf descriptors. It also provides enough information to resolve a nested field through unknown wire data.

elements#

elements contains each element of the path, starting from the root and recursing downward.

FieldPathElement Message#

FieldPathElement provides enough information to nest through a single protobuf field.

If the selected field is a map or repeated field, the subscript value selects a specific element from it. A path that refers to a value nested under a map key or repeated field index will have a subscript value. The field_type field allows unambiguous resolution of a field even if descriptors are not available.

field_number#

field_number is the field number this path element refers to.

field_name#

field_name contains the field name this path element refers to. This can be used to display a human-readable path even if the field number is unknown.

field_type#

field_type specifies the type of this field. When using reflection, this value is not needed.

This value is provided to make it possible to traverse unknown fields through wire data. When traversing wire data, be mindful of both packed1 and delimited2 encoding schemes.

N.B.: Although groups are deprecated, the corresponding delimited encoding scheme is not, and can be explicitly used in Protocol Buffers 2023 Edition.

key_type#

key_type specifies the map key type of this field. This value is useful when traversing unknown fields through wire data: specifically, it allows handling the differences between different integer encodings.

value_type#

value_type specifies map value type of this field. This is useful if you want to display a value inside unknown fields through wire data.

index#

index specifies a 0-based index into a repeated field.

bool_key#

bool_key specifies a map key of type bool.

int_key#

int_key specifies a map key of type int32, int64, sint32, sint64, sfixed32 or sfixed64.

uint_key#

uint_key specifies a map key of type uint32, uint64, fixed32 or fixed64.

string_key#

string_key specifies a map key of type string.