github.com/googleapis/api-linter@v1.65.2/rules/aip0203/field_behavior_required.go (about)

     1  // Copyright 2023 Google LLC
     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  //	https://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 aip0203
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"bitbucket.org/creachadair/stringset"
    21  	"github.com/googleapis/api-linter/lint"
    22  	"github.com/googleapis/api-linter/rules/internal/utils"
    23  	"github.com/jhump/protoreflect/desc"
    24  )
    25  
    26  var minimumRequiredFieldBehavior = stringset.New(
    27  	"OPTIONAL", "REQUIRED", "OUTPUT_ONLY", "IMMUTABLE",
    28  )
    29  
    30  var excusedResourceFields = stringset.New(
    31  	"name", // Uses https://google.aip.dev/203#identifier
    32  	"etag", // Prohibited by https://google.aip.dev/154
    33  )
    34  
    35  var fieldBehaviorRequired = &lint.MethodRule{
    36  	Name: lint.NewRuleName(203, "field-behavior-required"),
    37  	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
    38  		req := m.GetInputType()
    39  		p := m.GetFile().GetPackage()
    40  		ps := problems(req, p, map[desc.Descriptor]bool{})
    41  		if len(ps) == 0 {
    42  			return nil
    43  		}
    44  
    45  		return ps
    46  	},
    47  }
    48  
    49  func problems(m *desc.MessageDescriptor, pkg string, visited map[desc.Descriptor]bool) []lint.Problem {
    50  	var ps []lint.Problem
    51  
    52  	for _, f := range m.GetFields() {
    53  		// ignore the field if it was already visited
    54  		if ok := visited[f]; ok {
    55  			continue
    56  		}
    57  		visited[f] = true
    58  
    59  		if utils.IsResource(m) && excusedResourceFields.Contains(f.GetName()) {
    60  			continue
    61  		}
    62  
    63  		// Ignore a field if it is a OneOf (do not ignore children)
    64  		if f.AsFieldDescriptorProto().OneofIndex == nil {
    65  			p := checkFieldBehavior(f)
    66  			if p != nil {
    67  				ps = append(ps, *p)
    68  			}
    69  		}
    70  
    71  		if mt := f.GetMessageType(); mt != nil && !mt.IsMapEntry() && mt.GetFile().GetPackage() == pkg {
    72  			ps = append(ps, problems(mt, pkg, visited)...)
    73  		}
    74  	}
    75  
    76  	return ps
    77  }
    78  
    79  func checkFieldBehavior(f *desc.FieldDescriptor) *lint.Problem {
    80  	fb := utils.GetFieldBehavior(f)
    81  
    82  	if len(fb) == 0 {
    83  		return &lint.Problem{
    84  			Message:    fmt.Sprintf("google.api.field_behavior annotation must be set on %q and contain one of, \"%v\"", f.GetName(), minimumRequiredFieldBehavior),
    85  			Descriptor: f,
    86  		}
    87  	}
    88  
    89  	if !minimumRequiredFieldBehavior.Intersects(fb) {
    90  		// check for at least one valid annotation
    91  		return &lint.Problem{
    92  			Message: fmt.Sprintf(
    93  				"google.api.field_behavior on field %q must contain at least one, \"%v\"", f.GetName(), minimumRequiredFieldBehavior),
    94  			Descriptor: f,
    95  		}
    96  	}
    97  
    98  	return nil
    99  }