diff --git a/cmd/html_report.go b/cmd/html_report.go
index 9c3e5954..a4d09882 100644
--- a/cmd/html_report.go
+++ b/cmd/html_report.go
@@ -131,7 +131,7 @@ func GetHTMLReportCommand() *cobra.Command {
pterm.Println()
fi, _ := os.Stat(args[0])
- RenderTime(timeFlag, duration, fi)
+ RenderTime(timeFlag, duration, fi.Size())
return nil
},
diff --git a/cmd/lint.go b/cmd/lint.go
index 456fa72b..3d0de30a 100644
--- a/cmd/lint.go
+++ b/cmd/lint.go
@@ -12,6 +12,7 @@ import (
"github.com/dustin/go-humanize"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
+ "log/slog"
"os"
"path/filepath"
"strconv"
@@ -73,7 +74,12 @@ func GetLintCommand() *cobra.Command {
mf = true
}
- defaultRuleSets := rulesets.BuildDefaultRuleSets()
+ // setup logging
+ handler := pterm.NewSlogHandler(&pterm.DefaultLogger)
+ pterm.DefaultLogger.Level = pterm.LogLevelError
+ logger := slog.New(handler)
+
+ defaultRuleSets := rulesets.BuildDefaultRuleSetsWithLogger(logger)
selectedRS := defaultRuleSets.GenerateOpenAPIRecommendedRuleSet()
customFunctions, _ := LoadCustomFunctions(functionsFlag)
@@ -110,10 +116,18 @@ func GetLintCommand() *cobra.Command {
pterm.Println()
}
+ start := time.Now()
+ var size int64
for i, arg := range args {
go func(c chan bool, i int, arg string) {
+ // get size
+ s, _ := os.Stat(arg)
+ if s != nil {
+ size = size + s.Size()
+ }
+
lfr := lintFileRequest{
fileName: arg,
baseFlag: baseFlag,
@@ -132,6 +146,7 @@ func GetLintCommand() *cobra.Command {
selectedRS: selectedRS,
functions: customFunctions,
lock: &printLock,
+ logger: logger,
}
errs = append(errs, lintFile(lfr))
doneChan <- true
@@ -150,6 +165,10 @@ func GetLintCommand() *cobra.Command {
pterm.Println()
}
+ duration := time.Since(start)
+
+ RenderTime(timeFlag, duration, size)
+
if len(errs) > 0 {
return errors.Join(errs...)
}
@@ -210,6 +229,7 @@ type lintFileRequest struct {
selectedRS *rulesets.RuleSet
functions map[string]model.RuleFunction
lock *sync.Mutex
+ logger *slog.Logger
}
func lintFile(req lintFileRequest) error {
@@ -227,7 +247,6 @@ func lintFile(req lintFileRequest) error {
}
- start := time.Now()
result := motor.ApplyRulesToRuleSet(&motor.RuleSetExecution{
RuleSet: req.selectedRS,
Spec: specBytes,
@@ -235,13 +254,14 @@ func lintFile(req lintFileRequest) error {
Base: req.baseFlag,
AllowLookup: true,
SkipDocumentCheck: req.skipCheckFlag,
+ Logger: req.logger,
})
results := result.Results
if len(result.Errors) > 0 {
for _, err := range result.Errors {
- pterm.Error.Printf("linting error: %s", err.Error())
+ pterm.Error.Printf("unable to process spec '%s', error: %s", req.fileName, err.Error())
pterm.Println()
}
return fmt.Errorf("linting failed due to %d issues", len(result.Errors))
@@ -249,9 +269,6 @@ func lintFile(req lintFileRequest) error {
resultSet := model.NewRuleResultSet(results)
resultSet.SortResultsByLineNumber()
- fi, _ := os.Stat(req.fileName)
- duration := time.Since(start)
-
warnings := resultSet.GetWarnCount()
errs := resultSet.GetErrorCount()
informs := resultSet.GetInfoCount()
@@ -259,7 +276,6 @@ func lintFile(req lintFileRequest) error {
defer req.lock.Unlock()
if !req.detailsFlag {
RenderSummary(resultSet, req.silent, req.totalFiles, req.fileIndex, req.fileName, req.failSeverityFlag)
- RenderTime(req.timeFlag, duration, fi)
return CheckFailureSeverity(req.failSeverityFlag, errs, warnings, informs)
}
@@ -302,13 +318,13 @@ func lintFile(req lintFileRequest) error {
}
RenderSummary(resultSet, req.silent, req.totalFiles, req.fileIndex, req.fileName, req.failSeverityFlag)
- RenderTime(req.timeFlag, duration, fi)
+
return CheckFailureSeverity(req.failSeverityFlag, errs, warnings, informs)
}
func processResults(results []*model.RuleFunctionResult, specData []string, snippets, errors bool, silent bool, abs, filename string) {
- pterm.Println(pterm.LightMagenta(fmt.Sprintf("%s", abs)))
+ pterm.Println(pterm.LightMagenta(fmt.Sprintf("\n%s", abs)))
underline := make([]string, len(abs))
for x, _ := range abs {
underline[x] = "-"
diff --git a/cmd/shared_functions.go b/cmd/shared_functions.go
index 3216c908..34022ee7 100644
--- a/cmd/shared_functions.go
+++ b/cmd/shared_functions.go
@@ -9,7 +9,6 @@ import (
"github.com/daveshanley/vacuum/plugin"
"github.com/daveshanley/vacuum/rulesets"
"github.com/pterm/pterm"
- "os"
"time"
)
@@ -29,10 +28,14 @@ func BuildRuleSetFromUserSuppliedSet(rsBytes []byte, rs rulesets.RuleSets) (*rul
}
// RenderTime will render out the time taken to process a specification, and the size of the file in kb.
-func RenderTime(timeFlag bool, duration time.Duration, fi os.FileInfo) {
+func RenderTime(timeFlag bool, duration time.Duration, fi int64) {
if timeFlag {
pterm.Println()
- pterm.Info.Println(fmt.Sprintf("vacuum took %d milliseconds to lint %dkb", duration.Milliseconds(), fi.Size()/1000))
+ if (fi / 1000) <= 1024 {
+ pterm.Info.Println(fmt.Sprintf("vacuum took %d milliseconds to lint %dkb", duration.Milliseconds(), fi/1000))
+ } else {
+ pterm.Info.Println(fmt.Sprintf("vacuum took %d milliseconds to lint %dmb", duration.Milliseconds(), fi/1000000))
+ }
pterm.Println()
}
}
diff --git a/cmd/shared_functions_test.go b/cmd/shared_functions_test.go
index 9596ec59..4f678187 100644
--- a/cmd/shared_functions_test.go
+++ b/cmd/shared_functions_test.go
@@ -10,5 +10,5 @@ func TestRenderTime(t *testing.T) {
start := time.Now()
time.Sleep(1 * time.Millisecond)
fi, _ := os.Stat("shared_functions.go")
- RenderTime(true, time.Since(start), fi)
+ RenderTime(true, time.Since(start), fi.Size())
}
diff --git a/cmd/spectral_report.go b/cmd/spectral_report.go
index 7678ffce..67060580 100644
--- a/cmd/spectral_report.go
+++ b/cmd/spectral_report.go
@@ -176,7 +176,7 @@ func GetSpectralReportCommand() *cobra.Command {
pterm.Println()
fi, _ := os.Stat(args[0])
- RenderTime(timeFlag, duration, fi)
+ RenderTime(timeFlag, duration, fi.Size())
return nil
},
diff --git a/cmd/vacuum_report.go b/cmd/vacuum_report.go
index 9b211822..41442e3c 100644
--- a/cmd/vacuum_report.go
+++ b/cmd/vacuum_report.go
@@ -229,7 +229,7 @@ func GetVacuumReportCommand() *cobra.Command {
pterm.Println()
fi, _ := os.Stat(args[0])
- RenderTime(timeFlag, duration, fi)
+ RenderTime(timeFlag, duration, fi.Size())
return nil
},
diff --git a/functions/core/casing.go b/functions/core/casing.go
index b1451e02..db34c39d 100644
--- a/functions/core/casing.go
+++ b/functions/core/casing.go
@@ -145,7 +145,9 @@ func (c Casing) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) [
rx := regexp.MustCompile(fmt.Sprintf("^%s$", pattern))
node := nodes[0]
if utils.IsNodeMap(nodes[0]) || utils.IsNodeArray(nodes[0]) {
- node = nodes[0].Content[0]
+ if len(nodes[0].Content) > 0 {
+ node = nodes[0].Content[0]
+ }
}
if !rx.MatchString(node.Value) {
diff --git a/functions/core/schema.go b/functions/core/schema.go
index d8084baa..c5f2d330 100644
--- a/functions/core/schema.go
+++ b/functions/core/schema.go
@@ -94,7 +94,7 @@ func (sch Schema) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext)
forceValidationOnCurrentNode := utils.ExtractValueFromInterfaceMap("forceValidationOnCurrentNode", context.Options)
if _, ok := forceValidationOnCurrentNode.(bool); ok && len(nodes) > 0 {
schema.GoLow().Index = context.Index
- results = append(results, validateNodeAgainstSchema(schema, nodes[0], context, 0)...)
+ results = append(results, validateNodeAgainstSchema(&context, schema, nodes[0], context, 0)...)
return results
}
@@ -114,7 +114,7 @@ func (sch Schema) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext)
_, field := utils.FindKeyNodeTop(context.RuleAction.Field, no)
if field != nil {
schema.GoLow().Index = context.Index
- results = append(results, validateNodeAgainstSchema(schema, field, context, x)...)
+ results = append(results, validateNodeAgainstSchema(&context, schema, field, context, x)...)
} else {
// If the field is not found, and we're being strict, it's invalid.
@@ -138,7 +138,7 @@ func (sch Schema) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext)
var bannedErrors = []string{"if-then failed", "if-else failed", "allOf failed", "oneOf failed"}
-func validateNodeAgainstSchema(schema *highBase.Schema, field *yaml.Node,
+func validateNodeAgainstSchema(ctx *model.RuleFunctionContext, schema *highBase.Schema, field *yaml.Node,
context model.RuleFunctionContext, x int) []model.RuleFunctionResult {
ruleMessage := context.Rule.Description
@@ -149,7 +149,7 @@ func validateNodeAgainstSchema(schema *highBase.Schema, field *yaml.Node,
var results []model.RuleFunctionResult
// validate using schema provided.
- res, resErrors := parser.ValidateNodeAgainstSchema(schema, field, false)
+ res, resErrors := parser.ValidateNodeAgainstSchema(ctx, schema, field, false)
if res {
return results
diff --git a/functions/openapi/examples.go b/functions/openapi/examples.go
index 5d8be629..e56fd43b 100644
--- a/functions/openapi/examples.go
+++ b/functions/openapi/examples.go
@@ -169,7 +169,7 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext
}
}
- ctxTimeout, cancel := ctx.WithTimeout(ctx.Background(), time.Second*2)
+ ctxTimeout, cancel := ctx.WithTimeout(ctx.Background(), time.Second*1)
defer cancel()
f := make(chan bool)
go func() {
@@ -192,8 +192,8 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext
select {
case <-ctxTimeout.Done():
- pterm.Warning.Println("examples rule timed-out after two seconds, trying to scan examples for " +
- "response bodies, request bodies, and parameters")
+ pterm.Warning.Println("bug: examples function timed-out after a second, trying to scan examples for " +
+ "response bodies, request bodies, and parameters. Disable rules that use the example function checking for this spec. Please report this!")
return *results
case <-f:
// ok
@@ -325,10 +325,10 @@ func checkDefinitionForExample(componentNode *yaml.Node, compName string,
}
if schema != nil && schema.Type != nil && isArr && exValue != nil {
- res, errs = parser.ValidateNodeAgainstSchema(schema, exValue, true)
+ res, errs = parser.ValidateNodeAgainstSchema(&context, schema, exValue, true)
}
if schema != nil && schema.Type != nil && !isArr && exValue != nil {
- res, errs = parser.ValidateNodeAgainstSchema(schema, exValue, false)
+ res, errs = parser.ValidateNodeAgainstSchema(&context, schema, exValue, false)
}
// TODO: handle enums in here.
@@ -375,7 +375,7 @@ func checkDefinitionForExample(componentNode *yaml.Node, compName string,
var errorResults []*validationErrors.ValidationError
if topExValue != nil {
- _, errorResults = parser.ValidateNodeAgainstSchema(schema, topExValue, false)
+ _, errorResults = parser.ValidateNodeAgainstSchema(&context, schema, topExValue, false)
}
// extract all validation errors.
@@ -524,7 +524,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str
}
- res, errs := parser.ValidateNodeAgainstSchema(convertedSchema, valueNode, false)
+ res, errs := parser.ValidateNodeAgainstSchema(&context, convertedSchema, valueNode, false)
if !res {
// extract all validation errors.
@@ -606,7 +606,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str
//return results
- res, validateError := parser.ValidateNodeAgainstSchema(schema, eValue, false)
+ res, validateError := parser.ValidateNodeAgainstSchema(&context, schema, eValue, false)
var schemaErrors []*validationErrors.SchemaValidationFailure
for i := range validateError {
diff --git a/go.mod b/go.mod
index 2da40322..b36f900e 100644
--- a/go.mod
+++ b/go.mod
@@ -11,17 +11,16 @@ require (
github.com/gizak/termui/v3 v3.1.0
github.com/json-iterator/go v1.1.12
github.com/mitchellh/mapstructure v1.5.0
- github.com/pb33f/libopenapi v0.13.9
- github.com/pb33f/libopenapi-validator v0.0.27
- github.com/pterm/pterm v0.12.69
+ github.com/pb33f/libopenapi v0.13.11
+ github.com/pb33f/libopenapi-validator v0.0.28
+ github.com/pterm/pterm v0.12.70
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
- github.com/spf13/cobra v1.7.0
+ github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.4
github.com/vmware-labs/yaml-jsonpath v0.3.2
- go.uber.org/zap v1.26.0
- golang.org/x/exp v0.0.0-20231006140011-7918f672742d
+ golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa
gopkg.in/yaml.v3 v3.0.1
)
@@ -58,8 +57,8 @@ require (
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
- golang.org/x/sys v0.13.0 // indirect
- golang.org/x/term v0.11.0 // indirect
+ golang.org/x/sys v0.14.0 // indirect
+ golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 8a87fd2f..f2b11142 100644
--- a/go.sum
+++ b/go.sum
@@ -71,7 +71,7 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -248,10 +248,10 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
-github.com/pb33f/libopenapi v0.13.9 h1:LQKTjjhYObuw2RUISbzHx+PH6yueht3mNBx3SBVFjOY=
-github.com/pb33f/libopenapi v0.13.9/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
-github.com/pb33f/libopenapi-validator v0.0.27 h1:eO4kbEs2y5mILoFuFLFadWOWE1td2eDJ2RmzUWFswNk=
-github.com/pb33f/libopenapi-validator v0.0.27/go.mod h1:qXHUgYSewsSCOK30AMiwK39u1ZB0YZm5nff7IzYkckA=
+github.com/pb33f/libopenapi v0.13.11 h1:CHRT15/iakHcwRvr9y7bf63UPekXa8FB1Sc4D4BZ7NU=
+github.com/pb33f/libopenapi v0.13.11/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk=
+github.com/pb33f/libopenapi-validator v0.0.28 h1:XOKGLuRLkHtkiPvm4x1JZgqVqFyD2tPx15qx+aSeaBE=
+github.com/pb33f/libopenapi-validator v0.0.28/go.mod h1:+ozccOkHKFHhriUijn6XClfVHY9bdGrpvzg2t+mIDUY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -267,8 +267,8 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej
github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE=
github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8=
github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s=
-github.com/pterm/pterm v0.12.69 h1:fBCKnB8dSLAl8FlYRQAWYGp2WTI/Xm/tKJ21Hyo9USw=
-github.com/pterm/pterm v0.12.69/go.mod h1:wl06ko9MHnqxz4oDV++IORDpjCzw6+mfrvf0MPj6fdk=
+github.com/pterm/pterm v0.12.70 h1:8W0oBICz0xXvUeB8v9Pcfr2wNtsm7zfSb+FJzIbFB5w=
+github.com/pterm/pterm v0.12.70/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@@ -292,8 +292,8 @@ github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
+github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
+github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.17.0 h1:I5txKw7MJasPL/BrfkbA0Jyo/oELqVmux4pR/UxOMfI=
@@ -328,12 +328,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
-go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
-go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -352,8 +348,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
-golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
+golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -492,15 +488,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
-golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/model/rules.go b/model/rules.go
index f4f03adf..29ecd403 100644
--- a/model/rules.go
+++ b/model/rules.go
@@ -8,6 +8,7 @@ import (
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/index"
"gopkg.in/yaml.v3"
+ "log/slog"
"regexp"
"time"
)
@@ -44,6 +45,7 @@ type RuleFunctionContext struct {
Index *index.SpecIndex `json:"-" yaml:"-"` // A reference to the index created for the spec being parsed
SpecInfo *datamodel.SpecInfo `json:"specInfo,omitempty" yaml:"specInfo,omitempty"` // A reference to all specification information for the spec being parsed.
Document libopenapi.Document `json:"-" yaml:"-"` // A reference to the document being parsed
+ Logger *slog.Logger `json:"-" yaml:"-"` // Custom logger
}
// RuleFunctionResult describes a failure with linting after being run through a rule
diff --git a/motor/rule_applicator.go b/motor/rule_applicator.go
index 90c6b96c..0751ee96 100644
--- a/motor/rule_applicator.go
+++ b/motor/rule_applicator.go
@@ -6,6 +6,7 @@ package motor
import (
"errors"
"fmt"
+ "log/slog"
"net/url"
"sync"
@@ -16,7 +17,6 @@ import (
"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi/datamodel"
"github.com/pb33f/libopenapi/index"
- //"github.com/pb33f/libopenapi/resolver"
"github.com/pb33f/libopenapi/utils"
"github.com/pterm/pterm"
"gopkg.in/yaml.v3"
@@ -36,6 +36,7 @@ type ruleContext struct {
silenceLogs bool
document libopenapi.Document
skipDocumentCheck bool
+ logger *slog.Logger
}
// RuleSetExecution is an instruction set for executing a ruleset. It's a convenience structure to allow the signature
@@ -51,6 +52,7 @@ type RuleSetExecution struct {
AllowLookup bool // Allow remote lookup of files or links
Document libopenapi.Document // a ready to render model.
SkipDocumentCheck bool // Skip the document check, useful for fragments and non openapi specs.
+ Logger *slog.Logger // A custom logger.
}
// RuleSetExecutionResult returns the results of running the ruleset against the supplied spec.
@@ -92,6 +94,15 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult {
indexConfig.AvoidBuildIndex = true
docConfig := datamodel.NewDocumentConfiguration()
+ // add new pretty logger.
+ if execution.Logger == nil {
+ handler := pterm.NewSlogHandler(&pterm.DefaultLogger)
+ docConfig.Logger = slog.New(handler)
+ pterm.DefaultLogger.Level = pterm.LogLevelError
+ } else {
+ docConfig.Logger = execution.Logger
+ }
+
if execution.Base != "" {
// check if this is a URL or not
u, e := url.Parse(execution.Base)
@@ -370,6 +381,7 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult {
customFunctions: execution.CustomFunctions,
silenceLogs: execution.SilenceLogs,
skipDocumentCheck: execution.SkipDocumentCheck,
+ logger: docConfig.Logger,
}
if execution.PanicFunction != nil {
ctx.panicFunc = execution.PanicFunction
@@ -486,6 +498,7 @@ func buildResults(ctx ruleContext, ruleAction model.RuleAction, nodes []*yaml.No
Index: ctx.index,
SpecInfo: ctx.specInfo,
Document: ctx.document,
+ Logger: ctx.logger,
}
if !ctx.skipDocumentCheck && ctx.specInfo.SpecFormat == "" && ctx.specInfo.Version == "" {
diff --git a/parser/json_schema.go b/parser/json_schema.go
index 872a21af..539baefd 100644
--- a/parser/json_schema.go
+++ b/parser/json_schema.go
@@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "github.com/daveshanley/vacuum/model"
yamlAlt "github.com/ghodss/yaml"
validationErrors "github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi-validator/schema_validation"
@@ -192,7 +193,7 @@ func ConvertNodeDefinitionIntoSchema(node *yaml.Node) (*Schema, error) {
}
// ValidateNodeAgainstSchema will accept a schema and a node and check it's valid and return the result, or error.
-func ValidateNodeAgainstSchema(schema *highBase.Schema, node *yaml.Node, isArray bool) (bool, []*validationErrors.ValidationError) {
+func ValidateNodeAgainstSchema(ctx *model.RuleFunctionContext, schema *highBase.Schema, node *yaml.Node, isArray bool) (bool, []*validationErrors.ValidationError) {
//convert node to raw yaml first, then convert to json to be used in schema validation
var d []byte
@@ -219,6 +220,11 @@ func ValidateNodeAgainstSchema(schema *highBase.Schema, node *yaml.Node, isArray
var decoded any
_ = json.Unmarshal(n, &decoded)
- validator := schema_validation.NewSchemaValidator()
+ var validator schema_validation.SchemaValidator
+ if ctx != nil && ctx.Logger != nil {
+ validator = schema_validation.NewSchemaValidatorWithLogger(ctx.Logger)
+ } else {
+ validator = schema_validation.NewSchemaValidator()
+ }
return validator.ValidateSchemaObject(schema, decoded)
}
diff --git a/parser/json_schema_test.go b/parser/json_schema_test.go
index df6d0c71..3bf0a009 100644
--- a/parser/json_schema_test.go
+++ b/parser/json_schema_test.go
@@ -49,7 +49,7 @@ func TestConvertNode_Simple(t *testing.T) {
assert.Len(t, schema.Properties, 3)
// now check the schema is valid
- res, e := ValidateNodeAgainstSchema(schema, r[0], false)
+ res, e := ValidateNodeAgainstSchema(nil, schema, r[0], false)
assert.Nil(t, e)
assert.True(t, res)
}
diff --git a/rulesets/rulesets.go b/rulesets/rulesets.go
index 3281f157..b2c32f1d 100644
--- a/rulesets/rulesets.go
+++ b/rulesets/rulesets.go
@@ -8,13 +8,14 @@ import (
"encoding/json"
"errors"
"fmt"
+ "log/slog"
+ "os"
"strings"
"github.com/daveshanley/vacuum/model"
"github.com/mitchellh/mapstructure"
"github.com/pb33f/libopenapi/utils"
"github.com/santhosh-tekuri/jsonschema/v5"
- "go.uber.org/zap"
)
//go:embed schemas/ruleset.schema.json
@@ -109,12 +110,9 @@ const (
SpectralOff = "off"
)
-var log *zap.Logger
-
-//var log *zap.SugaredLogger
-
type ruleSetsModel struct {
openAPIRuleSet *RuleSet
+ logger *slog.Logger
}
// RuleSets is used to generate default RuleSets built into vacuum
@@ -136,10 +134,16 @@ type RuleSets interface {
var rulesetsSingleton *ruleSetsModel
func BuildDefaultRuleSets() RuleSets {
- log = zap.NewExample()
+ logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
+ Level: slog.LevelError,
+ }))
+ return BuildDefaultRuleSetsWithLogger(logger)
+}
+func BuildDefaultRuleSetsWithLogger(logger *slog.Logger) RuleSets {
rulesetsSingleton = &ruleSetsModel{
openAPIRuleSet: GenerateDefaultOpenAPIRuleSet(),
+ logger: logger,
}
return rulesetsSingleton
}
@@ -233,7 +237,7 @@ func (rsm ruleSetsModel) GenerateRuleSetFromSuppliedRuleSet(ruleset *RuleSet) *R
// let's check to see if this rule exists
if rs.Rules[k] == nil {
- log.Warn("Rule does not exist, ignoring it", zap.String("rule", k))
+ rsm.logger.Warn("Rule does not exist, ignoring it", "rule", k)
// we don't know anything about this rule, so skip it.
continue
@@ -252,7 +256,7 @@ func (rsm ruleSetsModel) GenerateRuleSetFromSuppliedRuleSet(ruleset *RuleSet) *R
if eval, ok := v.(bool); ok {
if eval {
if rsm.openAPIRuleSet.Rules[k] == nil {
- log.Warn("Rule does not exist, ignoring it", zap.String("rule", k))
+ rsm.logger.Warn("Rule does not exist, ignoring it", "rule", k)
continue
}
rs.Rules[k] = rsm.openAPIRuleSet.Rules[k]
@@ -271,11 +275,11 @@ func (rsm ruleSetsModel) GenerateRuleSetFromSuppliedRuleSet(ruleset *RuleSet) *R
dErr := mapstructure.Decode(newRule, &nr)
if dErr != nil {
- log.Error("Unable to decode rule", zap.String("error", dErr.Error()))
+ rsm.logger.Error("Unable to decode rule", "error", dErr.Error())
}
dErr = mapstructure.Decode(newRule["category"], &rc)
if dErr != nil {
- log.Error("Unable to decode rule category", zap.String("error", dErr.Error()))
+ rsm.logger.Error("Unable to decode rule category", "error", dErr.Error())
}
// add to validation category if it's not supplied