Skip to content

Commit

Permalink
Added no ambiguous paths function.
Browse files Browse the repository at this point in the history
Signed-off-by: Dave Shanley <dave@quobix.com>
  • Loading branch information
daveshanley committed Jul 16, 2022
1 parent abc1c11 commit de40c2b
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
94 changes: 94 additions & 0 deletions functions/openapi/no_ambiguous_paths.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2022 Dave Shanley / Quobix
// SPDX-License-Identifier: MIT

package openapi

import (
"fmt"
"github.com/daveshanley/vacuum/model"
"gopkg.in/yaml.v3"
"regexp"
"strings"
)

// AmbiguousPaths will determine if paths can be confused by a compiler.
type AmbiguousPaths struct {
}

// GetSchema returns a model.RuleFunctionSchema defining the schema of the AmbiguousPaths rule.
func (ap AmbiguousPaths) GetSchema() model.RuleFunctionSchema {
return model.RuleFunctionSchema{Name: "ambiguousPaths"}
}

// RunRule will execute the AmbiguousPaths rule, based on supplied context and a supplied []*yaml.Node slice.
func (ap AmbiguousPaths) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []model.RuleFunctionResult {

if len(nodes) <= 0 {
return nil
}

var results []model.RuleFunctionResult
var seen []string

ops := context.Index.GetPathsNode()

var opPath string

if ops != nil {
for i, op := range ops.Content {
if i%2 == 0 {
opPath = op.Value
continue
}
path := fmt.Sprintf("$.paths.%s", opPath)
for _, p := range seen {
ambigious := checkPaths(p, opPath)
if ambigious {

results = append(results, model.RuleFunctionResult{
Message: fmt.Sprintf("Paths are ambiguous with one another: `%s` and `%s`", p, opPath),
StartNode: op,
EndNode: op,
Path: path,
Rule: context.Rule,
})

}
}
seen = append(seen, opPath)

}
}
return results
}

func checkPaths(pA, pB string) bool {
segsA := strings.Split(pA, "/")[1:]
segsB := strings.Split(pB, "/")[1:]

if len(segsA) != len(segsB) {
return false
}

a := 0
b := 0
amb := true
for i, part := range segsA {
aVar, _ := regexp.MatchString("^{.+?}$", part)
bVar, _ := regexp.MatchString("^{.+?}$", segsB[i])
if aVar || bVar {
if aVar {
a++
}
if bVar {
b++
}
continue
} else {
if segsA[i] != segsB[i] {
amb = false
}
}
}
return amb && a == b
}
73 changes: 73 additions & 0 deletions functions/openapi/no_ambiguous_paths_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package openapi

import (
"github.com/daveshanley/vacuum/model"
"github.com/daveshanley/vacuum/utils"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3"
"testing"
)

func TestNoAmbiguousPaths_GetSchema(t *testing.T) {
def := AmbiguousPaths{}
assert.Equal(t, "ambiguousPaths", def.GetSchema().Name)
}

func TestNoAmbiguousPaths_RunRule(t *testing.T) {
def := AmbiguousPaths{}
res := def.RunRule(nil, model.RuleFunctionContext{})
assert.Len(t, res, 0)
}

func TestAmbiguousPaths_RunRule_SuccessCheck(t *testing.T) {

yml := `openapi: 3.0.0
paths:
'/good/{id}':
get:
summary: List all pets
'/good/last':
get:
summary: List all pets
'/good/{id}/{pet}':
get:
summary: List all pets
'/good/last/{id}':
get:
summary: List all pets
'/{id}/ambiguous':
get:
summary: List all pets
'/ambiguous/{id}':
get:
summary: List all pets
'/pet/last':
get:
summary: List all pets
'/pet/first':
get:
summary: List all pets
'/{entity}/{id}/last':
get:
summary: List all pets
'/pet/first/{id}':
get:
summary: List all pets`

path := "$"

var rootNode yaml.Node
yaml.Unmarshal([]byte(yml), &rootNode)

nodes, _ := utils.FindNodes([]byte(yml), path)

rule := buildOpenApiTestRuleAction(path, "ambiguousPaths", "", nil)
ctx := buildOpenApiTestContext(model.CastToRuleAction(rule.Then), nil)
ctx.Rule = &rule
ctx.Index = model.NewSpecIndex(&rootNode)

def := AmbiguousPaths{}
res := def.RunRule(nodes, ctx)

assert.Len(t, res, 3)
}

0 comments on commit de40c2b

Please sign in to comment.