Skip to content

Commit

Permalink
Merge pull request #86 from daveshanley/buffing
Browse files Browse the repository at this point in the history
A collection of new rules and tuning.
  • Loading branch information
daveshanley committed Jul 16, 2022
2 parents abc1c11 + 15be0ce commit c3171be
Show file tree
Hide file tree
Showing 23 changed files with 88,365 additions and 2,419 deletions.
1 change: 1 addition & 0 deletions functions/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func MapBuiltinFunctions() Functions {
funcs["oasPolymorphicOneOf"] = openapi_functions.PolymorphicOneOf{}
funcs["oasDocumentSchema"] = openapi_functions.OASSchema{}
funcs["oasAPIServers"] = openapi_functions.APIServers{}
funcs["noAmbiguousPaths"] = openapi_functions.AmbiguousPaths{}

})

Expand Down
2 changes: 1 addition & 1 deletion functions/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import (

func TestMapBuiltinFunctions(t *testing.T) {
funcs := MapBuiltinFunctions()
assert.Len(t, funcs.GetAllFunctions(), 38)
assert.Len(t, funcs.GetAllFunctions(), 39)
}
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)
}
55 changes: 38 additions & 17 deletions functions/openapi/oas_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package openapi
import (
"fmt"
"github.com/daveshanley/vacuum/model"
"github.com/santhosh-tekuri/jsonschema/v5"
"github.com/xeipuuv/gojsonschema"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -41,29 +42,49 @@ func (os OASSchema) RunRule(nodes []*yaml.Node, context model.RuleFunctionContex
return results
}

specBytes := *info.SpecJSONBytes
// Swagger specs are not supported with this schema checker (annoying, but you get what you pay for).
schema, err := jsonschema.CompileString("schema.json", info.APISchema)
if err != nil {

// create loader from original bytes.
doc := gojsonschema.NewStringLoader(string(specBytes))
// do the swagger thing.
swaggerSchema := gojsonschema.NewStringLoader(info.APISchema)
spec := gojsonschema.NewStringLoader(string(*info.SpecJSONBytes))
res, validateErr := gojsonschema.Validate(swaggerSchema, spec)

res, err := gojsonschema.Validate(info.APISchema, doc)
if validateErr != nil {
results = append(results, model.RuleFunctionResult{
Message: fmt.Sprintf("OpenAPI specification cannot be validated: %v", validateErr.Error()),
StartNode: nodes[0],
EndNode: nodes[0],
Path: "$",
Rule: context.Rule,
})
return results
}

if err != nil {
results = append(results, model.RuleFunctionResult{
Message: fmt.Sprintf("OpenAPI specification cannot be validated: %s", err.Error()),
StartNode: nodes[0],
EndNode: nodes[0],
Path: "$",
Rule: context.Rule,
})
return results
// if the spec is not valid, run through all the issues and return.
if !res.Valid() {
for _, resErr := range res.Errors() {
results = append(results, model.RuleFunctionResult{
Message: fmt.Sprintf("OpenAPI specification is invalid: %s", resErr.Description()),
StartNode: nodes[0],
EndNode: nodes[0],
Path: "$",
Rule: context.Rule,
})
}
return results
}
return nil
}

// if the spec is not valid, run through all the issues and return.
if !res.Valid() {
for _, err := range res.Errors() {
//validate using faster, more accurate resolver.
if validationError := schema.Validate(*info.SpecJSON); validationError != nil {
failure := validationError.(*jsonschema.ValidationError)
for _, fail := range failure.Causes {
results = append(results, model.RuleFunctionResult{
Message: fmt.Sprintf("OpenAPI specification is invalid: %s", err.Description()),
Message: fmt.Sprintf("OpenAPI specification is invalid: %s %v", fail.KeywordLocation,
fail.Message),
StartNode: nodes[0],
EndNode: nodes[0],
Path: "$",
Expand Down
53 changes: 53 additions & 0 deletions functions/openapi/oas_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,59 @@ paths:
assert.Len(t, res, 1)
}

func TestOAS3Schema_RunRule_Pass(t *testing.T) {

yml := `openapi: "3.0.0"
info:
contact:
name: Hi
url: https://quobix.com/vacuum
license:
name: MIT
termsOfService: https://quobix.com/vacuum
title: Quobix
version: "1.0"
paths:
/hi:
get:
responses:
"200":
description: OK`

path := "$"

specInfo, _ := model.ExtractSpecInfo([]byte(yml))

rule := buildOpenApiTestRuleAction(path, "oas3_schema", "", nil)
ctx := buildOpenApiTestContext(model.CastToRuleAction(rule.Then), nil)
ctx.Index = model.NewSpecIndex(specInfo.RootNode)
ctx.SpecInfo = specInfo

def := OASSchema{}
res := def.RunRule([]*yaml.Node{specInfo.RootNode}, ctx)

assert.Len(t, res, 0)
}

func TestOAS3Schema_RunRule_Fail(t *testing.T) {

yml := `openapi: "3.0"`

path := "$"

specInfo, _ := model.ExtractSpecInfo([]byte(yml))

rule := buildOpenApiTestRuleAction(path, "oas3_schema", "", nil)
ctx := buildOpenApiTestContext(model.CastToRuleAction(rule.Then), nil)
ctx.Index = model.NewSpecIndex(specInfo.RootNode)
ctx.SpecInfo = specInfo

def := OASSchema{}
res := def.RunRule([]*yaml.Node{specInfo.RootNode}, ctx)

assert.Len(t, res, 2)
}

func TestOAS2Schema_RunRule_Success(t *testing.T) {

yml := `swagger: '2.0'
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/json-iterator/go v1.1.12
github.com/mitchellh/mapstructure v1.5.0
github.com/pterm/pterm v0.12.42
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.0
github.com/vmware-labs/yaml-jsonpath v0.3.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ github.com/pterm/pterm v0.12.42/go.mod h1:hJgLlBafm45w/Hr0dKXxY//POD7CgowhePaG1s
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
Expand Down
Loading

0 comments on commit c3171be

Please sign in to comment.