go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/proto/protowalk/field_processor.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package protowalk
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  
    21  	"google.golang.org/protobuf/reflect/protoreflect"
    22  )
    23  
    24  // FieldSelector is called once per field per message type per process and
    25  // the result is cached by the type name of this FieldProcessor (i.e.
    26  // reflect.TypeOf to observe the package and local type name of the processor)
    27  // and the full proto message name.
    28  //
    29  // Returns an enum of how this processor wants to handle the provided field.
    30  //
    31  // This function is registered with a corresponding FieldProcessor in
    32  // RegisterFieldProcessor.
    33  type FieldSelector func(field protoreflect.FieldDescriptor) ProcessAttr
    34  
    35  // RegisterFieldProcessor registers a new FieldProcessor to allow it to be used
    36  // with protowalk.Fields.
    37  //
    38  // This should be called once per FieldProcessor, per process like:
    39  //
    40  //	func init() {
    41  //	  protowalk.RegisterFieldProcessor(&MyFP{}, MyFPFieldSelector)
    42  //	}
    43  //
    44  // Calling RegisterFieldProcessor twice for the same FieldProcessor will panic.
    45  func RegisterFieldProcessor(fp FieldProcessor, selector FieldSelector) {
    46  	fieldProcessorSelectorsMu.Lock()
    47  	defer fieldProcessorSelectorsMu.Unlock()
    48  
    49  	t := reflect.TypeOf(fp)
    50  	if fieldProcessorSelectors == nil {
    51  		fieldProcessorSelectors = make(map[reflect.Type]FieldSelector, 10)
    52  	}
    53  	if fieldProcessorSelectors[t] != nil {
    54  		panic(fmt.Sprintf("FieldProcessor %T already registered", fp))
    55  	}
    56  	fieldProcessorSelectors[t] = selector
    57  }
    58  
    59  // FieldProcessor allows processing a set of proto message fields in conjunction
    60  // with the package-level Fields() function.
    61  //
    62  // Typically FieldProcessor implementations will apply to fields with particular
    63  // annotations, but a FieldProcessor can technically react to any field(s) that
    64  // it wants to.
    65  type FieldProcessor interface {
    66  	// Process will only be called on fields where the registered FieldSelector
    67  	// function already returned a non-zero ProcessAttr value.
    68  	//
    69  	// Process will never be invoked for a field on a nil message. That is,
    70  	// technically, someMessage.someField is 'unset', even if someMessage is nil.
    71  	// Even if the FieldSelector returned ProccessUnset, it would still not be
    72  	// called on someField.
    73  	//
    74  	// If `applied` == true, `data` will be included in the Results from
    75  	// protowalk.Fields.
    76  	//
    77  	// It is allowed for Process to mutate the value of `field` in `msg`, but
    78  	// mutating other fields is undefined behavior.
    79  	//
    80  	// When processing a given message, an instance of FieldProcessor will have
    81  	// its Process method called sequentially per affected field, interspersed
    82  	// with other FieldProcessors in the same Fields call. For example, if you
    83  	// process a message with FieldProcessors A and B, where A processes evenly-
    84  	// numbered fields, and B processes oddly-numbered fields, the calls would
    85  	// look like:
    86  	//   * B.Process(1)
    87  	//   * A.Process(2)
    88  	//   * B.Process(3)
    89  	//
    90  	// If two processors apply to the same field in a message, they'll be called
    91  	// in the order specified to Fields (i.e. Fields(..., A{}, B{}) would call AS
    92  	// then B, and Fields(..., B{}, A{}) would call B then A).
    93  	Process(field protoreflect.FieldDescriptor, msg protoreflect.Message) (data ResultData, applied bool)
    94  }