Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vacuum not handling YAML anchors correctly #508

Open
TristanSpeakEasy opened this issue Jun 6, 2024 · 1 comment
Open

Vacuum not handling YAML anchors correctly #508

TristanSpeakEasy opened this issue Jun 6, 2024 · 1 comment

Comments

@TristanSpeakEasy
Copy link
Contributor

Consider the two attached OpenAPI docs. One is a yaml document with anchors and the other is the same document that I have just inlined the anchors by doing a roundtrip through https://github.com/go-yaml/yaml

The below reproducible code shows that the document with anchors returns incorrect yaml nodes when present (specifically I am getting a node with no content).

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/daveshanley/vacuum/model"
	"github.com/daveshanley/vacuum/motor"
	"github.com/daveshanley/vacuum/rulesets"
	"github.com/pb33f/libopenapi"
	"github.com/pb33f/libopenapi/datamodel"
	libopenapiUtils "github.com/pb33f/libopenapi/utils"
	"gopkg.in/yaml.v3"
)

type testRule struct{}

func (r *testRule) GetSchema() model.RuleFunctionSchema {
	return model.RuleFunctionSchema{
		Name: "test",
	}
}

func (r *testRule) RunRule(nodes []*yaml.Node,
	context model.RuleFunctionContext,
) []model.RuleFunctionResult {
	results := []model.RuleFunctionResult{}

	for _, schema := range context.Index.GetAllSchemas() {
		for _, nodeType := range []string{"anyOf", "allOf", "oneOf"} {
			keyNode, valNode := libopenapiUtils.FindKeyNode(nodeType, schema.Node.Content)
			if valNode == nil {
				continue
			}

			if len(valNode.Content) == 0 {
				results = append(results, model.RuleFunctionResult{
					Message:      "empty keyword found",
					Path:         schema.Path + "." + nodeType,
					RuleId:       context.Rule.Id,
					StartNode:    keyNode,
					EndNode:      valNode,
					RuleSeverity: context.Rule.Severity,
				})
			}
		}
	}

	return results
}

func (r *testRule) GetCategory() string {
	return "validation"
}

func main() {
	docConf := &datamodel.DocumentConfiguration{
		AllowRemoteReferences:               true,
		AllowFileReferences:                 true,
		IgnorePolymorphicCircularReferences: true,
		IgnoreArrayCircularReferences:       true,
		ExtractRefsSequentially:             true,
	}

	// data, err := os.ReadFile("openapi.yaml")
	data, err := os.ReadFile("openapi-inlined.yaml")
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	d, err := libopenapi.NewDocumentWithConfiguration(data, docConf)
	if err != nil {
		log.Fatalf("error: %v", err)
	}

	ex := &motor.RuleSetExecution{
		RuleSet: &rulesets.RuleSet{
			Rules: map[string]*model.Rule{
				"test": {
					Name:         "test",
					Id:           "test",
					Given:        "$",
					Resolved:     false,
					Severity:     "error",
					Formats:      model.OAS3AllFormat,
					RuleCategory: model.RuleCategories[model.CategoryValidation],
					Type:         rulesets.Validation,
					Then: model.RuleAction{
						Function: "test",
					},
				},
			},
		},
		Document: d,
		PanicFunction: func(err any) {
			log.Fatalf("error: %v", err)
		},
		CustomFunctions: map[string]model.RuleFunction{
			"test": &testRule{},
		},
		AllowLookup: true,
	}

	results := motor.ApplyRulesToRuleSet(ex)

	for _, result := range results.Results {
		log.Printf("result: %v", result)
	}

	if len(results.Results) > 0 {
		log.Fatalf("errors found")
	}

	fmt.Println("done")
}

The code returns errors found in the version with anchors and just returns done in the inlined version

openapi.zip

@Jakousa
Copy link

Jakousa commented Jul 5, 2024

This bug makes vacuum unusable for any real world use cases. Here's another example:

Given file:

openapi: 3.0.3

x-a: &anchor
  important-field: true

x-b:
  <<: *anchor

and rule set

rules:
  test-rule:
    description: Important field must be defined on all levels
    message: "important-field must be defined"
    severity: error
    given: $.x-b
    then:
      field: important-field
      function: defined

Spectral works as expected

$ spectral lint config.yaml --ruleset ruleset.yaml
No results with a severity of 'error' found!

Vacuum does not:

$ vacuum -r ruleset.yaml lint config.yaml -d
Location                           | Severity | Message                         | Rule      | Category   | Path
config.yaml:7:3                    | error    | important-field must be defined | test-rule | Validation | $.x-b

          Linting file 'config.yaml' failed with 1 errors, 0 warnings and 0 informs          

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants