Skip to content

[Feature Request] Add a message field ruleset and allow extending #485

@jeffsawatzky

Description

@jeffsawatzky

Feature description:
Create a (buf.validate.field).message rule type and allow extending. This would be allowed on all message fields, and be able to be extended with our own predefined rules.

This would be useful if we had a reusable message definition, but depending on the context as to where it is used, different validation rules could be applied.

Problem it solves or use case:
Given the following proto example:

edition = "2023";

package mybooks.core.schema.v1;

import "buf/validate/validate.proto";
import "google/api/field_behavior.proto";

/*
 * Uniquely identifies a resource based on type and id
 */
message ResourceIdentifier {
  // The resource type
  string type = 1 [
    (buf.validate.field).required = true,
    (buf.validate.field).string.pattern = "^[a-z_]+/[a-z_]+$",
    (buf.validate.field).string.example = "my_domain/my_resource"
  ];

  // The resource id
  string id = 2 [
    (buf.validate.field).required = true,
    (buf.validate.field).string.min_len = 1
  ];
}

/*
 * Represents a relationship with another resource
 */
message ResourceRelationship {
  // An identifier specifying the other resource
  ResourceIdentifier data = 1;
}

message Identity {
  // The resource type
  string type = 1 [
    (buf.validate.field).required = true,
    (buf.validate.field).string.const = "auth/identity"
  ];

  // The unique id for the membership
  string id = 2 [(buf.validate.field).string.uuid = true];
}

message Organization {
  // The resource type
  string type = 1 [
    (buf.validate.field).required = true,
    (buf.validate.field).string.const = "auth/organization"
  ];

  // The unique id for the organization
  string id = 2 [(buf.validate.field).string.uuid = true];
}

message Membership {
  // The resource type
  string type = 1 [
    (buf.validate.field).required = true,
    (buf.validate.field).string.const = "auth/membership"
  ];

  // The unique id for the membership
  string id = 2 [(buf.validate.field).string.uuid = true];

  // The identity this membership belongs to
  mybooks.core.schema.v1.ResourceRelationship identity = 2 [
    (buf.validate.field).required = true,
    (buf.validate.field).cel = {
      id: "resource_relationship_type"
      message: "resource type is not valid"
      expression: "this.data.type == 'auth/identity'"
    }
  ];

  // The organization this membership belongs to
  mybooks.core.schema.v1.ResourceRelationship organization = 3 [
    (buf.validate.field).required = true,
    (buf.validate.field).cel = {
      id: "resource_relationship_type"
      message: "resource type is not valid"
      expression: "this.data.type == 'auth/organization'"
    }
  ];
}

As you can see, the rules aren't very DRY for the identity and organization fields in the Membership message.

Proposed implementation or solution:

  1. Add an extension range to MessageRules (this would also help with [Feature Request] Allow extending MessageRules #483)
  2. Add a MessageRules message = 17 field to the FieldRules oneof type
  3. Update the validators to check for and run this rule on message fields, passing the message as this, if set

By doing that I can then create a MessageRules extension like so:

extend buf.validate.MessageRules {
  string resource_relationship = 10000 [(buf.validate.predefined).cel = {
    id: "message.resource_relationship"
    expression: "this.data.type == rule ? '' : 'the resource relationship data type must be ' + rule" 
  }];

Then I can DRY up the relationships a bit like so:

  // The identity this membership belongs to
  mybooks.core.schema.v1.ResourceRelationship identity = 2 [
    (buf.validate.field).required = true,
    (buf.validate.field).message.(resource_relationship) = "auth/identity"
    }
  ];

  // The organization this membership belongs to
  mybooks.core.schema.v1.ResourceRelationship organization = 3 [
    (buf.validate.field).required = true,
    (buf.validate.field).message.(resource_relationship) = "auth/organization"
  ];

Contribution:
I can help implement the proto changes, and add support to the protovalidate-python library if needed. Other libraries I can attempt to add support to, but you might want to get other people to do so.

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