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

     1  // Copyright 2022 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 aip0127
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/googleapis/api-linter/rules/internal/testutils"
    21  )
    22  
    23  func TestHttpTemplateSyntax(t *testing.T) {
    24  	tests := []struct {
    25  		testName string
    26  		URI      string
    27  		valid    bool
    28  	}{
    29  		// NOTE: These examples are contrived to test the enforcment of the
    30  		// template syntax. Many of these examples either fail or do not make
    31  		// sense in the context of other AIP rules.
    32  
    33  		// Valid cases
    34  		{"SingleLiteral", "/v1", true},
    35  		{"VersionedTemplate", "/{$api_version}/books", true},
    36  		{"TwoLiterals", "/v1/books", true},
    37  		{"ThreeLiterals", "/v1/books/shelves", true},
    38  		{"SingleLiteralWithVerb", "/v1:verb", true},
    39  		{"MultipleLiteralsWithVerb", "/v1/books:verb", true},
    40  		{"SingleWildcard", "/v1/*", true},
    41  		{"DoubleWildcard", "/v1/**", true},
    42  		{"SingleWildcardWithVerb", "/v1/*:verb", true},
    43  		{"DoubleWildcardWithVerb", "/v1/**:verb", true},
    44  		{"SingleWildcardFollowedByLiteral", "/v1/*/books", true},
    45  		{"DoubleWildcardFollowedByLiteral", "/v1/**/books", true},
    46  		{"LiteralFollowedBySingleWildcard", "/v1/books/*", true},
    47  		{"LiteralFollowedByDoubleWildcard", "/v1/books/**", true},
    48  		{"VariableWithFieldpath", "/v1/{field}", true},
    49  		{"VariableWithNestedFieldpath", "/v1/{field.subfield}", true},
    50  		{"VariableWithUltraNestedFieldpath", "/v1/{field.subfield.subsubfield}", true},
    51  		{"VariableWithLiteralTemplate", "/v1/{field=books}", true},
    52  		{"VariableWithSingleWildcardTemplate", "/v1/{field=*}", true},
    53  		{"VariableWithDoubleWildcardTemplate", "/v1/{field=**}", true},
    54  		{"VariableWithSingleWildcardFollowedByLiteral", "/v1/{field=*/books}", true},
    55  		{"VariableWithDoubleWildcardFollowedByLiteral", "/v1/{field=**/books}", true},
    56  		{"VariableWithLiteralFollowedBySingleWildcard", "/v1/{field=books/*}", true},
    57  		{"VariableWithLiteralFollowedByDoubleWildcard", "/v1/{field=books/**}", true},
    58  		{"VariableFollowedByLiteral", "/v1/{field}/books", true},
    59  		{"VariableFollowedByVariable", "/v1/{field}/{otherField}", true},
    60  		{"VariableWithTemplateFollowedByLiteral", "/v1/{field=books/*}/shelves", true},
    61  		{"VariableFollowedByVariableWithTemplate", "/v1/{field}/{otherField=books/*}", true},
    62  		{"VariableWithTemplateFollowedByVariableWithTemplate", "/v1/{field=books/*}/{otherField=shelves/*}", true},
    63  
    64  		// Invalid cases
    65  		{"LiteralWithoutLeadingSlash", "v1", false},
    66  		{"LiteralFollowedBySlash", "/v1/", false},
    67  		{"WrongVerbDelimiter", "/v1-verb", false},
    68  		{"MultipleVerbs", "/v1:verb:verb", false},
    69  		{"VerbFollowedBySlash", "/v1:verb/", false},
    70  		{"MultipleLiteralsWithWrongDelimiter", "/v1|books", false},
    71  		{"SingleWildcardFollowedBySlash", "/v1/*/", false},
    72  		{"DoubleWildcardFollowedBySlash", "/v1/**/", false},
    73  		{"TripleWildcard", "/v1/***", false},
    74  		{"WrongVariableMarker", "/v1/[field]", false},
    75  		{"WrongVariableSubfieldOperator", "/v1/[field->subfield]", false},
    76  		{"VariableTemplateContainsVariable", "/v1/{field={otherField=*}}", false},
    77  		{"WrongVariableTemplateAssignmentOperator", "/v1/{field≈books}", false},
    78  	}
    79  	for _, test := range tests {
    80  		t.Run(test.testName, func(t *testing.T) {
    81  			file := testutils.ParseProto3Tmpl(t, `
    82  				import "google/api/annotations.proto";
    83  				service Library {
    84  					rpc FooMethod(FooMethodRequest) returns (FooMethodResponse) {
    85  						option (google.api.http) = {
    86  							get: "{{.URI}}"
    87  						};
    88  					}
    89  				}
    90  				message FooMethodRequest {}
    91  				message FooMethodResponse {}
    92  			`, test)
    93  
    94  			problems := httpTemplateSyntax.Lint(file)
    95  
    96  			if test.valid && len(problems) > 0 {
    97  				t.Fatalf("Expected valid HTTP path template syntax but got invalid")
    98  			}
    99  
   100  			if !test.valid && len(problems) == 0 {
   101  				t.Fatalf("Expected invalid HTTP path template syntax but got valid")
   102  			}
   103  		})
   104  	}
   105  }