diff --git a/cmd/lint.go b/cmd/lint.go index 1f1f2024..60e55a46 100644 --- a/cmd/lint.go +++ b/cmd/lint.go @@ -49,6 +49,7 @@ func GetLintCommand() *cobra.Command { baseFlag, _ := cmd.Flags().GetString("base") skipCheckFlag, _ := cmd.Flags().GetBool("skip-check") remoteFlag, _ := cmd.Flags().GetBool("remote") + debugFlag, _ := cmd.Flags().GetBool("debug") // disable color and styling, for CI/CD use. // https://github.com/daveshanley/vacuum/issues/234 @@ -75,11 +76,16 @@ func GetLintCommand() *cobra.Command { mf = true } + logLevel := pterm.LogLevelError + if debugFlag { + logLevel = pterm.LogLevelDebug + } + // setup logging handler := pterm.NewSlogHandler(&pterm.Logger{ Formatter: pterm.LogFormatterColorful, Writer: os.Stdout, - Level: pterm.LogLevelError, + Level: logLevel, ShowTime: false, MaxWidth: 280, KeyStyles: map[string]pterm.Style{ diff --git a/cmd/root.go b/cmd/root.go index 6bd08a10..65ada90b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,6 +58,7 @@ func GetRootCommand() *cobra.Command { rootCmd.PersistentFlags().StringP("base", "p", "", "Override Base URL or path to use for resolving local file based or remote references") rootCmd.PersistentFlags().BoolP("remote", "u", true, "Allow local files and remote (http) references to be looked up") rootCmd.PersistentFlags().BoolP("skip-check", "k", false, "Skip checking for a valid OpenAPI document, useful for linting fragments or non-OpenAPI documents") + rootCmd.PersistentFlags().BoolP("debug", "w", false, "Turn on debug logging") regErr := rootCmd.RegisterFlagCompletionFunc("functions", cobra.FixedCompletions( []string{"so"}, cobra.ShellCompDirectiveFilterFileExt, diff --git a/go.mod b/go.mod index d278504c..eef097dc 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ 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.19 + github.com/pb33f/libopenapi v0.13.20 github.com/pb33f/libopenapi-validator v0.0.32 github.com/pterm/pterm v0.12.71 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 diff --git a/go.sum b/go.sum index 5d1d3f9d..a74fe5da 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,8 @@ 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.19 h1:oFkaQuKx5ZaLwYuHv4W2QeDb+Pm4SU4xqgQdcy2XH58= -github.com/pb33f/libopenapi v0.13.19/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk= +github.com/pb33f/libopenapi v0.13.20 h1:u07mWNSL30sO8nj+kRjM9mDcqxOxq+Etso/oUFflvU4= +github.com/pb33f/libopenapi v0.13.20/go.mod h1:Lv2eEtsAtbRFlF8hjH82L8SIGoUNgemMVoKoB6A9THk= github.com/pb33f/libopenapi-validator v0.0.32 h1:jM+IsUT8I0JOtdkgacGVQmTJayQ2AO5P6URI2HxN11g= github.com/pb33f/libopenapi-validator v0.0.32/go.mod h1:1HbsnP1IVFEaLFtbK9eZXRqUpvtQEGmdstqbgMG+72A= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= diff --git a/model/test_files/circular-tests.yaml b/model/test_files/circular-tests.yaml index beeb6778..7e076156 100644 --- a/model/test_files/circular-tests.yaml +++ b/model/test_files/circular-tests.yaml @@ -21,8 +21,8 @@ components: properties: testThing: "$ref": "#/components/schemas/One" - anyOf: - - "$ref": "#/components/schemas/Four" + anyOf: + - "$ref": "#/components/schemas/Four" required: - testThing - anyOf diff --git a/motor/rule_applicator.go b/motor/rule_applicator.go index 10dddda1..8526e11b 100644 --- a/motor/rule_applicator.go +++ b/motor/rule_applicator.go @@ -244,6 +244,7 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { version := docResolved.GetVersion() var resolvingErrors []*index.ResolvingError + var circularReferences []*index.CircularReferenceResult var rolodexResolved, rolodexUnresolved *index.Rolodex @@ -265,6 +266,11 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { specResolved = rolodexResolved.GetRootIndex().GetRootNode() specUnresolved = rolodexUnresolved.GetRootIndex().GetRootNode() + if rolodexResolved != nil && rolodexResolved.GetRootIndex() != nil { + resolvingErrors = rolodexResolved.GetRootIndex().GetResolver().GetResolvingErrors() + circularReferences = rolodexResolved.GetRootIndex().GetResolver().GetCircularReferences() + } + case '3': _, resolvedModelErrors = docResolved.BuildV3Model() rolodexResolved = docResolved.GetRolodex() @@ -283,6 +289,7 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { if rolodexResolved != nil && rolodexResolved.GetRootIndex() != nil { resolvingErrors = rolodexResolved.GetRootIndex().GetResolver().GetResolvingErrors() + circularReferences = rolodexResolved.GetRootIndex().GetResolver().GetCircularReferences() } } @@ -326,6 +333,7 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { if rolodexResolved != nil && rolodexResolved.GetRootIndex() != nil { resolvingErrors = rolodexResolved.GetRootIndex().GetResolver().GetResolvingErrors() + circularReferences = rolodexResolved.GetRootIndex().GetResolver().GetCircularReferences() } } @@ -354,6 +362,24 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { HowToFix: "Ensure that all $ref values are resolvable and locatable within a local or remote document. " + CircularReferencesFix, } + // add all circular reference errors to the results. + circularRefRule := &model.Rule{ + Name: "Circular References", + Id: "circular-references", + Description: "Circular reference detected", + Message: "Circular reference detected", + Given: "$", + Resolved: false, + Recommended: true, + RuleCategory: model.RuleCategories[model.CategorySchemas], + Type: "validation", + Severity: model.SeverityWarn, + Then: model.RuleAction{ + Function: "blank", + }, + HowToFix: CircularReferencesFix, + } + // add all resolving errors to the results. for _, er := range resolvingErrors { res := model.RuleFunctionResult{ @@ -367,6 +393,19 @@ func ApplyRulesToRuleSet(execution *RuleSetExecution) *RuleSetExecutionResult { ruleResults = append(ruleResults, res) } + // add all circular references to the results. + for _, cr := range circularReferences { + res := model.RuleFunctionResult{ + RuleId: "circular-references", + Rule: circularRefRule, + StartNode: cr.Start.Node, + EndNode: cr.LoopPoint.Node, + Message: fmt.Sprintf("Circular reference detected from %s", cr.Start.Definition), + Path: cr.GenerateJourneyPath(), + } + ruleResults = append(ruleResults, res) + } + for _, er := range indexResolved.GetReferenceIndexErrors() { var idxError *index.IndexingError errors.As(er, &idxError) diff --git a/motor/rule_applicator_test.go b/motor/rule_applicator_test.go index 9a230a74..ecd77efc 100644 --- a/motor/rule_applicator_test.go +++ b/motor/rule_applicator_test.go @@ -1882,7 +1882,7 @@ components: assert.Len(t, results.Errors, 0) assert.NotNil(t, results) - assert.Equal(t, "infinite circular reference detected: one: one -> two -> one [14:7]", + assert.Equal(t, "infinite circular reference detected: #/components/schemas/one: one -> two -> one [14:7]", results.Results[0].Message) assert.Equal(t, "resolving-references", results.Results[0].RuleId) }