Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions types/entity_uid.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,45 @@ import (
// Path is a series of idents separated by ::
type Path string

// IsQualified returns whether a Path has any qualifiers (i.e. at least one ::)
func (p Path) IsQualified() bool {
return strings.Contains(string(p), "::")
}

// Qualifier returns a Path with everything but the last element in the original Path or "" if there is only one element.
func (p Path) Qualifier() Path {
idx := strings.LastIndex(string(p), "::")
if idx == -1 {
return ""
}
return p[:idx]
}

// Basename returns the last element in the Path
func (p Path) Basename() string {
idx := strings.LastIndex(string(p), "::")
if idx == -1 {
return string(p)
}
return string(p[idx+2:])
}

// Namespace is a type of Path whose basename does not refer to a type
type Namespace Path

// EntityType is the type portion of an EntityUID
type EntityType Path

// Namespace returns the namespace for the EntityType or "" if the type has no namespace.
func (e EntityType) Namespace() Namespace {
return Namespace(Path(e).Qualifier())
}

// Basename returns the unqualified entity type name.
func (e EntityType) Basename() string {
return Path(e).Basename()
}

// An EntityUID is the identifier for a principal, action, or resource.
type EntityUID struct {
Type EntityType
Expand Down
40 changes: 40 additions & 0 deletions types/entity_uid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,46 @@ func TestEntity(t *testing.T) {
})
}

func TestPathQualification(t *testing.T) {
t.Parallel()

tests := []struct {
path types.Path
qualified bool
qualifier types.Path
basename string
}{
{"NS::User", true, "NS", "User"},
{"A::B::C", true, "A::B", "C"},
{"User", false, "", "User"},
{"", false, "", ""},
}
for _, tt := range tests {
testutil.Equals(t, tt.path.IsQualified(), tt.qualified)
testutil.Equals(t, tt.path.Qualifier(), tt.qualifier)
testutil.Equals(t, tt.path.Basename(), tt.basename)
}
}

func TestEntityTypeQualification(t *testing.T) {
t.Parallel()

tests := []struct {
typ types.EntityType
qualified bool
namespace types.Namespace
basename string
}{
{"NS::User", true, "NS", "User"},
{"A::B::C", true, "A::B", "C"},
{"User", false, "", "User"},
}
for _, tt := range tests {
testutil.Equals(t, tt.typ.Namespace(), tt.namespace)
testutil.Equals(t, tt.typ.Basename(), tt.basename)
}
}

