diff --git a/pkg/composableschemadsl/compiler/development_test.go b/pkg/composableschemadsl/compiler/development_test.go index 70d523bbf6..1818b4cf2b 100644 --- a/pkg/composableschemadsl/compiler/development_test.go +++ b/pkg/composableschemadsl/compiler/development_test.go @@ -110,7 +110,7 @@ func TestPositionToAstNode(t *testing.T) { line: 2, column: 6, expected: []dslshape.NodeType{ - dslshape.NodeTypeCaveatExpression, + dslshape.NodeTypeOpaqueBraceExpression, dslshape.NodeTypeCaveatDefinition, dslshape.NodeTypeFile, }, diff --git a/pkg/composableschemadsl/compiler/translator.go b/pkg/composableschemadsl/compiler/translator.go index f59af778f6..3e47283cc0 100644 --- a/pkg/composableschemadsl/compiler/translator.go +++ b/pkg/composableschemadsl/compiler/translator.go @@ -162,12 +162,12 @@ func translateCaveatDefinition(tctx translationContext, defNode *dslNode) (*core } // caveat expression. - expressionStringNode, err := defNode.Lookup(dslshape.NodeCaveatDefinitionPredicateExpession) + expressionStringNode, err := defNode.Lookup(dslshape.NodeOpaqueBraceExpressionPredicateExpression) if err != nil { return nil, defNode.WithSourceErrorf(definitionName, "invalid expression: %w", err) } - expressionString, err := expressionStringNode.GetString(dslshape.NodeCaveatExpressionPredicateExpression) + expressionString, err := expressionStringNode.GetString(dslshape.NodeOpaqueBraceExpressionPredicateExpression) if err != nil { return nil, defNode.WithSourceErrorf(expressionString, "invalid expression: %w", err) } diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index 1d5a480165..6875d54dc6 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -37,6 +37,21 @@ const ( NodeTypeCaveatTypeReference // A type reference for a caveat parameter. NodeTypeImport + + // A balanced brace-enclosed expression. Represents both caveat CELs and caveat JSON context. + NodeTypeOpaqueBraceExpression + + NodeTypeTest + NodeTypeTestRelations + NodeTypeTestRelation + NodeTypeTestObject + NodeTypeTestPermission + NodeTypeTestNegativePermission + NodeTypeTestAssertions + NodeTypeTestAssertion + NodeTypeTestExpectedRelations + NodeTypeTestExpectedRelation + NodeTypeTestExpectedRelationSource ) const ( @@ -198,4 +213,24 @@ const ( NodeImportPredicateSource = "import-source" NodeImportPredicatePathSegment = "path-segment" NodeImportPredicateDefinitionName = "imported-definition" + + NodeTestPredicateName = "test-name" + + NodeTestPredicateSubject = "subject" + NodeTestPredicateObject = "object" + NodeTestPredicateRelation = "relation" + NodeTestPredicatePermission = "permission" + NodeTestPredicateAssertionType = "assertion-type" + NodeTestPredicateCaveatName = "caveat-name" + NodeTestPredicateCaveatContext = "caveat-context" + NodeTestPredicateExpirationTime = "expiration-time" + + NodeTestObjectPredicateObjectType = "object-type" + NodeTestObjectPredicateObjectID = "object-id" + // Used for both positive and negative permissions + NodeTestRelationPermissionPredicateName = "permission-name" + + NodeTestExpectedRelationSourcePredicateCaveatAnnotation = "caveat-annotation" + + NodeOpaqueBraceExpressionPredicateExpression = "expression" ) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index 5e56ea49ec..ca81dc97b4 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -82,6 +82,20 @@ var keywords = map[string]struct{}{ "import": {}, "all": {}, "any": {}, + // caveat syntax + "and": {}, + "expiration": {}, + // Test keywords + "test": {}, + "relationships": {}, + "assertions": {}, + "expected": {}, + "is": {}, + "of": {}, + "not": {}, + "conditional": {}, + "when": {}, + "for": {}, } // IsKeyword returns whether the specified input string is a reserved keyword. diff --git a/pkg/composableschemadsl/lexer/lex_test.go b/pkg/composableschemadsl/lexer/lex_test.go index 6d40fa1eb9..cedb6511ef 100644 --- a/pkg/composableschemadsl/lexer/lex_test.go +++ b/pkg/composableschemadsl/lexer/lex_test.go @@ -66,6 +66,19 @@ var lexerTests = []lexerTest{ {"keyword", "import", []Lexeme{{TokenTypeKeyword, 0, "import", ""}, tEOF}}, {"keyword", "all", []Lexeme{{TokenTypeKeyword, 0, "all", ""}, tEOF}}, {"keyword", "nil", []Lexeme{{TokenTypeKeyword, 0, "nil", ""}, tEOF}}, + {"keyword", "and", []Lexeme{{TokenTypeKeyword, 0, "and", ""}, tEOF}}, + // Test keywords + {"keyword", "test", []Lexeme{{TokenTypeKeyword, 0, "test", ""}, tEOF}}, + {"keyword", "relationships", []Lexeme{{TokenTypeKeyword, 0, "relationships", ""}, tEOF}}, + {"keyword", "assertions", []Lexeme{{TokenTypeKeyword, 0, "assertions", ""}, tEOF}}, + {"keyword", "expected", []Lexeme{{TokenTypeKeyword, 0, "expected", ""}, tEOF}}, + {"keyword", "is", []Lexeme{{TokenTypeKeyword, 0, "is", ""}, tEOF}}, + {"keyword", "of", []Lexeme{{TokenTypeKeyword, 0, "of", ""}, tEOF}}, + {"keyword", "not", []Lexeme{{TokenTypeKeyword, 0, "not", ""}, tEOF}}, + {"keyword", "conditional", []Lexeme{{TokenTypeKeyword, 0, "conditional", ""}, tEOF}}, + {"keyword", "when", []Lexeme{{TokenTypeKeyword, 0, "when", ""}, tEOF}}, + {"keyword", "for", []Lexeme{{TokenTypeKeyword, 0, "for", ""}, tEOF}}, + {"identifier", "define", []Lexeme{{TokenTypeIdentifier, 0, "define", ""}, tEOF}}, {"typepath", "foo/bar", []Lexeme{ {TokenTypeIdentifier, 0, "foo", ""}, diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 3fa95d402b..53b64dd456 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -65,6 +65,9 @@ Loop: case p.isKeyword("from"): rootNode.Connect(dslshape.NodePredicateChild, p.consumeImport()) + case p.isKeyword("test"): + rootNode.Connect(dslshape.NodePredicateChild, p.consumeTest()) + default: p.emitErrorf("Unexpected token at root level: %v", p.currentToken.Kind) break Loop @@ -120,7 +123,7 @@ func (p *sourceParser) consumeCaveat() AstNode { return defNode } - exprNode, ok := p.consumeCaveatExpression() + exprNode, ok := p.consumeOpaqueBraceExpression() if !ok { return defNode } @@ -136,13 +139,16 @@ func (p *sourceParser) consumeCaveat() AstNode { return defNode } -func (p *sourceParser) consumeCaveatExpression() (AstNode, bool) { - exprNode := p.startNode(dslshape.NodeTypeCaveatExpression) +// Consume from an opening brace to a closing brace, returning all tokens as a single string. +// Used for caveat CEL expressions and test JSON expressions. +// Keeps track of brace balance. +// Special Logic Note: Since CEL is its own language, we consume here until we have a matching +// close brace, and then pass ALL the found tokens to CEL's own parser to attach the expression +// here. +func (p *sourceParser) consumeOpaqueBraceExpression() (AstNode, bool) { + exprNode := p.startNode(dslshape.NodeTypeOpaqueBraceExpression) defer p.mustFinishNode() - // Special Logic Note: Since CEL is its own language, we consume here until we have a matching - // close brace, and then pass ALL the found tokens to CEL's own parser to attach the expression - // here. braceDepth := 1 // Starting at 1 from the open brace above var startToken *commentedLexeme var endToken *commentedLexeme @@ -182,7 +188,7 @@ consumer: } caveatExpression := p.input[startToken.Position : int(endToken.Position)+len(endToken.Value)] - exprNode.MustDecorate(dslshape.NodeCaveatExpressionPredicateExpression, caveatExpression) + exprNode.MustDecorate(dslshape.NodeOpaqueBraceExpressionPredicateExpression, caveatExpression) return exprNode, true } @@ -665,3 +671,409 @@ func (p *sourceParser) consumeImport() AstNode { return importNode } + +func (p *sourceParser) consumeTest() AstNode { + testNode := p.startNode(dslshape.NodeTypeTest) + defer p.mustFinishNode() + + // These are how we enforce that there is at most one of + // each of these three + var consumedRelations, consumedAssertions, consumedExpected bool + + // test ... + p.consumeKeyword("test") + + testName, ok := p.consumeIdentifier() + if !ok { + return testNode + } + testNode.MustDecorate(dslshape.NodeTestPredicateName, testName) + + // { + _, ok = p.consume(lexer.TokenTypeLeftBrace) + if !ok { + return testNode + } + + // top-levels for test + for { + // } + if _, ok := p.tryConsume(lexer.TokenTypeRightBrace); ok { + break + } + // relations ... + // assertions ... + // expected ... + switch { + case p.isKeyword("relations"): + if consumedRelations { + p.emitErrorf("%s", "at most one relations block is permitted") + return testNode + } + testNode.Connect(dslshape.NodePredicateChild, p.consumeTestRelations()) + consumedRelations = true + case p.isKeyword("assertions"): + if consumedAssertions { + p.emitErrorf("%s", "at most one assertions block is permitted") + return testNode + } + testNode.Connect(dslshape.NodePredicateChild, p.consumeTestAssertions()) + consumedAssertions = true + case p.isKeyword("expected"): + if consumedExpected { + p.emitErrorf("%s", "at most one expected block is permitted") + return testNode + } + testNode.Connect(dslshape.NodePredicateChild, p.consumeTestExpectedRelations()) + consumedExpected = true + } + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + + return testNode +} + +func (p *sourceParser) consumeTestRelations() AstNode { + relationsNode := p.startNode(dslshape.NodeTypeTestRelations) + defer p.mustFinishNode() + + // relations ... + p.consumeKeyword("relations") + + // { + if _, ok := p.consume(lexer.TokenTypeLeftBrace); !ok { + return relationsNode + } + + for { + // } + if _, ok := p.tryConsume(lexer.TokenTypeRightBrace); ok { + break + } + + relationsNode.Connect(dslshape.NodePredicateChild, p.consumeTestRelation()) + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + + return relationsNode +} + +func (p *sourceParser) consumeTestRelation() AstNode { + relationNode := p.startNode(dslshape.NodeTypeTestRelation) + defer p.mustFinishNode() + + // A relation looks like: + // object:foo relation subject:bar + // object consumption + objectNode, ok := p.consumeTestObject() + if !ok { + return relationNode + } + relationNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + + // relation consumption + relation, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestPredicateRelation, relation) + + // subject consumption + subjectNode, ok := p.consumeTestObject() + if !ok { + return relationNode + } + relationNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) + + // optional caveat consumption + if !p.tryConsumeKeyword("with") { + // If we don't see the caveat marker, we stop parsing + return relationNode + } + + if !p.isKeyword("expiration") { + caveatName, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestPredicateCaveatName, caveatName) + + // optional caveat context + if _, ok := p.tryConsume(lexer.TokenTypeColon); ok { + caveatContextNode, ok := p.consumeOpaqueBraceExpression() + if !ok { + return relationNode + } + relationNode.Connect(dslshape.NodeTestPredicateCaveatContext, caveatContextNode) + } + + // This looks a little funky, but it's to account for the fact that we can + // have a caveat or an expiration or both, and that we expect a particular order, + // and means that we don't have to write a loop here to account for the various + // possibilities. + if !p.tryConsumeKeyword("and") { + return relationNode + } + } + + if p.isKeyword("expiration") { + // Don't have to error check here because we've already + // looked before we leapt + p.consumeKeyword("expiration") + if _, ok := p.consume(lexer.TokenTypeColon); !ok { + return relationNode + } + + // Get the datetime string + dateString, ok := p.consume(lexer.TokenTypeString) + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestPredicateExpirationTime, dateString.Value) + } + return relationNode +} + +// Consumes an objectType:objectId pair and returns a test object node with +// object type and ID +func (p *sourceParser) consumeTestObject() (AstNode, bool) { + objectNode := p.startNode(dslshape.NodeTypeTestObject) + defer p.mustFinishNode() + + objectType, ok := p.consumeIdentifier() + if !ok { + return objectNode, false + } + objectNode.MustDecorate(dslshape.NodeTestObjectPredicateObjectType, objectType) + + _, ok = p.consume(lexer.TokenTypeColon) + if !ok { + return objectNode, false + } + + objectID, ok := p.consumeIdentifier() + if !ok { + return objectNode, false + } + objectNode.MustDecorate(dslshape.NodeTestObjectPredicateObjectID, objectID) + return objectNode, true +} + +func (p *sourceParser) consumeTestAssertions() AstNode { + assertionsNode := p.startNode(dslshape.NodeTypeTestAssertions) + defer p.mustFinishNode() + + // relations ... + p.consumeKeyword("relations") + + // { + if _, ok := p.consume(lexer.TokenTypeLeftBrace); !ok { + return assertionsNode + } + + for { + // } + if _, ok := p.tryConsume(lexer.TokenTypeRightBrace); ok { + break + } + + assertionsNode.Connect(dslshape.NodePredicateChild, p.consumeTestAssertion()) + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + + return assertionsNode +} + +func (p *sourceParser) consumeTestAssertion() AstNode { + assertionNode := p.startNode(dslshape.NodeTypeTestAssertion) + defer p.mustFinishNode() + + // object consumption + objectNode, ok := p.consumeTestObject() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + + // is + if ok := p.consumeKeyword("is"); !ok { + return assertionNode + } + + // assertion type + if ok := p.tryConsumeKeyword("not"); ok { + assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "negative") + } else if ok := p.tryConsumeKeyword("conditional"); ok { + assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "conditional") + } else { + // If no marker, it's a positive assertion + assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "positive") + } + + // permission consumption + permission, ok := p.consumeIdentifier() + if !ok { + return assertionNode + } + assertionNode.MustDecorate(dslshape.NodeTestPredicatePermission, permission) + + // for + if ok := p.consumeKeyword("for"); !ok { + return assertionNode + } + + // subject consumption + subjectNode, ok := p.consumeTestObject() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) + + // optional caveat context + if ok := p.tryConsumeKeyword("when"); ok { + caveatContextNode, ok := p.consumeOpaqueBraceExpression() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateCaveatContext, caveatContextNode) + } + + return assertionNode +} + +func (p *sourceParser) consumeTestExpectedRelations() AstNode { + expectedRelationsNode := p.startNode(dslshape.NodeTypeTestExpectedRelations) + defer p.mustFinishNode() + + // { + if _, ok := p.consume(lexer.TokenTypeLeftBrace); !ok { + return expectedRelationsNode + } + + for { + // } + if _, ok := p.tryConsume(lexer.TokenTypeRightBrace); ok { + break + } + + expectedRelationsNode.Connect(dslshape.NodePredicateChild, p.consumeTestExpectedRelation()) + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + return expectedRelationsNode +} + +func (p *sourceParser) consumeTestExpectedRelation() AstNode { + expectedRelationNode := p.startNode(dslshape.NodeTypeTestExpectedRelation) + defer p.mustFinishNode() + + // object consumption + objectNode, ok := p.consumeTestObject() + if !ok { + return expectedRelationNode + } + expectedRelationNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + + // permission consumption + permission, ok := p.consumeIdentifier() + if !ok { + return expectedRelationNode + } + expectedRelationNode.MustDecorate(dslshape.NodeTestPredicatePermission, permission) + + // ( + if _, ok := p.consume(lexer.TokenTypeLeftParen); !ok { + return expectedRelationNode + } + + for { + // ) + if _, ok := p.tryConsume(lexer.TokenTypeRightParen); ok { + break + } + + expectedRelationNode.Connect(dslshape.NodePredicateChild, p.consumeTestExpectedRelationSource()) + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + + return expectedRelationNode +} + +func (p *sourceParser) consumeTestExpectedRelationSource() AstNode { + expectedRelationSourceNode := p.startNode(dslshape.NodeTypeTestExpectedRelationSource) + defer p.mustFinishNode() + + // subject consumption + subjectNode, ok := p.consumeTestObject() + if !ok { + return expectedRelationSourceNode + } + expectedRelationSourceNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) + + // optional context indicator + if p.isToken(lexer.TokenTypeLeftBracket) { + ok := p.consumeContextIndicator() + if !ok { + return expectedRelationSourceNode + } + expectedRelationSourceNode.MustDecorateWithInt(dslshape.NodeTestExpectedRelationSourcePredicateCaveatAnnotation, 1) + } + + // is + if !p.consumeKeyword("is") { + return expectedRelationSourceNode + } + + // relation consumption + relation, ok := p.consumeIdentifier() + if !ok { + return expectedRelationSourceNode + } + expectedRelationSourceNode.MustDecorate(dslshape.NodeTestPredicatePermission, relation) + + // of + if !p.consumeKeyword("of") { + return expectedRelationSourceNode + } + + // object consumption + objectNode, ok := p.consumeTestObject() + if !ok { + return expectedRelationSourceNode + } + expectedRelationSourceNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + + return expectedRelationSourceNode +} + +func (p *sourceParser) consumeContextIndicator() bool { + if _, ok := p.consume(lexer.TokenTypeLeftBracket); !ok { + return false + } + if _, ok := p.consume(lexer.TokenTypeEllipsis); !ok { + return false + } + if _, ok := p.consume(lexer.TokenTypeRightBracket); !ok { + return false + } + return true +} diff --git a/pkg/composableschemadsl/parser/parser_test.go b/pkg/composableschemadsl/parser/parser_test.go index 066e95408c..e56283b6fc 100644 --- a/pkg/composableschemadsl/parser/parser_test.go +++ b/pkg/composableschemadsl/parser/parser_test.go @@ -129,6 +129,11 @@ func TestParser(t *testing.T) { {"local imports with malformed import path test", "localimport_malformed_import_path"}, {"local imports with path missing leading period test", "localimport_path_missing_leading_period"}, {"local imports with typo in import separator test", "localimport_typo_in_import_separator"}, + {"test syntax happy path test", "test_happy_path"}, + {"test syntax relation syntax malformed object", "test_relation_malformed_object"}, + {"test syntax relation syntax missing object", "test_relation_missing_object"}, + {"test syntax relation syntax missing relation", "test_relation_missing_relation"}, + {"test syntax relation syntax missing subject", "test_relation_missing_subject"}, } for _, test := range parserTests { diff --git a/pkg/composableschemadsl/parser/tests/basiccaveat.zed.expected b/pkg/composableschemadsl/parser/tests/basiccaveat.zed.expected index 5759930402..e717bdd91f 100644 --- a/pkg/composableschemadsl/parser/tests/basiccaveat.zed.expected +++ b/pkg/composableschemadsl/parser/tests/basiccaveat.zed.expected @@ -14,9 +14,9 @@ NodeTypeFile input-source = basic caveat test start-rune = 23 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = somecondition == 42 && somebool && somestring == 'hello' + NodeType(20) end-rune = 154 + expression = somecondition == 42 && somebool && somestring == 'hello' input-source = basic caveat test start-rune = 99 parameters => diff --git a/pkg/composableschemadsl/parser/tests/caveatwithkeywordparam.zed.expected b/pkg/composableschemadsl/parser/tests/caveatwithkeywordparam.zed.expected index 6defad57c2..f7b69de77f 100644 --- a/pkg/composableschemadsl/parser/tests/caveatwithkeywordparam.zed.expected +++ b/pkg/composableschemadsl/parser/tests/caveatwithkeywordparam.zed.expected @@ -9,10 +9,10 @@ NodeTypeFile input-source = caveat with keyword parameter test start-rune = 0 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = someMap.isSubtreeOf(anotherMap) - + NodeType(20) end-rune = 93 + expression = someMap.isSubtreeOf(anotherMap) + input-source = caveat with keyword parameter test start-rune = 62 parameters => diff --git a/pkg/composableschemadsl/parser/tests/complexcaveat.zed.expected b/pkg/composableschemadsl/parser/tests/complexcaveat.zed.expected index 2ccd4997ca..eeb17bbefe 100644 --- a/pkg/composableschemadsl/parser/tests/complexcaveat.zed.expected +++ b/pkg/composableschemadsl/parser/tests/complexcaveat.zed.expected @@ -14,13 +14,13 @@ NodeTypeFile input-source = complex caveat test start-rune = 23 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = somecondition == 42 && somebool || something == "hi there" && + NodeType(20) + end-rune = 189 + expression = somecondition == 42 && somebool || something == "hi there" && ({ "themap": 42 }).contains("whatever") - end-rune = 189 input-source = complex caveat test start-rune = 80 parameters => @@ -52,10 +52,10 @@ NodeTypeFile input-source = complex caveat test start-rune = 193 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = somelist.contains("hiya") && somemap?.foo - + NodeType(20) end-rune = 300 + expression = somelist.contains("hiya") && somemap?.foo + input-source = complex caveat test start-rune = 259 parameters => diff --git a/pkg/composableschemadsl/parser/tests/invalidcaveatexpr.zed.expected b/pkg/composableschemadsl/parser/tests/invalidcaveatexpr.zed.expected index 71642a9100..c7c4c19c10 100644 --- a/pkg/composableschemadsl/parser/tests/invalidcaveatexpr.zed.expected +++ b/pkg/composableschemadsl/parser/tests/invalidcaveatexpr.zed.expected @@ -14,9 +14,9 @@ NodeTypeFile input-source = invalid caveat expr test start-rune = 20 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = somecondition == 42 + NodeType(20) end-rune = 79 + expression = somecondition == 42 input-source = invalid caveat expr test start-rune = 61 child-node => diff --git a/pkg/composableschemadsl/parser/tests/multipleslashes.zed.expected b/pkg/composableschemadsl/parser/tests/multipleslashes.zed.expected index 9f3dad7d74..40c0ebc8f7 100644 --- a/pkg/composableschemadsl/parser/tests/multipleslashes.zed.expected +++ b/pkg/composableschemadsl/parser/tests/multipleslashes.zed.expected @@ -42,9 +42,9 @@ NodeTypeFile input-source = multiple slashes in object type start-rune = 121 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = day_of_week == 'tuesday' + NodeType(20) end-rune = 199 + expression = day_of_week == 'tuesday' input-source = multiple slashes in object type start-rune = 176 parameters => diff --git a/pkg/composableschemadsl/parser/tests/superlarge.zed.expected b/pkg/composableschemadsl/parser/tests/superlarge.zed.expected index 3169587303..a98cbd232f 100644 --- a/pkg/composableschemadsl/parser/tests/superlarge.zed.expected +++ b/pkg/composableschemadsl/parser/tests/superlarge.zed.expected @@ -19854,10 +19854,10 @@ NodeTypeFile input-source = super large test start-rune = 105929 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = now < expires_at - + NodeType(20) end-rune = 106008 + expression = now < expires_at + input-source = super large test start-rune = 105992 parameters => diff --git a/pkg/composableschemadsl/parser/tests/test_happy_path.zed b/pkg/composableschemadsl/parser/tests/test_happy_path.zed new file mode 100644 index 0000000000..1d1fe56fa6 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_happy_path.zed @@ -0,0 +1,23 @@ +test DocumentCaveatedTest { + relationships { + post:one reader user:beatrice + post:one caveated_reader user:beatrice with likes_harry_potter + post:one temporary_reader user:claire with temporary_reader {"selected_day": "tuesday"} + } + assertions { + post:one view_as_fan user:beatrice {"likes": true} // Positive assertion + post:one !view_as_fan user:beatrice {"likes": false} // Negative assertion + post:one ?view_as_fan user:beatrice // Conditional assertion + + post:one view_temporarily user:claire {"current_day": "tuesday"} + post:one !view_temporarily user:claire {"current_day": "wednesday"} + } + expected { + post:one view { + user:beatrice is reader of post:one + } + post:one view_as_fan { + user:beatrice[...] is caveated_reader of post:one + } + } +} diff --git a/pkg/composableschemadsl/parser/tests/test_happy_path.zed.expected b/pkg/composableschemadsl/parser/tests/test_happy_path.zed.expected new file mode 100644 index 0000000000..a2beacbb7e --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_happy_path.zed.expected @@ -0,0 +1,23 @@ +NodeTypeFile + end-rune = 26 + input-source = test syntax happy path test + start-rune = 0 + child-node => + NodeType(21) + end-rune = 26 + input-source = test syntax happy path test + start-rune = 0 + test-name = DocumentCaveatedTest + child-node => + NodeTypeError + end-rune = 26 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = relationships + input-source = test syntax happy path test + start-rune = 30 + NodeTypeError + end-rune = 26 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = relationships + input-source = test syntax happy path test + start-rune = 30 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed b/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed new file mode 100644 index 0000000000..e08e63c96c --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed @@ -0,0 +1,5 @@ +test DocumentTest { + relationships { + post: somerelation user:beatrice + } +} diff --git a/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed.expected b/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed.expected new file mode 100644 index 0000000000..154f42ab29 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed.expected @@ -0,0 +1,23 @@ +NodeTypeFile + end-rune = 18 + input-source = test syntax relation syntax malformed object + start-rune = 0 + child-node => + NodeType(21) + end-rune = 18 + input-source = test syntax relation syntax malformed object + start-rune = 0 + test-name = DocumentTest + child-node => + NodeTypeError + end-rune = 18 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax malformed object + start-rune = 22 + NodeTypeError + end-rune = 18 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax malformed object + start-rune = 22 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed b/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed new file mode 100644 index 0000000000..c3beff4552 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed @@ -0,0 +1,5 @@ +test DocumentTest { + relationships { + somerelation user:beatrice + } +} diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed.expected b/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed.expected new file mode 100644 index 0000000000..dbec24fa08 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed.expected @@ -0,0 +1,23 @@ +NodeTypeFile + end-rune = 18 + input-source = test syntax relation syntax missing object + start-rune = 0 + child-node => + NodeType(21) + end-rune = 18 + input-source = test syntax relation syntax missing object + start-rune = 0 + test-name = DocumentTest + child-node => + NodeTypeError + end-rune = 18 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing object + start-rune = 22 + NodeTypeError + end-rune = 18 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing object + start-rune = 22 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed b/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed new file mode 100644 index 0000000000..d2fa28b6cc --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed @@ -0,0 +1,5 @@ +test DocumentTest { + relationships { + post:one user:beatrice + } +} diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed.expected b/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed.expected new file mode 100644 index 0000000000..e6d90f5277 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed.expected @@ -0,0 +1,23 @@ +NodeTypeFile + end-rune = 18 + input-source = test syntax relation syntax missing relation + start-rune = 0 + child-node => + NodeType(21) + end-rune = 18 + input-source = test syntax relation syntax missing relation + start-rune = 0 + test-name = DocumentTest + child-node => + NodeTypeError + end-rune = 18 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing relation + start-rune = 22 + NodeTypeError + end-rune = 18 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing relation + start-rune = 22 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed b/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed new file mode 100644 index 0000000000..b5cec811f6 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed @@ -0,0 +1,5 @@ +test DocumentTest { + relationships { + post:one somerelation + } +} diff --git a/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed.expected b/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed.expected new file mode 100644 index 0000000000..f2b658a648 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed.expected @@ -0,0 +1,23 @@ +NodeTypeFile + end-rune = 18 + input-source = test syntax relation syntax missing subject + start-rune = 0 + child-node => + NodeType(21) + end-rune = 18 + input-source = test syntax relation syntax missing subject + start-rune = 0 + test-name = DocumentTest + child-node => + NodeTypeError + end-rune = 18 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing subject + start-rune = 22 + NodeTypeError + end-rune = 18 + error-message = Unexpected token at root level: TokenTypeKeyword + error-source = relationships + input-source = test syntax relation syntax missing subject + start-rune = 22 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/unclosedcaveat.zed.expected b/pkg/composableschemadsl/parser/tests/unclosedcaveat.zed.expected index a3d2928dc2..d7c2c52b88 100644 --- a/pkg/composableschemadsl/parser/tests/unclosedcaveat.zed.expected +++ b/pkg/composableschemadsl/parser/tests/unclosedcaveat.zed.expected @@ -14,14 +14,14 @@ NodeTypeFile input-source = unclosed caveat test start-rune = 23 caveat-definition-expression => - NodeTypeCaveatExpression - caveat-expression-expressionstr = somemap{ + NodeType(20) + end-rune = 113 + expression = somemap{ } definition user {} - end-rune = 113 input-source = unclosed caveat test start-rune = 80 child-node => diff --git a/pkg/composableschemadsl/references/README.md b/pkg/composableschemadsl/references/README.md new file mode 100644 index 0000000000..dfb682e9f1 --- /dev/null +++ b/pkg/composableschemadsl/references/README.md @@ -0,0 +1,5 @@ +## Reference Schemas + +This is a directory that contains schemas that are intended to demonstrate +a particular part or parts of the schema language and can be used as development +references. diff --git a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed new file mode 100644 index 0000000000..77de60712b --- /dev/null +++ b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed @@ -0,0 +1,49 @@ +caveat likes_harry_potter(likes bool) { + likes == true +} + +caveat only_on_selected_day(selected_day string, current_day string) { + selected_day == current_day +} + +definition user {} + +definition post { + relation writer: user + relation reader: user + relation caveated_reader: user with likes_harry_potter + relation temporary_reader: user with only_on_selected_day + relation expiring_reader: user with only_on_selected_day and expiration + + permission write = writer + permission view = reader + writer + permission view_as_fan = caveated_reader + writer + permission view_temporarily = temporary_reader + permission view_temporarily_with_expiration = expiring_reader +} + +test DocumentCaveatedTest { + relationships { + post:one reader user:beatrice + post:one caveated_reader user:beatrice with likes_harry_potter + post:one temporary_reader user:claire with only_on_selected_day:{"selected_day": "tuesday"} + // TODO + post:one expiring_reader user:job with only_on_selected_day:{"selected_day": "wednesday"} and expiration:"2025-01-25" + } + assertions { + post:one is view_as_fan for user:beatrice when {"likes": true} // Positive assertion + post:one is not view_as_fan for user:beatrice when {"likes": false} // Negative assertion + post:one is conditional view_as_fan for user:beatrice // Conditional assertion + + post:one is view_temporarily for user:claire when {"current_day": "tuesday"} + post:one is not view_temporarily for user:claire when {"current_day": "wednesday"} + } + expected { + post:one view { + user:beatrice is reader of post:one + } + post:one view_as_fan { + user:beatrice[...] is caveated_reader of post:one + } + } +}