Skip to content

[Feature Request] Support google.protobuf.FieldMask well-known typeΒ #428

@choopm

Description

@choopm

Feature description:

Support of rules for the well-known type google.protobuf.FieldMask:

  • accept: repeated field defining allowed paths (violation if unlisted paths are encountered)
  • reject: repeated field defining disallowed paths (violation if any specified path is encountered)

Problem it solves or use case:

When trying to validate the well-known type google.protobuf.FieldMask (which simply has a repeated string paths) against a list of valid paths, one currently has to use a custom cel like this:

google.protobuf.FieldMask update_mask = 11 [(buf.validate.field).cel = {
  message: "field_mask contains invalid paths"
  expression: "this.paths.all(f, f in ['a', 'b'])"
}];

This is okay but you have to keep repeating your rules for other fields over and over again.

It would be great to define rules once and reuse them.

Proposed implementation or solution:

Add a field FieldMaskRules field_mask = 28; to FieldRules.type.

The definition of FieldMaskRules might look like this:

// FieldMaskRules describe rules applied exclusively to the `google.protobuf.FieldMask` well-known type.
message FieldMaskRules {
  // Requires the FieldMask paths to be a subset of `accept`.
  // If any unknown path is included, an error message is generated.
  //
  // ```proto
  // message MyFieldMask {
  //   //  The `value` FieldMask must only contain paths listed in `accept`.
  //   google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
  //       accept: ["a", "b", "c.a"]
  //   }];
  // }
  // ```
  repeated string accept = 1 [(predefined).cel = {
    id: "field_mask.accept"
    expression: "!this.paths.all(f, f in getField(rules, 'accept')) ? 'value must be a subset of %s'.format([getField(rules, 'accept')]) : ''"
  }];

  // Requires the FieldMask paths to not include any subset of `reject`.
  // If any reject path is included, an error message is generated.
  //
  // ```proto
  // message MyFieldMask {
  //   //  The `value` FieldMask shall not contain paths listed in `reject`.
  //   google.protobuf.FieldMask value = 1 [(buf.validate.field).field_mask = {
  //       reject: ["forbidden", "immutable", "c.a"]
  //   }];
  // }
  // ```
  repeated string reject = 2 [(predefined).cel = {
    id: "field_mask.reject"
    expression: "!this.paths.all(f, !(f in getField(rules, 'reject'))) ? 'value must not contain any subset of %s'.format([getField(rules, 'reject')]) : ''"
  }];

  // Extension fields in this range that have the (buf.validate.predefined)
  // option set will be treated as predefined field rules that can then be
  // set on the field options of other fields to apply field rules.
  // Extension numbers 1000 to 99999 are reserved for extension numbers that are
  // defined in the [Protobuf Global Extension Registry][1]. Extension numbers
  // above this range are reserved for extension numbers that are not explicitly
  // assigned. For rules defined in publicly-consumed schemas, use of extensions
  // above 99999 is discouraged due to the risk of conflicts.
  //
  // [1]: https://github.com/protocolbuffers/protobuf/blob/main/docs/options.md
  extensions 1000 to max;
}

Contribution:

I have created a PR for this. Please help me with test suite.

Additional context:

FieldMasks are most often used when dealing with update requests: Masks are applied against objects and help to select fields for the update of the entity. Most users want to filter immutable fields from update masks when using these.
Thanks to @timostamm for helping me out and pointing me here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatureNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions