Skip to content
This repository has been archived by the owner on Jun 28, 2022. It is now read-only.

Commit

Permalink
Add the filename to Rego fragment locations. (#13)
Browse files Browse the repository at this point in the history
It is awkward to map the location of Rego erros back to the right
line on the test document, but we can improve things (in a hacky way)
by capturing the file and line number in the filename that we feed to
Rego when we compile a fragment.

This updates #12.

Signed-off-by: James Peach <[email protected]>
  • Loading branch information
jpeach authored Aug 4, 2020
1 parent 8a756a9 commit a77b777
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 22 deletions.
25 changes: 9 additions & 16 deletions pkg/doc/fragment.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"bytes"
"errors"
"fmt"
"io"

"github.com/projectcontour/integration-tester/pkg/utils"

Expand Down Expand Up @@ -79,6 +78,10 @@ func (t FragmentType) String() string {

// Location tracks the lines that bound a Fragment within some larger Document.
type Location struct {
// Filename is the name of the file this Fragment was read from
// (if that is known by the fragment reader).
Filename string

// Start is the line number this location starts on.
Start int

Expand All @@ -87,6 +90,10 @@ type Location struct {
}

func (l Location) String() string {
if l.Filename != "" {
return fmt.Sprintf("%s:%d-%d", l.Filename, l.Start, l.End)
}

return fmt.Sprintf("%d-%d", l.Start, l.End)
}

Expand Down Expand Up @@ -153,20 +160,6 @@ func decodeYAMLOrJSON(data []byte) (*unstructured.Unstructured, error) {
return &unstructured.Unstructured{Object: into}, nil
}

func decodeModule(data []byte) (*ast.Module, error) {
m, err := utils.ParseCheckFragment(string(data))
if err != nil {
return nil, err
}

// ParseModule can return nil with no error (empty module).
if m == nil {
return nil, io.EOF
}

return m, nil
}

// IsDecoded returns whether this fragment has been decoded to a known fragment type.
func (f *Fragment) IsDecoded() bool {
switch f.Type {
Expand Down Expand Up @@ -208,7 +201,7 @@ func (f *Fragment) Decode() (FragmentType, error) {
// Since we do want to propagate errors so that users can debug
// scripts, we have to assume this is meant to be Rego.

m, err := decodeModule(f.Bytes)
m, err := utils.ParseCheckFragment(f.Location.String(), string(f.Bytes))
if err != nil {
return FragmentTypeInvalid,
utils.ChainErrors(
Expand Down
15 changes: 11 additions & 4 deletions pkg/doc/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Document struct {
// YAML document separator (see https://yaml.org/spec/1.0/#id2561718).
// The contents of each Fragment is opaque and need not be YAML.
func ReadDocument(in io.Reader) (*Document, error) {
filename := ""
startLine := 0
currentLine := 0

Expand All @@ -45,6 +46,10 @@ func ReadDocument(in io.Reader) (*Document, error) {

scanner := bufio.NewScanner(in)

if f, ok := in.(*os.File); ok {
filename = f.Name()
}

// Scan the input a line at a time.
for scanner.Scan() {
currentLine++
Expand All @@ -66,8 +71,9 @@ func ReadDocument(in io.Reader) (*Document, error) {
doc.Parts = append(doc.Parts, Fragment{
Bytes: utils.CopyBytes(buf.Bytes()),
Location: Location{
Start: startLine,
End: currentLine - 1,
Filename: filename,
Start: startLine,
End: currentLine - 1,
},
})
}
Expand All @@ -85,8 +91,9 @@ func ReadDocument(in io.Reader) (*Document, error) {
doc.Parts = append(doc.Parts, Fragment{
Bytes: utils.CopyBytes(buf.Bytes()),
Location: Location{
Start: startLine,
End: currentLine,
Filename: filename,
Start: startLine,
End: currentLine,
},
})
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/utils/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,20 @@ func ParseModuleFile(filePath string) (*ast.Module, error) {
// Rego input is assumed to not have a package declaration so a random
// package name is prepended to make the parsed module globally unique.
// ParseCheckFragment can return nil with no error if the input is empty.
func ParseCheckFragment(input string) (*ast.Module, error) {
// If the filename parameter is empty, an internal name will be generated.
func ParseCheckFragment(filename string, input string) (*ast.Module, error) {
// Rego requires a package name to generate any Rules. Force
// a package name that is unique to the fragment. Note that
// we also use this to generate a unique filename placeholder
// since Rego internals will sometime use this as a map key.
moduleName := RandomStringN(12)

if filename == "" {
filename = fmt.Sprintf("internal/check/%s", moduleName)
}

m, err := ast.ParseModule(
fmt.Sprintf("internal/check/%s", moduleName),
filename,
fmt.Sprintf("package check.%s\n%s", moduleName, input))
if err != nil {
return nil, err
Expand Down

0 comments on commit a77b777

Please sign in to comment.