diff --git a/pkg/schemadsl/dslshape/dslshape.go b/pkg/schemadsl/dslshape/dslshape.go index 2421f59690..cf0b776ad2 100644 --- a/pkg/schemadsl/dslshape/dslshape.go +++ b/pkg/schemadsl/dslshape/dslshape.go @@ -11,6 +11,7 @@ const ( NodeTypeError NodeType = iota // error occurred; value is text of error NodeTypeFile // The file root node NodeTypeComment // A single or multiline comment + NodeTypeUseFlag // A use flag NodeTypeDefinition // A definition. NodeTypeCaveatDefinition // A caveat definition. @@ -24,6 +25,7 @@ const ( NodeTypeTypeReference // A type reference NodeTypeSpecificTypeReference // A reference to a specific type. NodeTypeCaveatReference // A caveat reference under a type. + NodeTypeTraitReference // A trait reference under a typr. NodeTypeUnionExpression NodeTypeIntersectExpression @@ -71,6 +73,13 @@ const ( // The value of the comment, including its delimeter(s) NodeCommentPredicateValue = "comment-value" + // + // NodeTypeUseFlag + // + + // The name of the use flag. + NodeUseFlagPredicateName = "use-flag-name" + // // NodeTypeDefinition // @@ -155,6 +164,9 @@ const ( // A caveat under a type reference. NodeSpecificReferencePredicateCaveat = "caveat" + // A trait under a type reference. + NodeSpecificReferencePredicateTrait = "trait" + // // NodeTypeCaveatReference // @@ -162,6 +174,13 @@ const ( // The caveat name under the caveat. NodeCaveatPredicateCaveat = "caveat-name" + // + // NodeTypeTraitReference + // + + // The trait name under the trait. + NodeTraitPredicateTrait = "trait-name" + // // NodeTypePermission // diff --git a/pkg/schemadsl/dslshape/zz_generated.nodetype_string.go b/pkg/schemadsl/dslshape/zz_generated.nodetype_string.go index 3567416c6b..4ef1e067e7 100644 --- a/pkg/schemadsl/dslshape/zz_generated.nodetype_string.go +++ b/pkg/schemadsl/dslshape/zz_generated.nodetype_string.go @@ -11,27 +11,29 @@ func _() { _ = x[NodeTypeError-0] _ = x[NodeTypeFile-1] _ = x[NodeTypeComment-2] - _ = x[NodeTypeDefinition-3] - _ = x[NodeTypeCaveatDefinition-4] - _ = x[NodeTypeCaveatParameter-5] - _ = x[NodeTypeCaveatExpression-6] - _ = x[NodeTypeRelation-7] - _ = x[NodeTypePermission-8] - _ = x[NodeTypeTypeReference-9] - _ = x[NodeTypeSpecificTypeReference-10] - _ = x[NodeTypeCaveatReference-11] - _ = x[NodeTypeUnionExpression-12] - _ = x[NodeTypeIntersectExpression-13] - _ = x[NodeTypeExclusionExpression-14] - _ = x[NodeTypeArrowExpression-15] - _ = x[NodeTypeIdentifier-16] - _ = x[NodeTypeNilExpression-17] - _ = x[NodeTypeCaveatTypeReference-18] + _ = x[NodeTypeUseFlag-3] + _ = x[NodeTypeDefinition-4] + _ = x[NodeTypeCaveatDefinition-5] + _ = x[NodeTypeCaveatParameter-6] + _ = x[NodeTypeCaveatExpression-7] + _ = x[NodeTypeRelation-8] + _ = x[NodeTypePermission-9] + _ = x[NodeTypeTypeReference-10] + _ = x[NodeTypeSpecificTypeReference-11] + _ = x[NodeTypeCaveatReference-12] + _ = x[NodeTypeTraitReference-13] + _ = x[NodeTypeUnionExpression-14] + _ = x[NodeTypeIntersectExpression-15] + _ = x[NodeTypeExclusionExpression-16] + _ = x[NodeTypeArrowExpression-17] + _ = x[NodeTypeIdentifier-18] + _ = x[NodeTypeNilExpression-19] + _ = x[NodeTypeCaveatTypeReference-20] } -const _NodeType_name = "NodeTypeErrorNodeTypeFileNodeTypeCommentNodeTypeDefinitionNodeTypeCaveatDefinitionNodeTypeCaveatParameterNodeTypeCaveatExpressionNodeTypeRelationNodeTypePermissionNodeTypeTypeReferenceNodeTypeSpecificTypeReferenceNodeTypeCaveatReferenceNodeTypeUnionExpressionNodeTypeIntersectExpressionNodeTypeExclusionExpressionNodeTypeArrowExpressionNodeTypeIdentifierNodeTypeNilExpressionNodeTypeCaveatTypeReference" +const _NodeType_name = "NodeTypeErrorNodeTypeFileNodeTypeCommentNodeTypeUseFlagNodeTypeDefinitionNodeTypeCaveatDefinitionNodeTypeCaveatParameterNodeTypeCaveatExpressionNodeTypeRelationNodeTypePermissionNodeTypeTypeReferenceNodeTypeSpecificTypeReferenceNodeTypeCaveatReferenceNodeTypeTraitReferenceNodeTypeUnionExpressionNodeTypeIntersectExpressionNodeTypeExclusionExpressionNodeTypeArrowExpressionNodeTypeIdentifierNodeTypeNilExpressionNodeTypeCaveatTypeReference" -var _NodeType_index = [...]uint16{0, 13, 25, 40, 58, 82, 105, 129, 145, 163, 184, 213, 236, 259, 286, 313, 336, 354, 375, 402} +var _NodeType_index = [...]uint16{0, 13, 25, 40, 55, 73, 97, 120, 144, 160, 178, 199, 228, 251, 273, 296, 323, 350, 373, 391, 412, 439} func (i NodeType) String() string { if i < 0 || i >= NodeType(len(_NodeType_index)-1) { diff --git a/pkg/schemadsl/lexer/flaggablelexer.go b/pkg/schemadsl/lexer/flaggablelexer.go new file mode 100644 index 0000000000..d0700337bf --- /dev/null +++ b/pkg/schemadsl/lexer/flaggablelexer.go @@ -0,0 +1,59 @@ +package lexer + +// FlaggableLexler wraps a lexer, automatically translating tokens based on flags, if any. +type FlaggableLexler struct { + lex *Lexer // a reference to the lexer used for tokenization + enabledFlags map[string]transformer // flags that are enabled + seenDefinition bool + afterUseIdentifier bool +} + +// NewFlaggableLexler returns a new FlaggableLexler for the given lexer. +func NewFlaggableLexler(lex *Lexer) *FlaggableLexler { + return &FlaggableLexler{ + lex: lex, + enabledFlags: map[string]transformer{}, + } +} + +// Close stops the lexer from running. +func (l *FlaggableLexler) Close() { + l.lex.Close() +} + +// NextToken returns the next token found in the lexer. +func (l *FlaggableLexler) NextToken() Lexeme { + nextToken := l.lex.nextToken() + + // Look for `use somefeature` + if nextToken.Kind == TokenTypeIdentifier { + // Only allowed until we've seen a definition of some kind. + if !l.seenDefinition { + if l.afterUseIdentifier { + if transformer, ok := Flags[nextToken.Value]; ok { + l.enabledFlags[nextToken.Value] = transformer + } + + l.afterUseIdentifier = false + } else { + l.afterUseIdentifier = nextToken.Value == "use" + } + } + } + + if nextToken.Kind == TokenTypeKeyword && nextToken.Value == "definition" { + l.seenDefinition = true + } + if nextToken.Kind == TokenTypeKeyword && nextToken.Value == "caveat" { + l.seenDefinition = true + } + + for _, handler := range l.enabledFlags { + updated, ok := handler(nextToken) + if ok { + return updated + } + } + + return nextToken +} diff --git a/pkg/schemadsl/lexer/flaggablelexer_test.go b/pkg/schemadsl/lexer/flaggablelexer_test.go new file mode 100644 index 0000000000..b2da4c4a8e --- /dev/null +++ b/pkg/schemadsl/lexer/flaggablelexer_test.go @@ -0,0 +1,74 @@ +package lexer + +import ( + "slices" + "testing" + + "github.com/authzed/spicedb/pkg/schemadsl/input" +) + +var flaggableLexerTests = []lexerTest{ + {"use expiration", "use expiration", []Lexeme{ + {TokenTypeIdentifier, 0, "use", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeKeyword, 0, "expiration", ""}, + tEOF, + }}, + {"use expiration and", "use expiration and", []Lexeme{ + {TokenTypeIdentifier, 0, "use", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeKeyword, 0, "expiration", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeKeyword, 0, "and", ""}, + tEOF, + }}, + {"expiration as non-keyword", "foo expiration", []Lexeme{ + {TokenTypeIdentifier, 0, "foo", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeIdentifier, 0, "expiration", ""}, + tEOF, + }}, + {"and as non-keyword", "foo and", []Lexeme{ + {TokenTypeIdentifier, 0, "foo", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeIdentifier, 0, "and", ""}, + tEOF, + }}, + {"invalid use flag", "use foobar", []Lexeme{ + {TokenTypeIdentifier, 0, "use", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeIdentifier, 0, "foobar", ""}, + tEOF, + }}, + {"use flag after definition", "definition use expiration", []Lexeme{ + {TokenTypeKeyword, 0, "definition", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeIdentifier, 0, "use", ""}, + {TokenTypeWhitespace, 0, " ", ""}, + {TokenTypeIdentifier, 0, "expiration", ""}, + tEOF, + }}, +} + +func TestFlaggableLexer(t *testing.T) { + for _, test := range append(slices.Clone(lexerTests), flaggableLexerTests...) { + t.Run(test.name, func(t *testing.T) { + tokens := performFlaggedLex(&test) + if !equal(tokens, test.tokens) { + t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, tokens, test.tokens) + } + }) + } +} + +func performFlaggedLex(t *lexerTest) (tokens []Lexeme) { + lexer := NewFlaggableLexler(Lex(input.Source(t.name), t.input)) + for { + token := lexer.NextToken() + tokens = append(tokens, token) + if token.Kind == TokenTypeEOF || token.Kind == TokenTypeError { + break + } + } + return +} diff --git a/pkg/schemadsl/lexer/flags.go b/pkg/schemadsl/lexer/flags.go new file mode 100644 index 0000000000..3bfbbde775 --- /dev/null +++ b/pkg/schemadsl/lexer/flags.go @@ -0,0 +1,26 @@ +package lexer + +// FlagExpiration indicates that `expiration` is supported as a first-class +// feature in the schema. +const FlagExpiration = "expiration" + +type transformer func(lexeme Lexeme) (Lexeme, bool) + +// Flags is a map of flag names to their corresponding transformers. +var Flags = map[string]transformer{ + FlagExpiration: func(lexeme Lexeme) (Lexeme, bool) { + // `expiration` becomes a keyword. + if lexeme.Kind == TokenTypeIdentifier && lexeme.Value == "expiration" { + lexeme.Kind = TokenTypeKeyword + return lexeme, true + } + + // `and` becomes a keyword. + if lexeme.Kind == TokenTypeIdentifier && lexeme.Value == "and" { + lexeme.Kind = TokenTypeKeyword + return lexeme, true + } + + return lexeme, false + }, +} diff --git a/pkg/schemadsl/lexer/peekable_lex.go b/pkg/schemadsl/lexer/peekable_lex.go deleted file mode 100644 index 933cb0d3ad..0000000000 --- a/pkg/schemadsl/lexer/peekable_lex.go +++ /dev/null @@ -1,35 +0,0 @@ -package lexer - -import ( - "container/list" -) - -// PeekableLexer wraps a lexer and provides the ability to peek forward without -// losing state. -type PeekableLexer struct { - lex *Lexer // a reference to the lexer used for tokenization - readTokens *list.List // tokens already read from the lexer during a lookahead. -} - -// NewPeekableLexer returns a new PeekableLexer for the given lexer. -func NewPeekableLexer(lex *Lexer) *PeekableLexer { - return &PeekableLexer{ - lex: lex, - readTokens: list.New(), - } -} - -// Close stops the lexer from running. -func (l *PeekableLexer) Close() { - l.lex.Close() -} - -// NextToken returns the next token found in the lexer. -func (l *PeekableLexer) NextToken() Lexeme { - frontElement := l.readTokens.Front() - if frontElement != nil { - return l.readTokens.Remove(frontElement).(Lexeme) - } - - return l.lex.nextToken() -} diff --git a/pkg/schemadsl/parser/parser.go b/pkg/schemadsl/parser/parser.go index 6806420bbe..0b1ad2b69c 100644 --- a/pkg/schemadsl/parser/parser.go +++ b/pkg/schemadsl/parser/parser.go @@ -4,6 +4,8 @@ package parser import ( "strings" + "golang.org/x/exp/maps" + "github.com/authzed/spicedb/pkg/schemadsl/dslshape" "github.com/authzed/spicedb/pkg/schemadsl/input" "github.com/authzed/spicedb/pkg/schemadsl/lexer" @@ -38,12 +40,20 @@ func (p *sourceParser) consumeTopLevel() AstNode { return rootNode } + hasSeenDefinition := false + Loop: for { if p.isToken(lexer.TokenTypeEOF) { break Loop } + if !hasSeenDefinition { + if p.isIdentifier("use") { + rootNode.Connect(dslshape.NodePredicateChild, p.consumeUseFlag()) + } + } + // Consume a statement terminator if one was found. p.tryConsumeStatementTerminator() @@ -57,9 +67,11 @@ Loop: switch { case p.isKeyword("definition"): + hasSeenDefinition = true rootNode.Connect(dslshape.NodePredicateChild, p.consumeDefinition()) case p.isKeyword("caveat"): + hasSeenDefinition = true rootNode.Connect(dslshape.NodePredicateChild, p.consumeCaveat()) default: @@ -231,6 +243,35 @@ func (p *sourceParser) consumeCaveatTypeReference() AstNode { return typeRefNode } +// consumeUseFlag attempts to consume a use flag. +// ``` use flagname ``` +func (p *sourceParser) consumeUseFlag() AstNode { + useNode := p.startNode(dslshape.NodeTypeUseFlag) + defer p.mustFinishNode() + + // consume the `use` + p.consumeIdentifier() + + var useFlag string + if p.isToken(lexer.TokenTypeIdentifier) { + useFlag, _ = p.consumeIdentifier() + } else { + useName, ok := p.consumeVariableKeyword() + if !ok { + return useNode + } + useFlag = useName + } + + if _, ok := lexer.Flags[useFlag]; !ok { + p.emitErrorf("Unknown use flag: `%s`. Options are: %s", useFlag, strings.Join(maps.Keys(lexer.Flags), ", ")) + return useNode + } + + useNode.MustDecorate(dslshape.NodeUseFlagPredicateName, useFlag) + return useNode +} + // consumeDefinition attempts to consume a single schema definition. // ```definition somedef { ... }``` func (p *sourceParser) consumeDefinition() AstNode { @@ -323,17 +364,9 @@ func (p *sourceParser) consumeTypeReference() AstNode { // tryConsumeWithCaveat tries to consume a caveat `with` expression. func (p *sourceParser) tryConsumeWithCaveat() (AstNode, bool) { - if !p.isKeyword("with") { - return nil, false - } - caveatNode := p.startNode(dslshape.NodeTypeCaveatReference) defer p.mustFinishNode() - if ok := p.consumeKeyword("with"); !ok { - return nil, ok - } - consumed, ok := p.consumeTypePath() if !ok { return caveatNode, true @@ -348,14 +381,46 @@ func (p *sourceParser) consumeSpecificTypeWithCaveat() AstNode { specificNode := p.consumeSpecificTypeWithoutFinish() defer p.mustFinishNode() - caveatNode, ok := p.tryConsumeWithCaveat() - if ok { - specificNode.Connect(dslshape.NodeSpecificReferencePredicateCaveat, caveatNode) + // Check for a caveat and/or supported trait. + if !p.isKeyword("with") { + return specificNode + } + + p.consumeKeyword("with") + + if !p.isKeyword("expiration") { + caveatNode, ok := p.tryConsumeWithCaveat() + if ok { + specificNode.Connect(dslshape.NodeSpecificReferencePredicateCaveat, caveatNode) + } + + if !p.tryConsumeKeyword("and") { + return specificNode + } + } + + if p.isKeyword("expiration") { + // Check for expiration trait. + traitNode := p.consumeExpirationTrait() + + // Decorate with the expiration trait. + specificNode.Connect(dslshape.NodeSpecificReferencePredicateTrait, traitNode) } return specificNode } +// consumeExpirationTrait consumes an expiration trait. +func (p *sourceParser) consumeExpirationTrait() AstNode { + expirationTraitNode := p.startNode(dslshape.NodeTypeTraitReference) + p.consumeKeyword("expiration") + + expirationTraitNode.MustDecorate(dslshape.NodeTraitPredicateTrait, "expiration") + defer p.mustFinishNode() + + return expirationTraitNode +} + // consumeSpecificTypeOpen consumes an identifier as a specific type reference. func (p *sourceParser) consumeSpecificTypeWithoutFinish() AstNode { specificNode := p.startNode(dslshape.NodeTypeSpecificTypeReference) diff --git a/pkg/schemadsl/parser/parser_impl.go b/pkg/schemadsl/parser/parser_impl.go index 76f2bf3839..c63aec34ed 100644 --- a/pkg/schemadsl/parser/parser_impl.go +++ b/pkg/schemadsl/parser/parser_impl.go @@ -44,18 +44,18 @@ type commentedLexeme struct { // sourceParser holds the state of the parser. type sourceParser struct { - source input.Source // the name of the input; used only for error reports - input string // the input string itself - lex *lexer.PeekableLexer // a reference to the lexer used for tokenization - builder NodeBuilder // the builder function for creating AstNode instances - nodes *nodeStack // the stack of the current nodes - currentToken commentedLexeme // the current token - previousToken commentedLexeme // the previous token + source input.Source // the name of the input; used only for error reports + input string // the input string itself + lex *lexer.FlaggableLexler // a reference to the lexer used for tokenization + builder NodeBuilder // the builder function for creating AstNode instances + nodes *nodeStack // the stack of the current nodes + currentToken commentedLexeme // the current token + previousToken commentedLexeme // the previous token } // buildParser returns a new sourceParser instance. func buildParser(lx *lexer.Lexer, builder NodeBuilder, source input.Source, input string) *sourceParser { - l := lexer.NewPeekableLexer(lx) + l := lexer.NewFlaggableLexler(lx) return &sourceParser{ source: source, input: input, @@ -163,6 +163,11 @@ func (p *sourceParser) isToken(types ...lexer.TokenType) bool { return false } +// isIdentifier returns true if the current token is an identifier matching that given. +func (p *sourceParser) isIdentifier(identifier string) bool { + return p.isToken(lexer.TokenTypeIdentifier) && p.currentToken.Value == identifier +} + // isKeyword returns true if the current token is a keyword matching that given. func (p *sourceParser) isKeyword(keyword string) bool { return p.isToken(lexer.TokenTypeKeyword) && p.currentToken.Value == keyword @@ -178,6 +183,18 @@ func (p *sourceParser) emitErrorf(format string, args ...interface{}) { p.currentNode().Connect(dslshape.NodePredicateChild, errorNode) } +// consumeVariableKeyword consumes an expected keyword token or adds an error node. +func (p *sourceParser) consumeVariableKeyword() (string, bool) { + if !p.isToken(lexer.TokenTypeKeyword) { + p.emitErrorf("Expected keyword, found token %v", p.currentToken.Kind) + return "", false + } + + token := p.currentToken + p.consumeToken() + return token.Value, true +} + // consumeKeyword consumes an expected keyword token or adds an error node. func (p *sourceParser) consumeKeyword(keyword string) bool { if !p.tryConsumeKeyword(keyword) { diff --git a/pkg/schemadsl/parser/parser_test.go b/pkg/schemadsl/parser/parser_test.go index f5992e5177..b96eead9f9 100644 --- a/pkg/schemadsl/parser/parser_test.go +++ b/pkg/schemadsl/parser/parser_test.go @@ -122,6 +122,12 @@ func TestParser(t *testing.T) { {"arrow illegal operations test", "arrowillegalops"}, {"arrow illegal function test", "arrowillegalfunc"}, {"caveat with keyword parameter test", "caveatwithkeywordparam"}, + {"use expiration test", "useexpiration"}, + {"use expiration keyword test", "useexpirationkeyword"}, + {"expiration non-keyword test", "expirationnonkeyword"}, + {"invalid use", "invaliduse"}, + {"use after definition", "useafterdef"}, + {"invalid use expiration test", "invaliduseexpiration"}, } for _, test := range parserTests { diff --git a/pkg/schemadsl/parser/tests/caveatstype.zed.expected b/pkg/schemadsl/parser/tests/caveatstype.zed.expected index c8a9fa2f8f..bcbc38c8a0 100644 --- a/pkg/schemadsl/parser/tests/caveatstype.zed.expected +++ b/pkg/schemadsl/parser/tests/caveatstype.zed.expected @@ -35,7 +35,7 @@ NodeTypeFile caveat-name = somecaveat end-rune = 67 input-source = caveats type test - start-rune = 53 + start-rune = 58 NodeTypeSpecificTypeReference end-rune = 100 input-source = caveats type test @@ -47,7 +47,7 @@ NodeTypeFile caveat-name = anothercaveat end-rune = 100 input-source = caveats type test - start-rune = 83 + start-rune = 88 NodeTypeRelation end-rune = 187 input-source = caveats type test @@ -75,7 +75,7 @@ NodeTypeFile caveat-name = wildcardcaveat end-rune = 153 input-source = caveats type test - start-rune = 135 + start-rune = 140 NodeTypeSpecificTypeReference end-rune = 187 input-source = caveats type test @@ -86,4 +86,4 @@ NodeTypeFile caveat-name = someprefix/somecaveat end-rune = 187 input-source = caveats type test - start-rune = 162 \ No newline at end of file + start-rune = 167 \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/expirationnonkeyword.zed b/pkg/schemadsl/parser/tests/expirationnonkeyword.zed new file mode 100644 index 0000000000..4d23a3f262 --- /dev/null +++ b/pkg/schemadsl/parser/tests/expirationnonkeyword.zed @@ -0,0 +1,3 @@ +caveat expiration(someparam int) { + someparam == 42 +} \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/expirationnonkeyword.zed.expected b/pkg/schemadsl/parser/tests/expirationnonkeyword.zed.expected new file mode 100644 index 0000000000..109f23c22d --- /dev/null +++ b/pkg/schemadsl/parser/tests/expirationnonkeyword.zed.expected @@ -0,0 +1,29 @@ +NodeTypeFile + end-rune = 52 + input-source = expiration non-keyword test + start-rune = 0 + child-node => + NodeTypeCaveatDefinition + caveat-definition-name = expiration + end-rune = 52 + input-source = expiration non-keyword test + start-rune = 0 + caveat-definition-expression => + NodeTypeCaveatExpression + caveat-expression-expressionstr = someparam == 42 + + end-rune = 51 + input-source = expiration non-keyword test + start-rune = 36 + parameters => + NodeTypeCaveatParameter + caveat-parameter-name = someparam + end-rune = 30 + input-source = expiration non-keyword test + start-rune = 18 + caveat-parameter-type => + NodeTypeCaveatTypeReference + end-rune = 30 + input-source = expiration non-keyword test + start-rune = 28 + type-name = int \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/invaliduse.zed b/pkg/schemadsl/parser/tests/invaliduse.zed new file mode 100644 index 0000000000..a74a63239b --- /dev/null +++ b/pkg/schemadsl/parser/tests/invaliduse.zed @@ -0,0 +1,3 @@ +use something + +definition resource {} \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/invaliduse.zed.expected b/pkg/schemadsl/parser/tests/invaliduse.zed.expected new file mode 100644 index 0000000000..f36654ba97 --- /dev/null +++ b/pkg/schemadsl/parser/tests/invaliduse.zed.expected @@ -0,0 +1,22 @@ +NodeTypeFile + end-rune = 36 + input-source = invalid use + start-rune = 0 + child-node => + NodeTypeUseFlag + end-rune = 12 + input-source = invalid use + start-rune = 0 + child-node => + NodeTypeError + end-rune = 12 + error-message = Unknown use flag: `something`. Options are: expiration + error-source = + + input-source = invalid use + start-rune = 13 + NodeTypeDefinition + definition-name = resource + end-rune = 36 + input-source = invalid use + start-rune = 15 \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/invaliduseexpiration.zed b/pkg/schemadsl/parser/tests/invaliduseexpiration.zed new file mode 100644 index 0000000000..fc3dab5e81 --- /dev/null +++ b/pkg/schemadsl/parser/tests/invaliduseexpiration.zed @@ -0,0 +1,5 @@ +use expiration + +definition resource { + relation editor: user with somecaveat expiration +} \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/invaliduseexpiration.zed.expected b/pkg/schemadsl/parser/tests/invaliduseexpiration.zed.expected new file mode 100644 index 0000000000..09ef641bc2 --- /dev/null +++ b/pkg/schemadsl/parser/tests/invaliduseexpiration.zed.expected @@ -0,0 +1,50 @@ +NodeTypeFile + end-rune = 78 + input-source = invalid use expiration test + start-rune = 0 + child-node => + NodeTypeUseFlag + end-rune = 13 + input-source = invalid use expiration test + start-rune = 0 + use-flag-name = expiration + NodeTypeDefinition + definition-name = resource + end-rune = 78 + input-source = invalid use expiration test + start-rune = 16 + child-node => + NodeTypeRelation + end-rune = 78 + input-source = invalid use expiration test + relation-name = editor + start-rune = 42 + allowed-types => + NodeTypeTypeReference + end-rune = 78 + input-source = invalid use expiration test + start-rune = 59 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 78 + input-source = invalid use expiration test + start-rune = 59 + type-name = user + caveat => + NodeTypeCaveatReference + caveat-name = somecaveat + end-rune = 78 + input-source = invalid use expiration test + start-rune = 69 + NodeTypeError + end-rune = 78 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = expiration + input-source = invalid use expiration test + start-rune = 80 + NodeTypeError + end-rune = 78 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = expiration + input-source = invalid use expiration test + start-rune = 80 \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/multipleslashes.zed.expected b/pkg/schemadsl/parser/tests/multipleslashes.zed.expected index 9f3dad7d74..a9931fc9e7 100644 --- a/pkg/schemadsl/parser/tests/multipleslashes.zed.expected +++ b/pkg/schemadsl/parser/tests/multipleslashes.zed.expected @@ -35,7 +35,7 @@ NodeTypeFile caveat-name = foo/bar/only_on_tuesday end-rune = 116 input-source = multiple slashes in object type - start-rune = 89 + start-rune = 94 NodeTypeCaveatDefinition caveat-definition-name = foo/bar/only_on_tuesday end-rune = 201 diff --git a/pkg/schemadsl/parser/tests/superlarge.zed.expected b/pkg/schemadsl/parser/tests/superlarge.zed.expected index 3169587303..9c37affa9b 100644 --- a/pkg/schemadsl/parser/tests/superlarge.zed.expected +++ b/pkg/schemadsl/parser/tests/superlarge.zed.expected @@ -19910,7 +19910,7 @@ NodeTypeFile caveat-name = unexpired_grant end-rune = 106073 input-source = super large test - start-rune = 106054 + start-rune = 106059 NodeTypePermission end-rune = 106100 input-source = super large test diff --git a/pkg/schemadsl/parser/tests/useafterdef.zed b/pkg/schemadsl/parser/tests/useafterdef.zed new file mode 100644 index 0000000000..e171f928ff --- /dev/null +++ b/pkg/schemadsl/parser/tests/useafterdef.zed @@ -0,0 +1,3 @@ +definition resource {} + +use expiration \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/useafterdef.zed.expected b/pkg/schemadsl/parser/tests/useafterdef.zed.expected new file mode 100644 index 0000000000..7154b75c12 --- /dev/null +++ b/pkg/schemadsl/parser/tests/useafterdef.zed.expected @@ -0,0 +1,16 @@ +NodeTypeFile + end-rune = 22 + input-source = use after definition + start-rune = 0 + child-node => + NodeTypeDefinition + definition-name = resource + end-rune = 21 + input-source = use after definition + start-rune = 0 + NodeTypeError + end-rune = 22 + error-message = Unexpected token at root level: TokenTypeIdentifier + error-source = use + input-source = use after definition + start-rune = 24 \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/useexpiration.zed b/pkg/schemadsl/parser/tests/useexpiration.zed new file mode 100644 index 0000000000..3d39bc6e02 --- /dev/null +++ b/pkg/schemadsl/parser/tests/useexpiration.zed @@ -0,0 +1,6 @@ +use expiration + +definition resource { + relation viewer: user with expiration + relation editor: user with somecaveat and expiration +} \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/useexpiration.zed.expected b/pkg/schemadsl/parser/tests/useexpiration.zed.expected new file mode 100644 index 0000000000..bc632606f8 --- /dev/null +++ b/pkg/schemadsl/parser/tests/useexpiration.zed.expected @@ -0,0 +1,66 @@ +NodeTypeFile + end-rune = 137 + input-source = use expiration test + start-rune = 0 + child-node => + NodeTypeUseFlag + end-rune = 13 + input-source = use expiration test + start-rune = 0 + use-flag-name = expiration + NodeTypeDefinition + definition-name = resource + end-rune = 137 + input-source = use expiration test + start-rune = 16 + child-node => + NodeTypeRelation + end-rune = 78 + input-source = use expiration test + relation-name = viewer + start-rune = 42 + allowed-types => + NodeTypeTypeReference + end-rune = 78 + input-source = use expiration test + start-rune = 59 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 78 + input-source = use expiration test + start-rune = 59 + type-name = user + trait => + NodeTypeTraitReference + end-rune = 78 + input-source = use expiration test + start-rune = 69 + trait-name = expiration + NodeTypeRelation + end-rune = 135 + input-source = use expiration test + relation-name = editor + start-rune = 84 + allowed-types => + NodeTypeTypeReference + end-rune = 135 + input-source = use expiration test + start-rune = 101 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 135 + input-source = use expiration test + start-rune = 101 + type-name = user + caveat => + NodeTypeCaveatReference + caveat-name = somecaveat + end-rune = 120 + input-source = use expiration test + start-rune = 111 + trait => + NodeTypeTraitReference + end-rune = 135 + input-source = use expiration test + start-rune = 126 + trait-name = expiration \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/useexpirationkeyword.zed b/pkg/schemadsl/parser/tests/useexpirationkeyword.zed new file mode 100644 index 0000000000..4271ab396d --- /dev/null +++ b/pkg/schemadsl/parser/tests/useexpirationkeyword.zed @@ -0,0 +1,5 @@ +use expiration + +caveat expiration(someparam int) { + someparam == 42 +} \ No newline at end of file diff --git a/pkg/schemadsl/parser/tests/useexpirationkeyword.zed.expected b/pkg/schemadsl/parser/tests/useexpirationkeyword.zed.expected new file mode 100644 index 0000000000..df7f59d4df --- /dev/null +++ b/pkg/schemadsl/parser/tests/useexpirationkeyword.zed.expected @@ -0,0 +1,27 @@ +NodeTypeFile + end-rune = 21 + input-source = use expiration keyword test + start-rune = 0 + child-node => + NodeTypeUseFlag + end-rune = 13 + input-source = use expiration keyword test + start-rune = 0 + use-flag-name = expiration + NodeTypeCaveatDefinition + end-rune = 21 + input-source = use expiration keyword test + start-rune = 16 + child-node => + NodeTypeError + end-rune = 21 + error-message = Expected identifier, found token TokenTypeKeyword + error-source = expiration + input-source = use expiration keyword test + start-rune = 23 + NodeTypeError + end-rune = 21 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = expiration + input-source = use expiration keyword test + start-rune = 23 \ No newline at end of file