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

     1  // Copyright 2019 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 aip0123 contains rules defined in https://aip.dev/123.
    16  package aip0123
    17  
    18  import (
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  
    23  	"github.com/googleapis/api-linter/lint"
    24  	"github.com/googleapis/api-linter/rules/internal/utils"
    25  	"github.com/jhump/protoreflect/desc"
    26  	"github.com/stoewer/go-strcase"
    27  )
    28  
    29  // AddRules accepts a register function and registers each of
    30  // this AIP's rules to it.
    31  func AddRules(r lint.RuleRegistry) error {
    32  	return r.Register(
    33  		123,
    34  		duplicateResource,
    35  		resourceAnnotation,
    36  		resourceNameComponentsAlternate,
    37  		resourceNameField,
    38  		resourcePattern,
    39  		resourcePlural,
    40  		resourceReferenceType,
    41  		resourceSingular,
    42  		resourceTypeName,
    43  		resourceVariables,
    44  		resourceDefinitionVariables,
    45  		resourceDefinitionPatterns,
    46  		resourceDefinitionTypeName,
    47  		nameNeverOptional,
    48  	)
    49  }
    50  
    51  func isResourceMessage(m *desc.MessageDescriptor) bool {
    52  	// If the parent of this message is a message, it is nested and shoudn't
    53  	// be considered a resource, even if it has a name field.
    54  	_, nested := m.GetParent().(*desc.MessageDescriptor)
    55  	return m.FindFieldByName("name") != nil && !strings.HasSuffix(m.GetName(), "Request") &&
    56  		!strings.HasSuffix(m.GetName(), "Response") && !nested
    57  }
    58  
    59  func hasResourceAnnotation(m *desc.MessageDescriptor) bool {
    60  	return utils.GetResource(m) != nil
    61  }
    62  
    63  func hasResourceDefinitionAnnotation(f *desc.FileDescriptor) bool {
    64  	return len(utils.GetResourceDefinitions(f)) > 0
    65  }
    66  
    67  // getVariables returns a slice of variables declared in the pattern.
    68  //
    69  // For example, a pattern of "publishers/{publisher}/books/{book}" would
    70  // return []string{"publisher", "book"}.
    71  func getVariables(pattern string) []string {
    72  	answer := []string{}
    73  	for _, match := range varRegexp.FindAllStringSubmatch(pattern, -1) {
    74  		answer = append(answer, match[1])
    75  	}
    76  	return answer
    77  }
    78  
    79  // getPlainPattern returns the pattern with all variables replaced with "*".
    80  //
    81  // For example, a pattern of "publishers/{publisher}/books/{book}" would
    82  // return "publishers/*/books/*".
    83  func getPlainPattern(pattern string) string {
    84  	return varRegexp.ReplaceAllLiteralString(pattern, "*")
    85  }
    86  
    87  // getDesiredPattern returns the expected desired pattern, with errors we
    88  // lint for corrected.
    89  func getDesiredPattern(pattern string) string {
    90  	want := []string{}
    91  	for _, token := range strings.Split(pattern, "/") {
    92  		if strings.HasPrefix(token, "{") && strings.HasSuffix(token, "}") {
    93  			varname := token[1 : len(token)-1]
    94  			want = append(want, fmt.Sprintf("{%s}", strings.TrimSuffix(strcase.SnakeCase(varname), "_id")))
    95  		} else {
    96  			want = append(want, strcase.LowerCamelCase(token))
    97  		}
    98  	}
    99  	return strings.Join(want, "/")
   100  }
   101  
   102  var varRegexp = regexp.MustCompile(`\{([^}=]+)}`)