From e83f4351f115332c5d4dfa8c29d34b4723bc20b6 Mon Sep 17 00:00:00 2001 From: quobix Date: Fri, 17 Nov 2023 18:08:46 -0500 Subject: [PATCH] Adding formatted logging, cleaned some bugs bumped latest deps Signed-off-by: quobix --- cmd/html_report.go | 2 +- cmd/lint.go | 34 +++++++++++++++++++++++++--------- cmd/shared_functions.go | 9 ++++++--- cmd/shared_functions_test.go | 2 +- cmd/spectral_report.go | 2 +- cmd/vacuum_report.go | 2 +- functions/core/casing.go | 4 +++- functions/core/schema.go | 8 ++++---- functions/openapi/examples.go | 16 ++++++++-------- go.mod | 15 +++++++-------- go.sum | 34 +++++++++++++++------------------- model/rules.go | 2 ++ motor/rule_applicator.go | 15 ++++++++++++++- parser/json_schema.go | 10 ++++++++-- parser/json_schema_test.go | 2 +- rulesets/rulesets.go | 24 ++++++++++++++---------- 16 files changed, 111 insertions(+), 70 deletions(-) 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