From f461c6f1f4d6465cf0377dcc8c6652bd59098853 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Tue, 12 Nov 2024 16:42:41 -0700 Subject: [PATCH 01/21] Checkpoint; --- pkg/composableschemadsl/dslshape/dslshape.go | 16 ++ pkg/composableschemadsl/lexer/lex_def.go | 7 + pkg/composableschemadsl/parser/parser.go | 198 +++++++++++++++++++ 3 files changed, 221 insertions(+) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index 1d5a480165..941dbed17f 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -37,6 +37,15 @@ const ( NodeTypeCaveatTypeReference // A type reference for a caveat parameter. NodeTypeImport + + NodeTypeTest + NodeTypeTestRelations + NodeTypeTestRelation + NodeTypeTestObject + NodeTypeTestPermission + NodeTypeTestNegativePermission + NodeTypeTestAssertions + NodeTypeTestExpected ) const ( @@ -198,4 +207,11 @@ const ( NodeImportPredicateSource = "import-source" NodeImportPredicatePathSegment = "path-segment" NodeImportPredicateDefinitionName = "imported-definition" + + NodeTestPredicateName = "test-name" + + NodeTestObjectPredicateObjectType = "object-type" + NodeTestObjectPredicateObjectID = "object-id" + // Used for both positive and negative permissions + NodeTestRelationPermissionPredicateName = "permission-name" ) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index 5e56ea49ec..dce51f7e64 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -82,6 +82,13 @@ var keywords = map[string]struct{}{ "import": {}, "all": {}, "any": {}, + // Test keywords + "test": {}, + "relationships": {}, + "assertions": {}, + "expected": {}, + "is": {}, + "of": {}, } // IsKeyword returns whether the specified input string is a reserved keyword. diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 3fa95d402b..3b23aa4d8c 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 @@ -665,3 +668,198 @@ func (p *sourceParser) consumeImport() AstNode { return importNode } + +func (p *sourceParser) consumeTest() AstNode { + testNode := p.startNode(dslshape.NodeTypeImport) + 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.consumeTestExpected()) + consumedExpected = true + } + + ok := p.consumeStatementTerminator() + if !ok { + break + } + } + + return testNode +} + +func (p *sourceParser) consumeTestRelations() AstNode { + relationsNode := p.startNode(dslshape.NodeTypeImport) + 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.NodeTypeImport) + defer p.mustFinishNode() + + // A relation looks like: + // object:foo relation subject:bar + // object consumption + objectType, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationObjectType, objectType) + + _, ok = p.consume(lexer.TokenTypeColon) + if !ok { + return relationNode + } + + objectID, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationObjectID, objectID) + + // relation consumption + relation, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationRelation, relation) + + // subject consumption + subjectType, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationSubjectType, subjectType) + + _, ok = p.consume(lexer.TokenTypeColon) + if !ok { + return relationNode + } + + subjectID, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationSubjectID, subjectID) + + return relationNode +} + +// Consumes an objectType:objectId pair and returns a +// +func (p *sourceParser) consumeTestObject() AstNode { + +} + +func (p *sourceParser) consumeTestAssertions() AstNode { + assertionsNode := p.startNode(dslshape.NodeTypeImport) + 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.NodeTypeImport) + defer p.mustFinishNode() + + return assertionNode +} + +func (p *sourceParser) consumeTestExpected() AstNode { + expectedNode := p.startNode(dslshape.NodeTypeImport) + defer p.mustFinishNode() + + return expectedNode +} From 5e477230e0d7d3d14d9992f92d54e3ca4397dd05 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Fri, 22 Nov 2024 16:07:04 -0700 Subject: [PATCH 02/21] Implement consumeTestObject --- pkg/composableschemadsl/dslshape/dslshape.go | 4 ++ pkg/composableschemadsl/parser/parser.go | 52 ++++++++++---------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index 941dbed17f..c62452f0f2 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -210,6 +210,10 @@ const ( NodeTestPredicateName = "test-name" + NodeTestRelationPredicateSubject = "subject" + NodeTestRelationPredicateObject = "object" + NodeTestRelationPredicateRelation = "relation" + NodeTestObjectPredicateObjectType = "object-type" NodeTestObjectPredicateObjectID = "object-id" // Used for both positive and negative permissions diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 3b23aa4d8c..26b887df28 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -770,55 +770,53 @@ func (p *sourceParser) consumeTestRelation() AstNode { // A relation looks like: // object:foo relation subject:bar // object consumption - objectType, ok := p.consumeIdentifier() - if !ok { - return relationNode - } - relationNode.MustDecorate(dslshape.NodeTestRelationObjectType, objectType) - - _, ok = p.consume(lexer.TokenTypeColon) - if !ok { - return relationNode - } - objectID, ok := p.consumeIdentifier() + objectNode, ok := p.consumeTestObject() if !ok { return relationNode } - relationNode.MustDecorate(dslshape.NodeTestRelationObjectID, objectID) + relationNode.Connect(dslshape.NodeTestRelationPredicateObject, objectNode) // relation consumption relation, ok := p.consumeIdentifier() if !ok { return relationNode } - relationNode.MustDecorate(dslshape.NodeTestRelationRelation, relation) + relationNode.MustDecorate(dslshape.NodeTestRelationPredicateRelation, relation) // subject consumption - subjectType, ok := p.consumeIdentifier() + subjectNode, ok := p.consumeTestObject() if !ok { return relationNode } - relationNode.MustDecorate(dslshape.NodeTestRelationSubjectType, subjectType) + relationNode.Connect(dslshape.NodeTestRelationPredicateSubject, subjectNode) - _, ok = p.consume(lexer.TokenTypeColon) + 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 relationNode + return objectNode, false } + objectNode.MustDecorate(dslshape.NodeTestObjectPredicateObjectType, objectType) - subjectID, ok := p.consumeIdentifier() + _, ok = p.consume(lexer.TokenTypeColon) if !ok { - return relationNode + return objectNode, false } - relationNode.MustDecorate(dslshape.NodeTestRelationSubjectID, subjectID) - - return relationNode -} - -// Consumes an objectType:objectId pair and returns a -// -func (p *sourceParser) consumeTestObject() AstNode { + objectID, ok := p.consumeIdentifier() + if !ok { + return objectNode, false + } + objectNode.MustDecorate(dslshape.NodeTestObjectPredicateObjectID, objectID) + return objectNode, true } func (p *sourceParser) consumeTestAssertions() AstNode { From 1137ee4df9b92297c29f73824105ad2b25f79405 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Fri, 22 Nov 2024 17:22:47 -0700 Subject: [PATCH 03/21] Use opaque brace expression instead of caveat context --- pkg/composableschemadsl/compiler/development_test.go | 2 +- pkg/composableschemadsl/compiler/translator.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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) } From 1b38353482c3a7b71599ea855415fcf140218c45 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Fri, 22 Nov 2024 17:23:04 -0700 Subject: [PATCH 04/21] Add new keywords --- pkg/composableschemadsl/lexer/lex_def.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index dce51f7e64..3d7b3b43df 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -83,12 +83,12 @@ var keywords = map[string]struct{}{ "all": {}, "any": {}, // Test keywords - "test": {}, + "test": {}, "relationships": {}, - "assertions": {}, - "expected": {}, - "is": {}, - "of": {}, + "assertions": {}, + "expected": {}, + "is": {}, + "of": {}, } // IsKeyword returns whether the specified input string is a reserved keyword. From c3ea472140075e71d5acd19b0fd6afecc97d3060 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Fri, 22 Nov 2024 17:23:16 -0700 Subject: [PATCH 05/21] Add new node types and predicates --- pkg/composableschemadsl/dslshape/dslshape.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index c62452f0f2..79f581167f 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -38,6 +38,9 @@ const ( NodeTypeImport + // A balanced brace-enclosed expression. Represents both caveat CELs and caveat JSON context. + NodeTypeOpaqueBraceExpression + NodeTypeTest NodeTypeTestRelations NodeTypeTestRelation @@ -210,12 +213,17 @@ const ( NodeTestPredicateName = "test-name" - NodeTestRelationPredicateSubject = "subject" - NodeTestRelationPredicateObject = "object" + NodeTestRelationPredicateSubject = "subject" + NodeTestRelationPredicateObject = "object" NodeTestRelationPredicateRelation = "relation" + NodeTestRelationPredicateCaveatName = "caveat-name" + NodeTestRelationPredicateCaveatContext = "caveat-context" + NodeTestObjectPredicateObjectType = "object-type" - NodeTestObjectPredicateObjectID = "object-id" + NodeTestObjectPredicateObjectID = "object-id" // Used for both positive and negative permissions NodeTestRelationPermissionPredicateName = "permission-name" + + NodeOpaqueBraceExpressionPredicateExpression = "expression" ) From 15579416c7657958e5b3958ea7c358a99ba92eb3 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Fri, 22 Nov 2024 17:23:34 -0700 Subject: [PATCH 06/21] Add caveat expression consumption --- pkg/composableschemadsl/parser/parser.go | 38 ++++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 26b887df28..476a8490f5 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -123,7 +123,7 @@ func (p *sourceParser) consumeCaveat() AstNode { return defNode } - exprNode, ok := p.consumeCaveatExpression() + exprNode, ok := p.consumeOpaqueBraceExpression() if !ok { return defNode } @@ -139,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 @@ -185,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 } @@ -770,7 +773,6 @@ func (p *sourceParser) consumeTestRelation() AstNode { // A relation looks like: // object:foo relation subject:bar // object consumption - objectNode, ok := p.consumeTestObject() if !ok { return relationNode @@ -791,6 +793,24 @@ func (p *sourceParser) consumeTestRelation() AstNode { } relationNode.Connect(dslshape.NodeTestRelationPredicateSubject, subjectNode) + // optional caveat consumption + if p.tryConsumeKeyword("with") { + caveatName, ok := p.consumeIdentifier() + if !ok { + return relationNode + } + relationNode.MustDecorate(dslshape.NodeTestRelationPredicateCaveatName, caveatName) + + // optional caveat context + if _, ok := p.tryConsume(lexer.TokenTypeLeftBrace); ok { + caveatContextNode, ok := p.consumeOpaqueBraceExpression() + if !ok { + return relationNode + } + relationNode.Connect(dslshape.NodeTestRelationPredicateCaveatContext, caveatContextNode) + } + } + return relationNode } @@ -851,7 +871,7 @@ func (p *sourceParser) consumeTestAssertions() AstNode { func (p *sourceParser) consumeTestAssertion() AstNode { assertionNode := p.startNode(dslshape.NodeTypeImport) defer p.mustFinishNode() - + return assertionNode } From 40621724bfb687c7edf7fd38c521a1a779346119 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 25 Nov 2024 08:23:50 -0700 Subject: [PATCH 07/21] Add new node types and annotations --- pkg/composableschemadsl/dslshape/dslshape.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index 79f581167f..34255a0f76 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -48,7 +48,10 @@ const ( NodeTypeTestPermission NodeTypeTestNegativePermission NodeTypeTestAssertions - NodeTypeTestExpected + NodeTypeTestAssertion + NodeTypeTestExpectedRelations + NodeTypeTestExpectedRelation + NodeTypeTestExpectedRelationSource ) const ( @@ -213,17 +216,20 @@ const ( NodeTestPredicateName = "test-name" - NodeTestRelationPredicateSubject = "subject" - NodeTestRelationPredicateObject = "object" - NodeTestRelationPredicateRelation = "relation" - - NodeTestRelationPredicateCaveatName = "caveat-name" - NodeTestRelationPredicateCaveatContext = "caveat-context" + NodeTestPredicateSubject = "subject" + NodeTestPredicateObject = "object" + NodeTestPredicateRelation = "relation" + NodeTestPredicatePermission = "permission" + NodeTestPredicateAssertionType = "assertion-type" + NodeTestPredicateCaveatName = "caveat-name" + NodeTestPredicateCaveatContext = "caveat-context" NodeTestObjectPredicateObjectType = "object-type" NodeTestObjectPredicateObjectID = "object-id" // Used for both positive and negative permissions NodeTestRelationPermissionPredicateName = "permission-name" + NodeTestExpectedRelationSourcePredicateCaveatAnnotation = "caveat-annotation" + NodeOpaqueBraceExpressionPredicateExpression = "expression" ) From ca6c70efaecc407e1869e669da226aa8d9f00d56 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 25 Nov 2024 08:24:02 -0700 Subject: [PATCH 08/21] Finish first pass of parser implementation --- pkg/composableschemadsl/parser/parser.go | 188 +++++++++++++++++++++-- 1 file changed, 173 insertions(+), 15 deletions(-) diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 476a8490f5..8334cb25f9 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -673,7 +673,7 @@ func (p *sourceParser) consumeImport() AstNode { } func (p *sourceParser) consumeTest() AstNode { - testNode := p.startNode(dslshape.NodeTypeImport) + testNode := p.startNode(dslshape.NodeTypeTest) defer p.mustFinishNode() // These are how we enforce that there is at most one of @@ -724,7 +724,7 @@ func (p *sourceParser) consumeTest() AstNode { p.emitErrorf("%s", "at most one expected block is permitted") return testNode } - testNode.Connect(dslshape.NodePredicateChild, p.consumeTestExpected()) + testNode.Connect(dslshape.NodePredicateChild, p.consumeTestExpectedRelations()) consumedExpected = true } @@ -738,7 +738,7 @@ func (p *sourceParser) consumeTest() AstNode { } func (p *sourceParser) consumeTestRelations() AstNode { - relationsNode := p.startNode(dslshape.NodeTypeImport) + relationsNode := p.startNode(dslshape.NodeTypeTestRelations) defer p.mustFinishNode() // relations ... @@ -767,7 +767,7 @@ func (p *sourceParser) consumeTestRelations() AstNode { } func (p *sourceParser) consumeTestRelation() AstNode { - relationNode := p.startNode(dslshape.NodeTypeImport) + relationNode := p.startNode(dslshape.NodeTypeTestRelation) defer p.mustFinishNode() // A relation looks like: @@ -777,21 +777,21 @@ func (p *sourceParser) consumeTestRelation() AstNode { if !ok { return relationNode } - relationNode.Connect(dslshape.NodeTestRelationPredicateObject, objectNode) + relationNode.Connect(dslshape.NodeTestPredicateObject, objectNode) // relation consumption relation, ok := p.consumeIdentifier() if !ok { return relationNode } - relationNode.MustDecorate(dslshape.NodeTestRelationPredicateRelation, relation) + relationNode.MustDecorate(dslshape.NodeTestPredicateRelation, relation) // subject consumption subjectNode, ok := p.consumeTestObject() if !ok { return relationNode } - relationNode.Connect(dslshape.NodeTestRelationPredicateSubject, subjectNode) + relationNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) // optional caveat consumption if p.tryConsumeKeyword("with") { @@ -799,15 +799,15 @@ func (p *sourceParser) consumeTestRelation() AstNode { if !ok { return relationNode } - relationNode.MustDecorate(dslshape.NodeTestRelationPredicateCaveatName, caveatName) + relationNode.MustDecorate(dslshape.NodeTestPredicateCaveatName, caveatName) // optional caveat context - if _, ok := p.tryConsume(lexer.TokenTypeLeftBrace); ok { + if p.isToken(lexer.TokenTypeLeftBrace) { caveatContextNode, ok := p.consumeOpaqueBraceExpression() if !ok { return relationNode } - relationNode.Connect(dslshape.NodeTestRelationPredicateCaveatContext, caveatContextNode) + relationNode.Connect(dslshape.NodeTestPredicateCaveatContext, caveatContextNode) } } @@ -840,7 +840,7 @@ func (p *sourceParser) consumeTestObject() (AstNode, bool) { } func (p *sourceParser) consumeTestAssertions() AstNode { - assertionsNode := p.startNode(dslshape.NodeTypeImport) + assertionsNode := p.startNode(dslshape.NodeTypeTestAssertions) defer p.mustFinishNode() // relations ... @@ -869,15 +869,173 @@ func (p *sourceParser) consumeTestAssertions() AstNode { } func (p *sourceParser) consumeTestAssertion() AstNode { - assertionNode := p.startNode(dslshape.NodeTypeImport) + assertionNode := p.startNode(dslshape.NodeTypeTestAssertion) defer p.mustFinishNode() + // object consumption + objectNode, ok := p.consumeTestObject() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + + // assertion type + if _, ok := p.tryConsume(lexer.TokenTypeExclamationPoint); ok { + assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "negative") +} else if _, ok := p.tryConsume(lexer.TokenTypeQuestionMark); 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) + + // subject consumption + subjectNode, ok := p.consumeTestObject() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) + + // optional caveat context + if p.isToken(lexer.TokenTypeLeftBrace) { + caveatContextNode, ok := p.consumeOpaqueBraceExpression() + if !ok { + return assertionNode + } + assertionNode.Connect(dslshape.NodeTestPredicateCaveatContext, caveatContextNode) + } + return assertionNode } -func (p *sourceParser) consumeTestExpected() AstNode { - expectedNode := p.startNode(dslshape.NodeTypeImport) +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() - return expectedNode + // 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 } From 8b74c081eb03ca19796acf9ca0081186654b6eb5 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 25 Nov 2024 08:38:00 -0700 Subject: [PATCH 09/21] Add a references dir --- pkg/composableschemadsl/references/README.md | 5 +++ .../references/test_syntax_with_caveats.zed | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 pkg/composableschemadsl/references/README.md create mode 100644 pkg/composableschemadsl/references/test_syntax_with_caveats.zed 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..98db4f6ec8 --- /dev/null +++ b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed @@ -0,0 +1,45 @@ +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 + + permission write = writer + permission view = reader + writer + permission view_as_fan = caveated_reader + writer + permission view_temporarily = temporary_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 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 + } + } +} From 8641b7675a715fdf83261d28c3b41a9135fa19f2 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 25 Nov 2024 12:52:47 -0700 Subject: [PATCH 10/21] Add some tests and regen --- .../parser/tests/basiccaveat.zed.expected | 4 ++-- .../tests/caveatwithkeywordparam.zed.expected | 6 ++--- .../parser/tests/complexcaveat.zed.expected | 12 +++++----- .../tests/invalidcaveatexpr.zed.expected | 4 ++-- .../parser/tests/multipleslashes.zed.expected | 4 ++-- .../parser/tests/superlarge.zed.expected | 6 ++--- .../parser/tests/test_happy_path.zed | 23 +++++++++++++++++++ .../parser/tests/test_happy_path.zed.expected | 23 +++++++++++++++++++ .../tests/test_relation_malformed_object.zed | 5 ++++ ...est_relation_malformed_object.zed.expected | 23 +++++++++++++++++++ .../tests/test_relation_missing_object.zed | 5 ++++ .../test_relation_missing_object.zed.expected | 23 +++++++++++++++++++ .../tests/test_relation_missing_relation.zed | 5 ++++ ...est_relation_missing_relation.zed.expected | 23 +++++++++++++++++++ .../tests/test_relation_missing_subject.zed | 5 ++++ ...test_relation_missing_subject.zed.expected | 23 +++++++++++++++++++ .../parser/tests/unclosedcaveat.zed.expected | 6 ++--- 17 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 pkg/composableschemadsl/parser/tests/test_happy_path.zed create mode 100644 pkg/composableschemadsl/parser/tests/test_happy_path.zed.expected create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_malformed_object.zed.expected create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_object.zed.expected create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_relation.zed.expected create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed create mode 100644 pkg/composableschemadsl/parser/tests/test_relation_missing_subject.zed.expected 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 => From 1e5db11a20b96180fbb4c92ec5ff930cbfdd2044 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 25 Nov 2024 12:53:00 -0700 Subject: [PATCH 11/21] Add tests to list --- pkg/composableschemadsl/parser/parser_test.go | 5 +++++ 1 file changed, 5 insertions(+) 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 { From 9ff430770d266d0dc20dd7e306392612bf97ec7c Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 6 Jan 2025 10:41:09 -0700 Subject: [PATCH 12/21] Add updates from most recent conversation --- .../references/test_syntax_with_caveats.zed | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed index 98db4f6ec8..29b53b9f2c 100644 --- a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed +++ b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed @@ -13,26 +13,31 @@ definition post { 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 temporary_reader {"selected_day": "tuesday"} + 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 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 + // TODO + post:one is view_as_fan user:beatrice when {"likes": true} // Positive assertion + post:one is not view_as_fan user:beatrice when {"likes": false} // Negative assertion + post:one is conditional 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"} + post:one is view_temporarily user:claire when {"current_day": "tuesday"} + post:one is not view_temporarily user:claire when {"current_day": "wednesday"} } expected { post:one view { From 0f1d566175370563db4efc2bff52282ac8b27580 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Mon, 6 Jan 2025 10:45:42 -0700 Subject: [PATCH 13/21] Add 'for' syntax for assertions --- .../references/test_syntax_with_caveats.zed | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed index 29b53b9f2c..0e3f767ab7 100644 --- a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed +++ b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed @@ -32,12 +32,12 @@ test DocumentCaveatedTest { } assertions { // TODO - post:one is view_as_fan user:beatrice when {"likes": true} // Positive assertion - post:one is not view_as_fan user:beatrice when {"likes": false} // Negative assertion - post:one is conditional view_as_fan user:beatrice // Conditional assertion + 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 user:claire when {"current_day": "tuesday"} - post:one is not view_temporarily user:claire when {"current_day": "wednesday"} + 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 { From e3735c42a535384b0059c07cd57886d023c109d2 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:31:48 -0700 Subject: [PATCH 14/21] Add all the new keywords --- pkg/composableschemadsl/lexer/lex_def.go | 6 ++++++ pkg/composableschemadsl/lexer/lex_test.go | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index 3d7b3b43df..d6232f89fe 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -82,6 +82,8 @@ var keywords = map[string]struct{}{ "import": {}, "all": {}, "any": {}, + // caveat syntax + "and": {}, // Test keywords "test": {}, "relationships": {}, @@ -89,6 +91,10 @@ var keywords = map[string]struct{}{ "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", ""}, From 0a37a6ffb9d8a4187f6c75af8d6a618c06f84deb Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:31:58 -0700 Subject: [PATCH 15/21] Gofumpt --- pkg/composableschemadsl/dslshape/dslshape.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index 34255a0f76..bb9edfcf16 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -216,10 +216,10 @@ const ( NodeTestPredicateName = "test-name" - NodeTestPredicateSubject = "subject" - NodeTestPredicateObject = "object" - NodeTestPredicateRelation = "relation" - NodeTestPredicatePermission = "permission" + NodeTestPredicateSubject = "subject" + NodeTestPredicateObject = "object" + NodeTestPredicateRelation = "relation" + NodeTestPredicatePermission = "permission" NodeTestPredicateAssertionType = "assertion-type" NodeTestPredicateCaveatName = "caveat-name" NodeTestPredicateCaveatContext = "caveat-context" From 8acd67ca452dbbcee62af2dfbcdc26433fb330a2 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:32:10 -0700 Subject: [PATCH 16/21] Remove a todo --- pkg/composableschemadsl/references/test_syntax_with_caveats.zed | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed index 0e3f767ab7..77de60712b 100644 --- a/pkg/composableschemadsl/references/test_syntax_with_caveats.zed +++ b/pkg/composableschemadsl/references/test_syntax_with_caveats.zed @@ -31,7 +31,6 @@ test DocumentCaveatedTest { post:one expiring_reader user:job with only_on_selected_day:{"selected_day": "wednesday"} and expiration:"2025-01-25" } assertions { - // TODO 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 From eeaa7e7b84afab10085aa81e7fcaa0258b5c224f Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:32:30 -0700 Subject: [PATCH 17/21] Update test assertion parsing with new syntax --- pkg/composableschemadsl/parser/parser.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 8334cb25f9..5a35cea780 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -879,10 +879,15 @@ func (p *sourceParser) consumeTestAssertion() AstNode { } assertionNode.Connect(dslshape.NodeTestPredicateObject, objectNode) + // is + if ok := p.consumeKeyword("is"); !ok { + return assertionNode + } + // assertion type - if _, ok := p.tryConsume(lexer.TokenTypeExclamationPoint); ok { + if ok := p.tryConsumeKeyword("not"); ok { assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "negative") -} else if _, ok := p.tryConsume(lexer.TokenTypeQuestionMark); ok { + } else if ok := p.tryConsumeKeyword("conditional"); ok { assertionNode.MustDecorate(dslshape.NodeTestPredicateAssertionType, "conditional") } else { // If no marker, it's a positive assertion @@ -896,6 +901,11 @@ func (p *sourceParser) consumeTestAssertion() AstNode { } assertionNode.MustDecorate(dslshape.NodeTestPredicatePermission, permission) + // for + if ok := p.consumeKeyword("for"); !ok { + return assertionNode + } + // subject consumption subjectNode, ok := p.consumeTestObject() if !ok { @@ -904,7 +914,7 @@ func (p *sourceParser) consumeTestAssertion() AstNode { assertionNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) // optional caveat context - if p.isToken(lexer.TokenTypeLeftBrace) { + if ok := p.tryConsumeKeyword("when"); ok { caveatContextNode, ok := p.consumeOpaqueBraceExpression() if !ok { return assertionNode From 8c94decaf618ef9cd0124a21818aa29c24cb5932 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:57:52 -0700 Subject: [PATCH 18/21] Add expiration to the list of keywords --- pkg/composableschemadsl/lexer/lex_def.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index d6232f89fe..3f8474e5de 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -84,6 +84,7 @@ var keywords = map[string]struct{}{ "any": {}, // caveat syntax "and": {}, + "expiration": {}, // Test keywords "test": {}, "relationships": {}, From 39cf9689320d81531626731f158b35dcbac5ee3f Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:58:09 -0700 Subject: [PATCH 19/21] Gofumpt --- pkg/composableschemadsl/lexer/lex_def.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index 3f8474e5de..ca81dc97b4 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -83,7 +83,7 @@ var keywords = map[string]struct{}{ "all": {}, "any": {}, // caveat syntax - "and": {}, + "and": {}, "expiration": {}, // Test keywords "test": {}, From f996744dbf0aef161562e26c542c26d1268c187c Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:58:23 -0700 Subject: [PATCH 20/21] Add expiration time and format --- pkg/composableschemadsl/dslshape/dslshape.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index bb9edfcf16..6875d54dc6 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -216,13 +216,14 @@ const ( NodeTestPredicateName = "test-name" - NodeTestPredicateSubject = "subject" - NodeTestPredicateObject = "object" - NodeTestPredicateRelation = "relation" - NodeTestPredicatePermission = "permission" - NodeTestPredicateAssertionType = "assertion-type" - NodeTestPredicateCaveatName = "caveat-name" - NodeTestPredicateCaveatContext = "caveat-context" + 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" From 93bd80d1c298ccb5e12913a15eaf66b68be8c706 Mon Sep 17 00:00:00 2001 From: Tanner Stirrat Date: Wed, 8 Jan 2025 16:58:46 -0700 Subject: [PATCH 21/21] Implement remaining caveat syntax --- pkg/composableschemadsl/parser/parser.go | 32 ++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/pkg/composableschemadsl/parser/parser.go b/pkg/composableschemadsl/parser/parser.go index 5a35cea780..53b64dd456 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -794,7 +794,12 @@ func (p *sourceParser) consumeTestRelation() AstNode { relationNode.Connect(dslshape.NodeTestPredicateSubject, subjectNode) // optional caveat consumption - if p.tryConsumeKeyword("with") { + 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 @@ -802,15 +807,38 @@ func (p *sourceParser) consumeTestRelation() AstNode { relationNode.MustDecorate(dslshape.NodeTestPredicateCaveatName, caveatName) // optional caveat context - if p.isToken(lexer.TokenTypeLeftBrace) { + 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 }