Documentation
¶
Overview ¶
Package protowalk contains routines for efficiently walking proto messages, focusing on the ability to react to field annotations.
This package defines an interface (FieldProcessor) which you can implement to react to any fields you like.
This package also comes with stock implementations for the following common field annotations:
- google.api.field_behavior = REQUIRED
- google.api.field_behavior = OUTPUT_ONLY
- deprecated = true
How it works ¶
1. Construct a new `Walker`
Call NewWalker[*YourProtoMessage](processors...) to get a new [Walker[M]] instance. This computes an execution plan to quickly check instances of M for any fields which need processing according to `processors`. You should make this Walker[M] at `init()` time, or similar, to avoid paying the plan computation cost multiple times.
2. Use Walker.Execute() with an instance of *YourProtoMessage.
This will efficiently walk over the actual instance, checking just the fields which any of the given processors indicated needed processing.
The upshot is that Walker.Execute() should be O(CheckableFields), no matter how big the proto messages are, rather than a naive O(Fields) implementation. Note that if the checkable field is in a repeated message or similar, that the processors will be called once per instance of this checkable field, so it is still possible for an incoming request message to cause variable amounts of processing work.
The returned Result contains the ProtoPath and a message generated by the FieldProcessor. The ProtoPath describes the path through the source message to the affected field, and can either be printed for display, or can be used to retrieve the actual value.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type DeprecatedProcessor ¶
type DeprecatedProcessor struct { // If true, clear the data out of all deprecated fields. ClearField bool }
DeprecatedProcessor will find, and produce a Result for every deprecated field which is set. Deprecated fields are marked like:
type name = <tag> [deprecated = true];
You may also optionally clear the data in deprecated fields by setting ClearField=true.
func (DeprecatedProcessor) Process ¶
func (d DeprecatedProcessor) Process(field protoreflect.FieldDescriptor, msg protoreflect.Message) (data ResultData, applied bool)
Process implements FieldProcessor.
This will optionally clear (zero) the field in `msg` to avoid accidental usage of deprecated fields
func (DeprecatedProcessor) ShouldProcess ¶
func (d DeprecatedProcessor) ShouldProcess(field protoreflect.FieldDescriptor) ProcessAttr
ShouldProcess implements FieldProcessor.
This flags all fields which have `[deprecated = true]` and ignores all other fields.
type DynamicWalker ¶
type DynamicWalker struct {
// contains filtered or unexported fields
}
DynamicWalker is a non-generic version of Walker.
This is useful when you need to generate Walker instances dynamically. If you statically know the message types you're handling, use Walker instead.
Construct with NewDynamicWalker.
A zero-value DynamicWalker will always return nil Results.
func NewDynamicWalker ¶
func NewDynamicWalker(msgD protoreflect.MessageDescriptor, processors ...FieldProcessor) *DynamicWalker
NewDynamicWalker constructs a new DynamicWalker.
func (*DynamicWalker) Execute ¶
func (l *DynamicWalker) Execute(msg proto.Message) (Results, error)
Execute evaluates `msg` in terms of all [FieldProcessor]s given when the DynamicWalker was constructed with NewWalker.
This will only return an error if the type of `msg` does not match the type that was given to NewDynamicWalker. If you statically know that `msg` is of the same type, use MustExecute.
This function is goroutine safe.
This will always return Results whose length is the number of FieldProcessors passed to NewDynamicWalker. Use Results.Empty to check if this contains any actionable data. It is not valid to mutate the return value (use Results.Clone if you need to do this).
func (*DynamicWalker) MustExecute ¶
func (l *DynamicWalker) MustExecute(msg proto.Message) Results
MustExecute is the same as Execute, except it will panic if the type of `msg` does not match the type that was given to NewDynamicWalker.
If you do not statically know that `msg` is of the same type, use Execute and handle the error.
type FieldProcessor ¶
type FieldProcessor interface { // ShouldProcess is called once per field descriptor in // [NewWalker]/[NewDynamicWalker]. // // Note that this is NOT called in [Walker.Execute]/[DynamicWalker.Execute]. // The return value of this is cached in the [Walker]/[DynamicWalker]. // // Returns an enum of how this processor wants to handle the provided field. ShouldProcess(field protoreflect.FieldDescriptor) ProcessAttr // Process is called when examining a message with [Walker.Execute] or // [DynamicWalker.Execute]. // // It will only be called on fields where ShouldProcess returned // a [ProcessAttr] value other than [ProcessNever]. // // Process will never be invoked for a field on a nil message. That is, // technically, someMessage.someField is 'unset', even if someMessage is nil. // Even if the FieldSelector returned ProccessUnset, it would still not be // called on someField. // // If `applied` == true, `data` will be included in the Results from // protowalk.Fields. // // It is allowed for Process to mutate the value of `field` in `msg`, but // mutating other fields is undefined behavior. // // When processing a given message, an instance of FieldProcessor will have // its Process method called sequentially per affected field, interspersed // with other FieldProcessors in the same Fields call. For example, if you // process a message with FieldProcessors A and B, where A processes evenly- // numbered fields, and B processes oddly-numbered fields, the calls would // look like: // * B.Process(1) // * A.Process(2) // * B.Process(3) // // If two processors apply to the same field in a message, they'll be called // in the order specified to Fields (i.e. NewWalker(..., A{}, B{}) would call A // then B, and NewWalker(..., B{}, A{}) would call B then A). Process(field protoreflect.FieldDescriptor, msg protoreflect.Message) (data ResultData, applied bool) }
FieldProcessor allows processing a set of proto message fields in conjunction with NewWalker.
Typically FieldProcessor implementations will apply to fields with particular annotations, but a FieldProcessor can technically react to any field(s) that it wants to.
type OutputOnlyProcessor ¶
type OutputOnlyProcessor struct{}
OutputOnlyProcessor implements the recommended behavior of aip.dev/203#output-only, namely that fields marked like:
type name = <tag> [(google.api.field_behavior) = OUTPUT_ONLY];
Should be cleared by the server (without raising an error) if a user request includes a value for them.
func (OutputOnlyProcessor) Process ¶
func (OutputOnlyProcessor) Process(field protoreflect.FieldDescriptor, msg protoreflect.Message) (data ResultData, applied bool)
Process implements FieldProcessor.
This will clear (zero) the field in `msg`.
func (OutputOnlyProcessor) ShouldProcess ¶
func (OutputOnlyProcessor) ShouldProcess(field protoreflect.FieldDescriptor) ProcessAttr
type ProcessAttr ¶
type ProcessAttr byte
ProcessAttr is a cache data value which indicates that this field needs to be processed, whether for set fields, unset fields, or both.
const ( // ProcessNever indicates that this processor doesn't want to process this // field. ProcessNever ProcessAttr = 0b00 // ProcessIfSet indicates that this processor only applies to this field when // the field has a value (i.e. protoreflect.Message.Has(field)). ProcessIfSet ProcessAttr = 0b01 // ProcessIfUnset indicates that this processor only applies to this field when // the field does not have a value (i.e. !protoreflect.Message.Has(field)). ProcessIfUnset ProcessAttr = 0b10 // ProcessAlways indicates that this processor always applies to this field // regardless of value status. ProcessAlways = ProcessIfSet | ProcessIfUnset )
func (ProcessAttr) Valid ¶
func (a ProcessAttr) Valid() bool
Valid returns true if `a` is a known ProcessAttr value.
type RequiredProcessor ¶
type RequiredProcessor struct{}
RequiredProcessor implements the recommended behavior of aip.dev/203#required, namely that it produces a Report for unset fields marked like:
type name = <tag> [(google.api.field_behavior) = REQUIRED];
NOTE: In LUCI code, it's recommended to use "protoc-gen-validate" (i.e. PGV) annotations to indicate required fields. The PGV generated code is easier to use and (likely) more efficient than the implementation in this package.
func (RequiredProcessor) Process ¶
func (RequiredProcessor) Process(field protoreflect.FieldDescriptor, msg protoreflect.Message) (data ResultData, applied bool)
Process implements FieldProcessor.
func (RequiredProcessor) ShouldProcess ¶
func (RequiredProcessor) ShouldProcess(field protoreflect.FieldDescriptor) ProcessAttr
type Result ¶
type Result struct { // Path is the path to the field where the FieldProcessor acted. Path reflectutil.Path Data ResultData }
Result is a singluar outcome from a FieldProcessor.Process which acted on a message field.
type ResultData ¶
type ResultData struct { // Message is a human-readable message from FieldProcessor.Process describing // the nature of the violation or correction (depends on the FieldProcessor // implementation). Message string // IsErr is a flag for if the processed result should be considered as an error. IsErr bool }
ResultData is the data produced from the FieldProcessor.
func (ResultData) String ¶
func (d ResultData) String() string
type Results ¶
type Results [][]Result
Results holds a slice of Result structs for each FieldProcessor passed in.
type Walker ¶
Walker is the type-safe interface for protowalk.
Construct one with NewWalker.
A zero-value Walker will always return nil Results.
func NewWalker ¶
func NewWalker[M proto.Message](processors ...FieldProcessor) Walker[M]
NewWalker returns a Walker[M] which can be executed against protos of type M and process them with the given `processors`.
Mutating `processors` after passing them in here is undefined - don't do it.
func (Walker[M]) Execute ¶
Execute evaluates `msg` in terms of all [FieldProcessor]s given when the Walker was constructed with NewWalker.
This function is goroutine safe.
This will always return Results whose length is the number of FieldProcessors passed to NewWalker. Use Results.Empty to check if this contains any actionable data. It is not valid to mutate the return value (use Results.Clone if you need to do this).