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

[ast/opa parse] Support marshalling of all ast location data #5576

Merged
merged 8 commits into from
Mar 9, 2023
91 changes: 84 additions & 7 deletions ast/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ const (
type (
// Annotations represents metadata attached to other AST nodes such as rules.
Annotations struct {
Location *Location `json:"-"`
Scope string `json:"scope"`
Title string `json:"title,omitempty"`
Entrypoint bool `json:"entrypoint,omitempty"`
Expand All @@ -36,8 +35,11 @@ type (
Authors []*AuthorAnnotation `json:"authors,omitempty"`
Schemas []*SchemaAnnotation `json:"schemas,omitempty"`
Custom map[string]interface{} `json:"custom,omitempty"`
node Node
comments []*Comment
Location *Location `json:"location,omitempty"`

comments []*Comment
node Node
jsonOptions JSONOptions
}

// SchemaAnnotation contains a schema declaration for the document identified by the path.
Expand Down Expand Up @@ -70,10 +72,13 @@ type (
}

AnnotationsRef struct {
Location *Location `json:"location"` // The location of the node the annotations are applied to
Path Ref `json:"path"` // The path of the node the annotations are applied to
Path Ref `json:"path"` // The path of the node the annotations are applied to
Annotations *Annotations `json:"annotations,omitempty"`
node Node // The node the annotations are applied to
Location *Location `json:"location,omitempty"` // The location of the node the annotations are applied to

jsonOptions JSONOptions

node Node // The node the annotations are applied to
}

AnnotationsRefSet []*AnnotationsRef
Expand All @@ -82,7 +87,7 @@ type (
)

func (a *Annotations) String() string {
bs, _ := json.Marshal(a)
bs, _ := a.MarshalJSON()
return string(bs)
}

Expand Down Expand Up @@ -175,6 +180,60 @@ func (a *Annotations) GetTargetPath() Ref {
}
}

func (a *Annotations) setJSONOptions(opts JSONOptions) {
a.jsonOptions = opts
}

func (a *Annotations) MarshalJSON() ([]byte, error) {
if a == nil {
return []byte(`{"scope":""}`), nil
}

data := map[string]interface{}{
"scope": a.Scope,
}

if a.Title != "" {
data["title"] = a.Title
}

if a.Description != "" {
data["description"] = a.Description
}

if a.Entrypoint {
data["entrypoint"] = a.Entrypoint
}

if len(a.Organizations) > 0 {
data["organizations"] = a.Organizations
}

if len(a.RelatedResources) > 0 {
data["related_resources"] = a.RelatedResources
}

if len(a.Authors) > 0 {
data["authors"] = a.Authors
}

if len(a.Schemas) > 0 {
data["schemas"] = a.Schemas
}

if len(a.Custom) > 0 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many checks here to preserve the omit empty functionality.

data["custom"] = a.Custom
}

if a.jsonOptions.MarshalOptions.IncludeLocation.Annotations {
if a.Location != nil {
data["location"] = a.Location
}
}

return json.Marshal(data)
}

func NewAnnotationsRef(a *Annotations) *AnnotationsRef {
var loc *Location
if a.node != nil {
Expand Down Expand Up @@ -209,6 +268,24 @@ func (ar *AnnotationsRef) GetRule() *Rule {
}
}

func (ar *AnnotationsRef) MarshalJSON() ([]byte, error) {
data := map[string]interface{}{
"path": ar.Path,
}

if ar.Annotations != nil {
data["annotations"] = ar.Annotations
}

if ar.jsonOptions.MarshalOptions.IncludeLocation.AnnotationsRef {
if ar.Location != nil {
data["location"] = ar.Location
}
}

return json.Marshal(data)
}

func scopeCompare(s1, s2 string) int {

o1 := scopeOrder(s1)
Expand Down
8 changes: 4 additions & 4 deletions ast/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ p := 7`},
}

// Output:
// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
// data.foo at foo.rego:5 has annotations {"organizations":["Acme Corp."],"scope":"subpackages"}
// data.foo.bar at mod:3 has annotations {"description":"A couple of useful rules","scope":"package"}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new marshalling functions, some of the field orderings have changed.

// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
}

Expand Down Expand Up @@ -102,8 +102,8 @@ p := 7`},

// Output:
// data.foo.bar.p at mod:7 has annotations {"scope":"rule","title":"My Rule P"}
// data.foo.bar at mod:3 has annotations {"scope":"package","description":"A couple of useful rules"}
// data.foo at foo.rego:5 has annotations {"scope":"subpackages","organizations":["Acme Corp."]}
// data.foo.bar at mod:3 has annotations {"description":"A couple of useful rules","scope":"package"}
// data.foo at foo.rego:5 has annotations {"organizations":["Acme Corp."],"scope":"subpackages"}
}

func TestAnnotationSet_Flatten(t *testing.T) {
Expand Down
7 changes: 7 additions & 0 deletions ast/marshal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ast

// customJSON is an interface that can be implemented by AST nodes that
// allows the parser to set options for JSON operations on that node.
type customJSON interface {
setJSONOptions(JSONOptions)
}
Loading