func TestEntityUIDSet(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 1 addition & 1 deletion x/exp/schema/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type Actions map[types.String]Action
type CommonTypes map[types.Ident]CommonType

// Namespaces maps namespace paths to their definitions.
type Namespaces map[types.Path]Namespace
type Namespaces map[types.Namespace]Namespace

// Schema is the top-level Cedar schema AST.
// The Entities, Enums, Actions, and CommonTypes are for the top-level namespace.
Expand Down
24 changes: 24 additions & 0 deletions x/exp/schema/ast/ast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ func TestConstructors(t *testing.T) {
testutil.Equals(t, ast.Type("MyType"), ast.TypeRef("MyType"))
}

func TestEntityTypeRefQualification(t *testing.T) {
qualified := ast.EntityTypeRef("NS::User")
testutil.Equals(t, qualified.IsQualified(), true)
testutil.Equals(t, qualified.Namespace(), types.Namespace("NS"))
testutil.Equals(t, qualified.Basename(), "User")

unqualified := ast.EntityTypeRef("User")
testutil.Equals(t, unqualified.IsQualified(), false)
testutil.Equals(t, unqualified.Namespace(), types.Namespace(""))
testutil.Equals(t, unqualified.Basename(), "User")
}

func TestTypeRefQualification(t *testing.T) {
qualified := ast.TypeRef("NS::MyType")
testutil.Equals(t, qualified.IsQualified(), true)
testutil.Equals(t, qualified.Namespace(), types.Namespace("NS"))
testutil.Equals(t, qualified.Basename(), "MyType")

unqualified := ast.TypeRef("MyType")
testutil.Equals(t, unqualified.IsQualified(), false)
testutil.Equals(t, unqualified.Namespace(), types.Namespace(""))
testutil.Equals(t, unqualified.Basename(), "MyType")
}

func TestParentRefFromID(t *testing.T) {
ref := ast.ParentRefFromID("view")
testutil.Equals(t, ref.ID, types.String("view"))
Expand Down
30 changes: 30 additions & 0 deletions x/exp/schema/ast/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,21 @@ type EntityTypeRef types.EntityType

func (EntityTypeRef) isType() { _ = 0 }

// IsQualified reports whether the entity type reference contains a namespace qualifier.
func (e EntityTypeRef) IsQualified() bool {
return types.EntityType(e).Namespace() != ""
}

// Namespace returns the namespace portion of a qualified entity type reference, or "" if unqualified.
func (e EntityTypeRef) Namespace() types.Namespace {
return types.Namespace(types.Path(e).Qualifier())
}

// Basename returns the unqualified entity type name.
func (e EntityTypeRef) Basename() string {
return types.EntityType(e).Basename()
}

// EntityType returns an EntityTypeRef for the given entity type name.
func EntityType(name types.EntityType) EntityTypeRef {
return EntityTypeRef(name)
Expand All @@ -91,6 +106,21 @@ type TypeRef types.Path

func (TypeRef) isType() { _ = 0 }

// IsQualified reports whether the type reference contains a namespace qualifier.
func (t TypeRef) IsQualified() bool {
return types.Path(t).IsQualified()
}

// Namespace returns the namespace portion of a qualified type reference, or "" if unqualified.
func (t TypeRef) Namespace() types.Namespace {
return types.Namespace(types.Path(t).Qualifier())
}

// Basename returns the unqualified type name.
func (t TypeRef) Basename() string {
return types.Path(t).Basename()
}

// Type returns a TypeRef for the given path.
func Type(name types.Path) TypeRef {
return TypeRef(name)
Expand Down
8 changes: 4 additions & 4 deletions x/exp/schema/internal/json/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (s *Schema) MarshalJSON() ([]byte, error) {

// Bare declarations go under the empty string key.
if hasBareDecls((*ast.Schema)(s)) {
ns, err := marshalNamespace("", ast.Namespace{
ns, err := marshalNamespace(ast.Namespace{
Entities: s.Entities,
Enums: s.Enums,
Actions: s.Actions,
Expand All @@ -32,7 +32,7 @@ func (s *Schema) MarshalJSON() ([]byte, error) {
}

for name, ns := range s.Namespaces {
jns, err := marshalNamespace(name, ns)
jns, err := marshalNamespace(ns)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -67,7 +67,7 @@ func (s *Schema) UnmarshalJSON(b []byte) error {
if result.Namespaces == nil {
result.Namespaces = ast.Namespaces{}
}
result.Namespaces[types.Path(name)] = ns
result.Namespaces[types.Namespace(name)] = ns
}
}
*s = Schema(result)
Expand Down Expand Up @@ -131,7 +131,7 @@ type jsonAttr struct {
Annotations map[string]string `json:"annotations,omitempty"`
}

func marshalNamespace(name types.Path, ns ast.Namespace) (jsonNamespace, error) {
func marshalNamespace(ns ast.Namespace) (jsonNamespace, error) {
jns := jsonNamespace{
EntityTypes: make(map[string]jsonEntityType),
Actions: make(map[string]jsonAction),
Expand Down
12 changes: 6 additions & 6 deletions x/exp/schema/internal/json/json_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestMarshalRecordTypeError(t *testing.T) {
}

func TestMarshalNamespaceCommonTypeError(t *testing.T) {
_, err := marshalNamespace("", ast.Namespace{
_, err := marshalNamespace(ast.Namespace{
CommonTypes: ast.CommonTypes{
"Bad": ast.CommonType{Type: nil},
},
Expand All @@ -35,7 +35,7 @@ func TestMarshalNamespaceCommonTypeError(t *testing.T) {
}

func TestMarshalNamespaceEntityShapeError(t *testing.T) {
_, err := marshalNamespace("", ast.Namespace{
_, err := marshalNamespace(ast.Namespace{
Entities: ast.Entities{
"Foo": ast.Entity{
Shape: ast.RecordType{
Expand All @@ -50,7 +50,7 @@ func TestMarshalNamespaceEntityShapeError(t *testing.T) {
func TestMarshalNamespaceEntityTagsError(t *testing.T) {
// Tags is nil, but the code checks `entity.Tags != nil` first
// So we need a non-nil tags that fails. Use SetType{Element: nil}.
_, err := marshalNamespace("", ast.Namespace{
_, err := marshalNamespace(ast.Namespace{
Entities: ast.Entities{
"Foo": ast.Entity{Tags: nil},
},
Expand All @@ -59,7 +59,7 @@ func TestMarshalNamespaceEntityTagsError(t *testing.T) {
}

func TestMarshalNamespaceEntityTagsError2(t *testing.T) {
_, err := marshalNamespace("", ast.Namespace{
_, err := marshalNamespace(ast.Namespace{
Entities: ast.Entities{
"Foo": ast.Entity{Tags: ast.SetType{Element: nil}},
},
Expand All @@ -68,7 +68,7 @@ func TestMarshalNamespaceEntityTagsError2(t *testing.T) {
}

func TestMarshalNamespaceActionAnnotations(t *testing.T) {
ns, err := marshalNamespace("", ast.Namespace{
ns, err := marshalNamespace(ast.Namespace{
Actions: ast.Actions{
"view": ast.Action{
Annotations: ast.Annotations{"doc": "test"},
Expand All @@ -80,7 +80,7 @@ func TestMarshalNamespaceActionAnnotations(t *testing.T) {
}

func TestMarshalNamespaceContextError(t *testing.T) {
_, err := marshalNamespace("", ast.Namespace{
_, err := marshalNamespace(ast.Namespace{
Actions: ast.Actions{
"view": ast.Action{
AppliesTo: &ast.AppliesTo{
Expand Down
7 changes: 4 additions & 3 deletions x/exp/schema/internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (p *parser) parseSchema() (*ast.Schema, error) {
}

type parsedNamespace struct {
name types.Path
name types.Namespace
ns ast.Namespace
}

Expand All @@ -150,7 +150,8 @@ func (p *parser) parseNamespace(annotations ast.Annotations) (parsedNamespace, e
if err != nil {
return parsedNamespace{}, err
}
if slices.Contains(strings.Split(string(path), "::"), "__cedar") {
nsName := types.Namespace(path)
if slices.Contains(strings.Split(string(nsName), "::"), "__cedar") {
return parsedNamespace{}, fmt.Errorf("%s: the name %q contains \"__cedar\", which is reserved", p.tok.Pos, path)
}
if err := p.expect(tokenLBrace); err != nil {
Expand All @@ -177,7 +178,7 @@ func (p *parser) parseNamespace(annotations ast.Annotations) (parsedNamespace, e
ns.Enums = innerSchema.Enums
ns.Actions = innerSchema.Actions
ns.CommonTypes = innerSchema.CommonTypes
return parsedNamespace{name: path, ns: ns}, nil
return parsedNamespace{name: nsName, ns: ns}, nil
}

func (p *parser) parseDecl(annotations ast.Annotations, schema *ast.Schema) error {
Expand Down
Loading