Skip to content

[BUG] Violation.FieldDescriptor is nil for map.values violations #322

Description

@haines

Description

FieldDescriptor is nil on violations for map values, which is problematic when attempting to handle violations by referring to the source field's protoreflect type.

Steps to Reproduce

https://github.com/haines/protovalidate-violation-field-descriptor

syntax = "proto3";

package example.v1;

import "buf/validate/validate.proto";

option go_package = "github.com/haines/protovalidate-violation-field-descriptor/gen/example/v1;examplev1";

enum Value {
  VALUE_UNSPECIFIED = 0;
  VALUE_A = 1;
  VALUE_B = 2;
}

message Example {
  Value value = 1 [(buf.validate.field).enum = {
    in: [
      1,
      2
    ]
  }];

  map<string, Value> values = 2 [(buf.validate.field).map.values.enum = {
    in: [
      1,
      2
    ]
  }];
}
package main

import (
	"errors"
	"fmt"
	"log"

	"buf.build/go/protovalidate"
	examplev1 "github.com/haines/protovalidate-violation-field-descriptor/gen/example/v1"
)

func main() {
	err := protovalidate.Validate(&examplev1.Example{
		Value: examplev1.Value_VALUE_UNSPECIFIED,
		Values: map[string]examplev1.Value{"foo": examplev1.Value_VALUE_UNSPECIFIED},
	})
	validationErr, ok := errors.AsType[*protovalidate.ValidationError](err)
	if !ok {
		log.Fatalln(err)
	}

	for _, violation := range validationErr.Violations {
		fmt.Println(violation)
		fmt.Println(violation.FieldDescriptor)
		fmt.Println()
	}
}
$ go run github.com/haines/protovalidate-violation-field-descriptor@v0.0.0-20260615182102-eacba7874c96
value: must be in list [1, 2]
FieldDescriptor{Syntax: proto3, FullName: example.v1.Example.value, Number: 1, Cardinality: optional, Kind: enum, HasJSONName: true, JSONName: "value", Enum: example.v1.Value}

values["foo"]: must be in list [1, 2]
<nil>

Expected Behavior

violation.FieldDescriptor is set to the map values' field descriptor (i.e. what you get by calling MapValue() on the parent field descriptor).

Actual Behavior

violation.FieldDescriptor is nil.

Screenshots/Logs

N/A

Environment

  • Operating System: N/A
  • Version: N/A
  • Compiler/Toolchain: N/A
  • Protobuf Compiler & Version: buf v1.70.0, buf.build/protocolbuffers/go:v1.36.11
  • Protovalidate Version: v1.2.0

Possible Solution

Additional Context

I am trying to work around #125 by creating a new error message for violations of enum.in rules.

I came up with the following

for _, violation := range validationErr.Violations {
	if violation.RuleDescriptor.FullName() == "buf.validate.EnumRules.in" {
		allValues := violation.FieldDescriptor.Enum().Values()
		allowedValues := violation.RuleValue.List()

		allowedNames := make([]string, allowedValues.Len())
		for i := range allowedValues.Len() {
			number := allowedValues.Get(i).Int()
			desc := allValues.ByNumber(protoreflect.EnumNumber(number))
			if desc == nil {
				allowedNames[i] = strconv.FormatInt(number, 10)
			} else {
				allowedNames[i] = string(desc.Name())
			}
		}

		slices.Sort(allowedNames)
		violation.Proto.Message = proto.String(fmt.Sprintf("must be one of [%s]", strings.Join(allowedNames, ", ")))
	}
}

This works nicely for enum fields in messages. However, it panics on enum-valued maps, because violation.FieldDescriptor is nil.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions