diff --git a/functions/openapi/examples.go b/functions/openapi/examples.go index fe6b9d6c..950cae2b 100644 --- a/functions/openapi/examples.go +++ b/functions/openapi/examples.go @@ -10,6 +10,7 @@ import ( "github.com/daveshanley/vacuum/utils" "github.com/xeipuuv/gojsonschema" "gopkg.in/yaml.v3" + "sync" ) // Examples is a rule that checks that examples are being correctly used. @@ -23,6 +24,22 @@ func (ex Examples) GetSchema() model.RuleFunctionSchema { } } +func checkExampleAsync(wg *sync.WaitGroup, rbNode *yaml.Node, basePath string, results *[]model.RuleFunctionResult, context model.RuleFunctionContext) { + checkExamples(rbNode, basePath, results, context) + wg.Done() +} + +func analyzeExampleAsync(wg *sync.WaitGroup, nameNodeValue string, mediaTypeNode *yaml.Node, basePath string, results *[]model.RuleFunctionResult, context model.RuleFunctionContext) { + analyzeExample(nameNodeValue, mediaTypeNode, basePath, results, context) + wg.Done() +} + +type opExample struct { + path string + node *yaml.Node + param *yaml.Node +} + // RunRule will execute the Examples rule, based on supplied context and a supplied []*yaml.Node slice. func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext) []model.RuleFunctionResult { @@ -35,6 +52,12 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext ops := context.Index.GetPathsNode() var opPath, opMethod string + + // collect responses to scan + var responseBodyCollection []opExample + var requestBodyCollection []opExample + var paramCollection []opExample + if ops != nil { for i, op := range ops.Content { if i%2 == 0 { @@ -50,17 +73,18 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext } basePath := fmt.Sprintf("$.paths.%s.%s", opPath, opMethod) - fmt.Sprintf(basePath) - // check requests. + //check requests. _, rbNode := utils.FindKeyNode("requestBody", method.Content) //check responses _, respNode := utils.FindKeyNode("responses", method.Content) - //fmt.Sprintf("%v", respNode) if rbNode != nil { - results = checkExamples(rbNode, utils.BuildPath(basePath, []string{"requestBody"}), results, context) + requestBodyCollection = append(requestBodyCollection, opExample{ + path: utils.BuildPath(basePath, []string{"requestBody"}), + node: rbNode, + }) } // check parameters. @@ -72,21 +96,32 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext // extract name from param _, nameNode := utils.FindKeyNode("name", []*yaml.Node{param}) if nameNode != nil { - results = analyzeExample(nameNode.Value, param, - utils.BuildPath(basePath, []string{fmt.Sprintf("%s[%d]", "parameters", y)}), results, context) + + paramCollection = append(paramCollection, opExample{ + path: utils.BuildPath(basePath, []string{fmt.Sprintf("%s[%d]", "parameters", y)}), + node: nameNode, + param: param, + }) } } } if respNode != nil { var code string + //wg.Add() for x, respCodeNode := range respNode.Content { + if x%2 == 0 { code = respCodeNode.Value continue } - results = checkExamples(respCodeNode, utils.BuildPath(basePath, []string{fmt.Sprintf("%s.%s", - "responses", code)}), results, context) + + responseBodyCollection = append( + responseBodyCollection, + opExample{ + node: respCodeNode, + path: utils.BuildPath(basePath, []string{fmt.Sprintf("%s.%s", "responses", code)}), + }) } } @@ -94,6 +129,22 @@ func (ex Examples) RunRule(nodes []*yaml.Node, context model.RuleFunctionContext } } + // scan requests, responses as fast as we can asynchronously. + var wg sync.WaitGroup + wg.Add(len(responseBodyCollection)) + wg.Add(len(requestBodyCollection)) + wg.Add(len(paramCollection)) + for _, rb := range responseBodyCollection { + go checkExampleAsync(&wg, rb.node, rb.path, results, context) + } + for _, rb := range requestBodyCollection { + go checkExampleAsync(&wg, rb.node, rb.path, results, context) + } + for _, p := range paramCollection { + go analyzeExampleAsync(&wg, p.node.Value, p.param, p.path, results, context) + } + wg.Wait() + //check components. objNode := context.Index.GetSchemasNode() @@ -352,7 +403,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str res.EndNode = sValue res.Path = basePath res.Rule = context.Rule - *results = append(*results, res) + modifyExampleResults(results, res) return results } @@ -386,7 +437,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.EndNode = valueNode z.Path = nodePath z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) continue } @@ -397,7 +448,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.EndNode = valueNode z.Path = nodePath z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) continue } @@ -416,7 +467,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.EndNode = valueNode z.Path = nodePath z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) } } @@ -429,7 +480,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.EndNode = valueNode z.Path = nodePath z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) } // can`t both have a value and an external value set! @@ -441,8 +492,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.EndNode = valueNode z.Path = nodePath z.Rule = context.Rule - *results = append(*results, z) - + modifyExampleResults(results, z) } } @@ -468,7 +518,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str } z.Rule = context.Rule z.Path = basePath - *results = append(*results, z) + modifyExampleResults(results, z) return results } @@ -490,7 +540,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str } z.Rule = context.Rule z.Path = basePath - *results = append(*results, z) + modifyExampleResults(results, z) return results } @@ -508,7 +558,7 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str } z.Rule = context.Rule z.Path = basePath - *results = append(*results, z) + modifyExampleResults(results, z) } return results } @@ -540,9 +590,9 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.StartNode = sValue z.EndNode = sValue z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) } - + } if schema == nil { return results @@ -559,10 +609,18 @@ func analyzeExample(nameNodeValue string, mediaTypeNode *yaml.Node, basePath str z.StartNode = pNode z.EndNode = endNode z.Rule = context.Rule - *results = append(*results, z) + modifyExampleResults(results, z) } } } return results } + +var exampleLock sync.Mutex + +func modifyExampleResults(results *[]model.RuleFunctionResult, result model.RuleFunctionResult) { + exampleLock.Lock() + *results = append(*results, result) + exampleLock.Unlock() +} diff --git a/functions/openapi/parameter_description.go b/functions/openapi/parameter_description.go index 62bdb72d..5b9d7002 100644 --- a/functions/openapi/parameter_description.go +++ b/functions/openapi/parameter_description.go @@ -60,21 +60,22 @@ func (pd ParameterDescription) RunRule(nodes []*yaml.Node, context model.RuleFun for path, methodMap := range opParams { for method, paramMap := range methodMap { for pName, param := range paramMap { + if param != nil && param.Node != nil { + _, in := utils.FindKeyNode("in", param.Node.Content) + _, desc := utils.FindKeyNode("description", param.Node.Content) + lastNode := utils.FindLastChildNode(param.Node) - _, in := utils.FindKeyNode("in", param.Node.Content) - _, desc := utils.FindKeyNode("description", param.Node.Content) - lastNode := utils.FindLastChildNode(param.Node) - - if in != nil { - if desc == nil || desc.Value == "" { - pathString := fmt.Sprintf("$.paths.%s.%s.parameters", path, method) - results = append(results, model.RuleFunctionResult{ - Message: fmt.Sprintf(msg, pName), - StartNode: param.Node, - EndNode: lastNode, - Path: pathString, - Rule: context.Rule, - }) + if in != nil { + if desc == nil || desc.Value == "" { + pathString := fmt.Sprintf("$.paths.%s.%s.parameters", path, method) + results = append(results, model.RuleFunctionResult{ + Message: fmt.Sprintf(msg, pName), + StartNode: param.Node, + EndNode: lastNode, + Path: pathString, + Rule: context.Rule, + }) + } } } } diff --git a/parser/json_schema.go b/parser/json_schema.go index 411a636c..02f8e860 100644 --- a/parser/json_schema.go +++ b/parser/json_schema.go @@ -126,13 +126,15 @@ func ConvertNodeDefinitionIntoSchema(node *yaml.Node) (*Schema, error) { errChan := make(chan error, 1) go func() { - - dat, err := yaml.Marshal(node) - if err != nil { + var dat []byte + var err error + dat, err = yaml.Marshal(node) + if dat == nil && err != nil { errChan <- err } var schema Schema err = yaml.Unmarshal(dat, &schema) + if err != nil { errChan <- err } @@ -146,10 +148,9 @@ func ConvertNodeDefinitionIntoSchema(node *yaml.Node) (*Schema, error) { case schema = <-schChan: schema.Schema = &utils.SchemaSource schema.Id = &utils.SchemaId - return &schema, nil - case <-time.After(40 * time.Millisecond): // even this seems long to me. - return nil, errors.New("schema failed to unpack in a reasonable timeframe") + case <-time.After(500 * time.Millisecond): // even this seems long to me. + return nil, errors.New("schema is too big! It failed to unpack in a reasonable timeframe") } } diff --git a/rulesets/ruleset_functions.go b/rulesets/ruleset_functions.go index 1081364b..60ed4412 100644 --- a/rulesets/ruleset_functions.go +++ b/rulesets/ruleset_functions.go @@ -425,7 +425,7 @@ func GetOAS2ParameterDescriptionRule() *model.Rule { Description: "Parameter description checks", Given: "$", Resolved: true, - Recommended: false, + Recommended: true, RuleCategory: model.RuleCategories[model.CategoryDescriptions], Type: style, Severity: warn, @@ -445,7 +445,7 @@ func GetOAS3ParameterDescriptionRule() *model.Rule { Description: "Parameter description checks", Given: "$", Resolved: true, - Recommended: false, + Recommended: true, RuleCategory: model.RuleCategories[model.CategoryDescriptions], Type: style, Severity: warn, diff --git a/rulesets/rulesets_test.go b/rulesets/rulesets_test.go index 33f2b5b3..074a1918 100644 --- a/rulesets/rulesets_test.go +++ b/rulesets/rulesets_test.go @@ -124,7 +124,7 @@ func TestRuleSet_GetConfiguredRules_All(t *testing.T) { assert.Len(t, ruleSet.Rules, 47) ruleSet = rs.GenerateOpenAPIRecommendedRuleSet() - assert.Len(t, ruleSet.Rules, 35) + assert.Len(t, ruleSet.Rules, 37) } @@ -140,7 +140,7 @@ rules: def := BuildDefaultRuleSets() rs, _ := CreateRuleSetFromData([]byte(yaml)) override := def.GenerateRuleSetFromSuppliedRuleSet(rs) - assert.Len(t, override.Rules, 35) + assert.Len(t, override.Rules, 37) assert.Len(t, override.RuleDefinitions, 1) } @@ -188,7 +188,7 @@ rules: def := BuildDefaultRuleSets() rs, _ := CreateRuleSetFromData([]byte(yaml)) override := def.GenerateRuleSetFromSuppliedRuleSet(rs) - assert.Len(t, override.Rules, 34) + assert.Len(t, override.Rules, 36) assert.Len(t, override.RuleDefinitions, 1) } @@ -204,7 +204,7 @@ rules: def := BuildDefaultRuleSets() rs, _ := CreateRuleSetFromData([]byte(yaml)) override := def.GenerateRuleSetFromSuppliedRuleSet(rs) - assert.Len(t, override.Rules, 35) + assert.Len(t, override.Rules, 37) assert.Equal(t, "hint", override.Rules["operation-success-response"].Severity) }