Skip to content

Commit 4457037

Browse files
Add sumtype:decl to Declaration type
We also need to fix up one location `convertNamespace` which handles nested namespaces. Nested namespaces are not valid (and don't even parse), but here I'm handling them with an error rather than a panic. This leads to some changes in functions that transitively call converNamespace. Signed-off-by: Greg NISBET <gregory.nisbet@gmail.com>
1 parent 0a2ca33 commit 4457037

File tree

6 files changed

+102
-9
lines changed

6 files changed

+102
-9
lines changed

internal/schema/ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ func (s *Schema) End() token.Position {
9797
return token.Position{}
9898
}
9999

100+
//sumtype:decl
100101
type Declaration interface {
101102
Node
102103
isDecl()

internal/schema/ast/convert_human.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,32 @@ import (
99
// Any information related to ordering, formatting, comments, etc... are lost completely.
1010
//
1111
// TODO: Add errors if the schema is invalid (references names that don't exist)
12-
func ConvertHuman2JSON(n *Schema) JSONSchema {
12+
func ConvertHuman2JSON(n *Schema) (JSONSchema, error) {
1313
out := make(JSONSchema)
1414
// In Cedar, all anonymous types (not under a namespace) are put into the "root" namespace,
1515
// which just has a name of "".
1616
anonymousNamespace := &Namespace{}
1717
for _, decl := range n.Decls {
1818
switch decl := decl.(type) {
1919
case *Namespace:
20-
out[decl.Name.String()] = convertNamespace(decl)
20+
namespace, err := convertNamespace(decl)
21+
if err != nil {
22+
return nil, err
23+
}
24+
out[decl.Name.String()] = namespace
2125
default:
2226
anonymousNamespace.Decls = append(anonymousNamespace.Decls, decl)
2327
}
2428
}
2529
if len(anonymousNamespace.Decls) > 0 {
26-
out[""] = convertNamespace(anonymousNamespace)
30+
// Any error converting the anonymous namespace here would have been caught above.
31+
namespace, _ := convertNamespace(anonymousNamespace)
32+
out[""] = namespace
2733
}
28-
return out
34+
return out, nil
2935
}
3036

31-
func convertNamespace(n *Namespace) *JSONNamespace {
37+
func convertNamespace(n *Namespace) (*JSONNamespace, error) {
3238
jsNamespace := new(JSONNamespace)
3339
jsNamespace.Actions = make(map[string]*JSONAction)
3440
jsNamespace.EntityTypes = make(map[string]*JSONEntity)
@@ -104,9 +110,13 @@ func convertNamespace(n *Namespace) *JSONNamespace {
104110
commonType.Annotations[a.Key.String()] = a.Value.String()
105111
}
106112
jsNamespace.CommonTypes[astDecl.Name.String()] = commonType
113+
case *CommentBlock:
114+
// do nothing
115+
case *Namespace:
116+
return nil, fmt.Errorf("namespace %q contains subnamespace %q", n.Name.String(), astDecl.Name.String())
107117
}
108118
}
109-
return jsNamespace
119+
return jsNamespace, nil
110120
}
111121

112122
func convertType(t Type) *JSONType {

internal/schema/ast/convert_human_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"encoding/json"
66
"io/fs"
7+
"strings"
78
"testing"
89

910
"github.com/google/go-cmp/cmp"
@@ -28,7 +29,10 @@ func TestConvertHumanToJson(t *testing.T) {
2829
t.Fatalf("Error parsing example schema: %v", err)
2930
}
3031

31-
jsonSchema := ast.ConvertHuman2JSON(schema)
32+
jsonSchema, err := ast.ConvertHuman2JSON(schema)
33+
if err != nil {
34+
t.Fatalf("Error in schema: %v", err)
35+
}
3236
var got bytes.Buffer
3337
enc := json.NewEncoder(&got)
3438
enc.SetIndent("", " ")
@@ -47,3 +51,25 @@ func TestConvertHumanToJson(t *testing.T) {
4751
diff := cmp.Diff(gotJ, wantJ)
4852
testutil.FatalIf(t, diff != "", "mismatch -want +got:\n%v", diff)
4953
}
54+
55+
func TestConvertHumanToJson_NestedNamespace(t *testing.T) {
56+
namespace := &ast.Schema{
57+
Decls: []ast.Declaration{
58+
&ast.Namespace{
59+
Name: &ast.Path{Parts: []*ast.Ident{{Value: "hi"}}},
60+
Decls: []ast.Declaration{
61+
&ast.Namespace{
62+
Name: &ast.Path{Parts: []*ast.Ident{{Value: "hi"}}},
63+
},
64+
},
65+
},
66+
},
67+
}
68+
_, err := ast.ConvertHuman2JSON(namespace)
69+
if err == nil {
70+
t.Error("error should not be nil")
71+
}
72+
if !strings.Contains(err.Error(), "namespace") {
73+
t.Errorf("bad error %v", err)
74+
}
75+
}

internal/schema/ast/convert_json_test.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ func TestConvertJsonToHumanRoundtrip(t *testing.T) {
2929

3030
// Convert to human-readable format and back to JSON
3131
humanSchema := ast.ConvertJSON2Human(jsonSchema)
32-
jsonSchema2 := ast.ConvertHuman2JSON(humanSchema)
32+
jsonSchema2, err := ast.ConvertHuman2JSON(humanSchema)
33+
if err != nil {
34+
t.Fatalf("Error dumping schema: %v", err)
35+
}
3336

3437
// Compare the JSON schemas
3538
json1, err := json.MarshalIndent(jsonSchema, "", " ")
@@ -92,3 +95,26 @@ func TestConvertJsonToHumanInvalidType(t *testing.T) {
9295
t.Errorf("expected panic message to contain %q, got %q", expected, panicMsg)
9396
}
9497
}
98+
99+
func TestConvertHuman2JSON_NestedNamespace(t *testing.T) {
100+
namePath := &ast.Path{Parts: []*ast.Ident{{Value: "hi"}}}
101+
innerNamespace := &ast.Namespace{
102+
Name: namePath,
103+
}
104+
outerNamespace := &ast.Namespace{
105+
Name: namePath,
106+
Decls: []ast.Declaration{
107+
innerNamespace,
108+
},
109+
}
110+
schema := &ast.Schema{
111+
Decls: []ast.Declaration{
112+
outerNamespace,
113+
},
114+
}
115+
116+
_, err := ast.ConvertHuman2JSON(schema)
117+
if err == nil {
118+
t.Errorf("should have failed")
119+
}
120+
}

x/exp/schema/schema.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ func (s *Schema) MarshalJSON() (out []byte, err error) {
6969
if s.humanSchema != nil {
7070
// Error should not be possible since s.humanSchema comes from our parser.
7171
// If it happens, we return empty JSON.
72-
s.jsonSchema = ast.ConvertHuman2JSON(s.humanSchema)
72+
schema, err := ast.ConvertHuman2JSON(s.humanSchema)
73+
if err != nil {
74+
return nil, err
75+
}
76+
s.jsonSchema = schema
7377
}
7478
if s.jsonSchema == nil {
7579
return nil, nil

x/exp/schema/schema_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ package schema
33
import (
44
"encoding/json"
55
"reflect"
6+
"strings"
67
"testing"
8+
9+
"github.com/cedar-policy/cedar-go/internal/schema/ast"
710
)
811

912
func TestSchemaCedarMarshalUnmarshal(t *testing.T) {
@@ -106,6 +109,29 @@ func TestSchemaJSONMarshalEmpty(t *testing.T) {
106109
}
107110
}
108111

112+
func TestSchemaJSONMarshalNestedNamespace(t *testing.T) {
113+
var s Schema
114+
s.humanSchema = &ast.Schema{
115+
Decls: []ast.Declaration{
116+
&ast.Namespace{
117+
Name: &ast.Path{Parts: []*ast.Ident{{Value: "hi"}}},
118+
Decls: []ast.Declaration{
119+
&ast.Namespace{
120+
Name: &ast.Path{Parts: []*ast.Ident{{Value: "hi"}}},
121+
},
122+
},
123+
},
124+
},
125+
}
126+
_, err := s.MarshalJSON()
127+
if err == nil {
128+
t.Error("error should not be nil")
129+
}
130+
if !strings.Contains(err.Error(), "namespace") {
131+
t.Errorf("bad non-namespace-related error: %v", err)
132+
}
133+
}
134+
109135
func TestSchemaJSONMarshalUnmarshal(t *testing.T) {
110136
tests := []struct {
111137
name string

0 commit comments

Comments
 (0)