struct MessageDescriptorValidatorVisitor {
    walked_message_descriptor: Vec<MessageDescriptor>,
}
Expand description

There are five valid forms of field descriptor that we want. They are numbered (1), (2), (a), (b.i), (b.ii). We also follow Java RecordLayer restrictions on unsigned types.

The form (a) is a special case of form (2).

Forms (1) and (2) describe most common field descriptor forms that we want, which are fields containing optional and repeated.

  1. cardinality: Optional, supports_presence: true, is_list: false, is_map: false, default_value: None, containing_oneof: Some("..."), where ... is the name of the oneof. ... can be explicitly provided when oneof keyword is used. Alternatively, when optional is specified, it is generated by the protobuf compiler.

  2. cardinality: Repeated, supports_presence: false, default_value: None, containing_oneof: None. When is_list: true then the field is a repeated. When is_map: true, the field is a map (with a compiler generated map entry message type).

    In proto3, there is no concept of optional repeated 1 or optional map.

When cardinality: Optional, support_presence: false and is_list: false, and is_map: false that is a round about way of saying “required” with default value. We do not allow that for form (1) described above.

The only way to specify that a field is “required” is by using key expression.

For all valid forms (i.e., form (1) and (2) described above and map form described below) and we outright reject cardinality: Required. If we see cardinality: Required, there is some serious bug, as we are checking for proto3 from file descriptor.

For all valid forms, we reject is_group: true, default_value: Some(...). There is no API from FileDescriptor type to check default_value. Therefore we need to check using FileDescriptorProto type. Hence you will see the following code.

field_descriptor
  .field_descriptor_proto()
  .default_value
  .is_some()

Protobuf packing can vary depending on the type. So, we do not check is_packed.

Protobuf message can have no fields. Together with oneof this is very useful in building up discriminated unions (tagged union, sum types). We use this technique in cursor.proto in KeyValueContinuation message.

We support oneof keyword. Oneof fields are just fields in the enclosing message descriptor 2 and they have containing_oneof: Some("..."), where ... specified in .proto file rather than being compiler generated. Fields that are part of oneof keyword have supports_presence: true, cardinality: Optional, which is form (1) described above.

Additionally, within oneof, there cannot be a repeated field 3. This ensures that we won’t see an empty repeated field within oneof.

Following message type gives us the correct field descriptor for form (1).

message HelloWorld {
  optional fdb_rl.field.v1.UUID primary_key = 1;
  optional string hello = 2;
  optional string world = 3;
}

If we were to remove the optional keyword, for example:

message HelloWorld {
  fdb_rl.field.v1.UUID non_optional_primary_key = 4;
  string non_optional_string = 5;
}

Then the field non_optional_primary_key would have containing_oneof: None. The field non_optional_string would have containing_oneof: None and supports_presence: false.

The logic for checking form (1) and (2) is implemented in visit_field_descriptor.

Protobuf map type is a special case. The map field is implemented using with a protobuf generated message type. We will refer to the generated protobuf message type as GeneratedMapEntryMessage.

In case of map fields, there are three field descriptors to be aware of.

a. The field descriptor where map field is defined. This field descriptor will have the kind of protobuf generated message type (i.e., GeneratedMapEntryMessage)

b. Within GeneratedMapEntryMessage, there would be two field descriptors whose names are: i. key ii. value

The map field’s field descriptor (i.e., (a) above) will have cardinality: Repeated, supports_presence: false, containing_oneof: None. It will have is_map: true. This scenario is taken care of by (2) above in visit_field_descriptor.

The GeneratedMapEntryMessage message descriptor has is_map_entry: true. It has two field descriptors with name key (key_type) and value (value_type). How we walk a message descriptor depends on if is_map_entry is true or false. This is handled in walk_message_descriptor.

The field descriptor with name key ((b.i) above) has cardinality: Optional, supports_presence: false, is_list: false, is_map: false, default_value: None, containing_oneof: None.

The field descriptor with name value ((b.ii) above) has cardinality: Optional, is_list: false, is_map: false, default_value: None, containing_oneof: None. Note: support_presence would be false if the value_type is scalar and it would be true if the value_type is a message.

Protobuf allows key_type to be any integral or string type 4. We however want to limit key_type to be a string. The motivation for this is when necessary we want the key value to be PartiQL tuple attribute, which is a string.

The logic for checking field descriptor with name key ((b.i) above) is implemented in visit_map_entry_key_type_field_descriptor and the logic for checking field descriptor with name value ((b.ii) above) is implemented in visit_map_entry_value_type_field_descriptor.

Unsigned types (uint32, uint64, fixed32 and fixed64) are invalid 5.

Fields§

§walked_message_descriptor: Vec<MessageDescriptor>

Implementations§

Trait Implementations§

source§

impl Debug for MessageDescriptorValidatorVisitor

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Visitor for MessageDescriptorValidatorVisitor

source§

fn check_fdb_wkt(&self, message_descriptor: &MessageDescriptor) -> bool

Checks if the message descriptor is a FDB Record Layer well known type.

source§

fn previously_walked_check_or_update( &mut self, message_descriptor: &MessageDescriptor ) -> bool

Checks if we have seen the message descriptor before. If not, we update that we have seen it and returns false. Therefore, next time we are called, we can return true.

source§

fn visit_parent_file_descriptor(&self, file_descriptor: FileDescriptor) -> bool

Returns true if .proto file was compiled using syntax = proto3.

source§

fn visit_field_descriptor(&mut self, field_descriptor: FieldDescriptor) -> bool

Returns true if the field descriptor is considered “valid”.

See documentation on type MessageDescriptorValidatorVisitor for details.

source§

fn visit_map_entry_key_type_field_descriptor( &self, field_descriptor: FieldDescriptor ) -> bool

Returns true if the field descriptor is considered “valid”.

See documentation on type MessageDescriptorValidatorVisitor for details.

source§

fn visit_map_entry_value_type_field_descriptor( &mut self, field_descriptor: FieldDescriptor ) -> bool

Returns true if the field descriptor is considered “valid”.

See documentation on type MessageDescriptorValidatorVisitor for details.

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for Twhere T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for Twhere T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for Twhere T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for Twhere U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for Twhere U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for Twhere U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.