diff --git a/internal/schema/ast/ast.go b/internal/schema/ast/ast.go index 8d9a079c..d01359af 100644 --- a/internal/schema/ast/ast.go +++ b/internal/schema/ast/ast.go @@ -22,7 +22,7 @@ import ( // AttrDecls := Name ['?'] ':' Type [',' | ',' AttrDecls] // AppliesTo := 'appliesTo' '{' AppDecls '}' // AppDecls := ('principal' | 'resource') ':' EntOrTyps [',' | ',' AppDecls] -// | 'context' ':' RecType [',' | ',' AppDecls] +// | 'context' ':' (Path | RecType) [',' | ',' AppDecls] // Path := IDENT {'::' IDENT} // Ref := Path '::' STR | Name // RefOrRefs := Ref | '[' [RefOrRefs] ']' @@ -339,9 +339,10 @@ type AppliesTo struct { AppliesToTok token.Position CloseBrace token.Position - Principal []*Path // one of required - Resource []*Path - Context *RecordType // nil if none + Principal []*Path // one of required + Resource []*Path + ContextPath *Path // nil if none + ContextRecord *RecordType // nil if none Inline *Comment // after { PrincipalComments NodeComments diff --git a/internal/schema/ast/convert_human.go b/internal/schema/ast/convert_human.go index 4cecbfdf..5ade4e83 100644 --- a/internal/schema/ast/convert_human.go +++ b/internal/schema/ast/convert_human.go @@ -66,8 +66,10 @@ func convertNamespace(n *Namespace) *JSONNamespace { for _, res := range astDecl.AppliesTo.Resource { jsAction.AppliesTo.ResourceTypes = append(jsAction.AppliesTo.ResourceTypes, res.String()) } - if astDecl.AppliesTo.Context != nil { - jsAction.AppliesTo.Context = convertType(astDecl.AppliesTo.Context) + if astDecl.AppliesTo.ContextRecord != nil { + jsAction.AppliesTo.Context = convertType(astDecl.AppliesTo.ContextRecord) + } else if astDecl.AppliesTo.ContextPath != nil { + jsAction.AppliesTo.Context = convertType(astDecl.AppliesTo.ContextPath) } } jsNamespace.Actions[astActionName.String()] = jsAction diff --git a/internal/schema/ast/convert_json.go b/internal/schema/ast/convert_json.go index ae7d7449..c0f353e8 100644 --- a/internal/schema/ast/convert_json.go +++ b/internal/schema/ast/convert_json.go @@ -187,8 +187,11 @@ func convertJSONAppliesTo(appliesTo *JSONAppliesTo) *AppliesTo { // Convert context if appliesTo.Context != nil { - if context, ok := convertJSONType(appliesTo.Context).(*RecordType); ok { - at.Context = context + switch t := convertJSONType(appliesTo.Context).(type) { + case *RecordType: + at.ContextRecord = t + case *Path: + at.ContextPath = t } } diff --git a/internal/schema/ast/format.go b/internal/schema/ast/format.go index a61392b5..c2a91ba4 100644 --- a/internal/schema/ast/format.go +++ b/internal/schema/ast/format.go @@ -302,10 +302,14 @@ func (p *formatter) printAppliesTo(n *AppliesTo) { } p.write("\n") } - if n.Context != nil { + if n.ContextRecord != nil || n.ContextPath != nil { p.print(n.ContextComments.Before) p.printInd("context: ") - p.print(n.Context) + if n.ContextRecord != nil { + p.print(n.ContextRecord) + } else { + p.print(n.ContextPath) + } p.write(",") if n.ContextComments.Inline != nil { p.print(n.ContextComments.Inline) diff --git a/internal/schema/ast/testdata/convert/test.cedarschema b/internal/schema/ast/testdata/convert/test.cedarschema index 87d83dbc..543a378a 100644 --- a/internal/schema/ast/testdata/convert/test.cedarschema +++ b/internal/schema/ast/testdata/convert/test.cedarschema @@ -40,18 +40,17 @@ namespace PhotoFlash { }, } }; + type authenticatedContext = { + "authenticated": Bool, + }; action "viewPhoto" in [groupAction1, groupAction2, random::nested::name::"actionGroup"] appliesTo { principal: User, resource: Photo, - context: { - "authenticated": Bool, - } + context: authenticatedContext, }; action "listAlbums" appliesTo { principal: User, resource: Account, - context: { - "authenticated": Bool, - } + context: authenticatedContext, }; } \ No newline at end of file diff --git a/internal/schema/ast/testdata/convert/test_want.json b/internal/schema/ast/testdata/convert/test_want.json index 66f8784e..9d9fdee3 100644 --- a/internal/schema/ast/testdata/convert/test_want.json +++ b/internal/schema/ast/testdata/convert/test_want.json @@ -20,6 +20,15 @@ "annotation1": "type", "annotation2": "type" } + }, + "authenticatedContext": { + "type": "Record", + "attributes": { + "authenticated": { + "type": "Boolean", + "required": true + } + } } }, "entityTypes": { @@ -162,13 +171,8 @@ "User" ], "context": { - "type": "Record", - "attributes": { - "authenticated": { - "required": true, - "type": "Boolean" - } - } + "type": "EntityOrCommon", + "name": "authenticatedContext" } } }, @@ -219,13 +223,8 @@ "User" ], "context": { - "type": "Record", - "attributes": { - "authenticated": { - "required": true, - "type": "Boolean" - } - } + "type": "EntityOrCommon", + "name": "authenticatedContext" } }, "memberOf": [ diff --git a/internal/schema/ast/testdata/format/test.cedarschema b/internal/schema/ast/testdata/format/test.cedarschema index 673a5b12..2a9baffa 100644 --- a/internal/schema/ast/testdata/format/test.cedarschema +++ b/internal/schema/ast/testdata/format/test.cedarschema @@ -67,13 +67,14 @@ namespace PhotoFlash { // inline namespace comment "authenticated": Bool, // attribute comment inline }, // context comment }; + type authenticatedContext = { + "authenticated": Bool, + appliesTo: String, // keywords are valid identifiers + }; action "listAlbums" in "read" appliesTo { principal: User, resource: Account, - context: { - "authenticated": Bool, - appliesTo: String, // keywords are valid identifiers - }, + context: authenticatedContext, }; // Remainder comment block // should also be kept around diff --git a/internal/schema/ast/walk_test.go b/internal/schema/ast/walk_test.go index 435caee6..f107acf1 100644 --- a/internal/schema/ast/walk_test.go +++ b/internal/schema/ast/walk_test.go @@ -178,8 +178,11 @@ func (vis *visitor) walk(n ast.Node, open, exit func(ast.Node) bool) { for _, r := range v.Resource { vis.walk(r, open, exit) } - if v.Context != nil { - vis.walk(v.Context, open, exit) + if v.ContextRecord != nil { + vis.walk(v.ContextRecord, open, exit) + } + if v.ContextPath != nil { + vis.walk(v.ContextPath, open, exit) } case *ast.RecordType: for _, attr := range v.Attributes { diff --git a/internal/schema/parser/parser.go b/internal/schema/parser/parser.go index 6c8a510e..5521aa0c 100644 --- a/internal/schema/parser/parser.go +++ b/internal/schema/parser/parser.go @@ -346,9 +346,14 @@ loop: case token.CONTEXT: p.eat() p.eatOnly(token.COLON, "expected :") - appliesTo.Context = p.parseRecType() + if p.peek().Type == token.LEFTBRACE { + appliesTo.ContextRecord = p.parseRecType() + node = appliesTo.ContextRecord + } else { + appliesTo.ContextPath = p.parsePath() + node = appliesTo.ContextPath + } nodeComments = &appliesTo.ContextComments - node = appliesTo.Context case token.COMMENT: comments = append(comments, p.parseComment()) continue diff --git a/internal/schema/parser/testdata/cases/example.cedarschema b/internal/schema/parser/testdata/cases/example.cedarschema index be8291e2..89e4d820 100644 --- a/internal/schema/parser/testdata/cases/example.cedarschema +++ b/internal/schema/parser/testdata/cases/example.cedarschema @@ -77,6 +77,15 @@ namespace PhotoFlash { // inline namespace comment appliesTo: String, // keywords are valid identifiers }, }; + type commonContext = { + "authenticated": Bool, + appliesTo: String, // keywords are valid identifiers + }; + action "commonContext" appliesTo { + principal: User, + resource: Account, + context: commonContext, + }; // Remainder comment block // should also be kept around } // Footer comment on namespace