Skip to content

Commit

Permalink
More annotation test cases for parser
Browse files Browse the repository at this point in the history
Signed-off-by: Ansu Varghese <avarghese@us.ibm.com>
  • Loading branch information
aavarghese committed Feb 9, 2021
1 parent f82dfc2 commit 91ee070
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 31 deletions.
160 changes: 130 additions & 30 deletions ast/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2663,42 +2663,142 @@ else = {
}

func TestGetAnnotation(t *testing.T) {
const (
testModule = `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data.servers:schemas.servers,data.networks:schemas.networks,data.ports:schemas.ports
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`
)

mod, err := ParseModule("test.rego", testModule)
if err != nil {
t.Fatalf("Unexpected parse error when getting annotations: %v", err)
tests := []struct {
note string
testModule string
expNumComments int
expNumAnnotations int
expAnnotations []*SchemaAnnotation
expError string
}{
{
note: "Single valid annotation",
testModule: `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data.servers:schemas.servers
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`,
expNumComments: 2,
expNumAnnotations: 1,
expAnnotations: []*SchemaAnnotation{&SchemaAnnotation{Name: "data.servers", Schema: "schemas.servers"}},
},
{
note: "Multiple annotations on a single line",
testModule: `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data.servers:schemas.servers,data.networks:schemas.networks,data.ports:schemas.ports
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`,
expNumComments: 2,
expNumAnnotations: 3,
expAnnotations: []*SchemaAnnotation{&SchemaAnnotation{Name: "data.servers", Schema: "schemas.servers"}, &SchemaAnnotation{Name: "data.networks", Schema: "schemas.networks"}, &SchemaAnnotation{Name: "data.ports", Schema: "schemas.ports"}},
},
{
note: "Multiple annotations on a multiple lines",
testModule: `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data.servers:schemas.servers
#@rulesSchema=data.networks:schemas.networks,data.ports:schemas.ports
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`,
expNumComments: 3,
expNumAnnotations: 2,
expAnnotations: []*SchemaAnnotation{&SchemaAnnotation{Name: "data.networks", Schema: "schemas.networks"}, &SchemaAnnotation{Name: "data.ports", Schema: "schemas.ports"}},
},
{
note: "Ill-structured (valid) annotation",
testModule: `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data/servers:schemas/servers
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`,
expNumComments: 2,
expNumAnnotations: 1,
expAnnotations: []*SchemaAnnotation{&SchemaAnnotation{Name: "data/servers", Schema: "schemas/servers"}},
},
{
note: "Ill-structured (invalid) annotation",
testModule: `
package opa.examples
import data.servers
import data.networks
import data.ports
#Schema annotation for this rule referencing three schemas
#@rulesSchema=data.servers=schemas
public_servers[server] {
server = servers[i]; server.ports[j] = ports[k].id
ports[k].networks[l] = networks[m].id;
networks[m].public = true
}`,
expNumComments: 2,
expNumAnnotations: 0,
expAnnotations: nil,
expError: "Invalid schema annotation:",
},
}

if len(mod.Comments) != 2 { //description + annotation
t.Errorf("Expected %v comments but got %v", 2, len(mod.Comments))
}
for _, tc := range tests {
t.Run(tc.note, func(t *testing.T) {
mod, err := ParseModule("test.rego", tc.testModule)
if err != nil {
if tc.expError == "" || !strings.Contains(err.Error(), tc.expError) {
t.Fatalf("Unexpected parse error when getting annotations: %v", err)
}
return
}

annotations := mod.Rules[0].Annotation
if len(annotations) != 3 {
t.Errorf("Expected %v annotations but got %v", 3, len(annotations))
}
if len(mod.Comments) != tc.expNumComments {
t.Errorf("Expected %v comments but got %v", tc.expNumComments, len(mod.Comments))
}

expected := []*SchemaAnnotation{}
expected = append(expected, &SchemaAnnotation{Name: "data.servers", Schema: "schemas.servers"}, &SchemaAnnotation{Name: "data.networks", Schema: "schemas.networks"}, &SchemaAnnotation{Name: "data.ports", Schema: "schemas.ports"})
annotations := mod.Rules[0].Annotation
if len(annotations) != tc.expNumAnnotations {
t.Errorf("Expected %v annotations but got %v", tc.expNumAnnotations, len(annotations))
}

if !reflect.DeepEqual(expected, annotations) {
t.Errorf("Expected %v annotations but got %v", expected, annotations)
if !reflect.DeepEqual(tc.expAnnotations, annotations) {
t.Errorf("Expected %v annotations but got %v", tc.expAnnotations, annotations)
}
})
}
}

Expand Down
3 changes: 2 additions & 1 deletion docs/content/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ Consider the following input document:
A rule can be annotated with a comment of the form:

#@rulesSchema=<expression>:<path-to-schema>,...,<expression>:<path-to-schema>

An expression is of the form <input|data>.field1. ... .fieldN

This annotation associates a schema (uploaded via OPA's `opa eval --schema`) with the corresponding expression. So it can be used to give a schema to the input or any data document. The type checker derives a Rego Object type for the schema and an appropriate entry is added to the type environment. This entry is removed upon exit from the rule.
Expand Down Expand Up @@ -149,7 +150,7 @@ deny[msg] {
}
```

The above rule annotation indicates that the input has a type derived from the input schema (`default-input-schema.json`) [note that the default input schema does not need to be explicitly specified in the rule annotation], and that in addition, `input.request.object` has a type which is derived from the pod data schema. The second annotation overrides the type in the first annotation for the path input.request.object.
The above rule annotation indicates that the input has a type derived from the input schema (`default-input-schema.json`) [note that the default input schema does not need to be explicitly specified in the rule annotation], and that in addition, `input.request.object` has a type which is derived from the pod data schema. The second annotation overrides the type in the first annotation for the path `input.request.object`.

Notice that the order of annotations matter for overriding to work correctly.

Expand Down

0 comments on commit 91ee070

Please sign in to comment.