diff --git a/pkg/composableschemadsl/dslshape/dslshape.go b/pkg/composableschemadsl/dslshape/dslshape.go index a9fd101bf5..57a850e106 100644 --- a/pkg/composableschemadsl/dslshape/dslshape.go +++ b/pkg/composableschemadsl/dslshape/dslshape.go @@ -37,6 +37,8 @@ const ( NodeTypeCaveatTypeReference // A type reference for a caveat parameter. NodeTypeImport + NodeTypePartial + NodeTypePartialReference // A location where a partial is referenced ) const ( @@ -195,4 +197,14 @@ const ( // NodeTypeImport // NodeImportPredicatePath = "import-path" + + // + // NodeTypePartial + // + NodePartialPredicateName = "partial-name" + + // + // NodeTypePartialReference + // + NodePartialReferencePredicateName = "partial-reference-name" ) diff --git a/pkg/composableschemadsl/dslshape/zz_generated.nodetype_string.go b/pkg/composableschemadsl/dslshape/zz_generated.nodetype_string.go index 125fba8abc..42d4acbe53 100644 --- a/pkg/composableschemadsl/dslshape/zz_generated.nodetype_string.go +++ b/pkg/composableschemadsl/dslshape/zz_generated.nodetype_string.go @@ -28,11 +28,13 @@ func _() { _ = x[NodeTypeNilExpression-17] _ = x[NodeTypeCaveatTypeReference-18] _ = x[NodeTypeImport-19] + _ = x[NodeTypePartial-20] + _ = x[NodeTypePartialReference-21] } -const _NodeType_name = "NodeTypeErrorNodeTypeFileNodeTypeCommentNodeTypeDefinitionNodeTypeCaveatDefinitionNodeTypeCaveatParameterNodeTypeCaveatExpressionNodeTypeRelationNodeTypePermissionNodeTypeTypeReferenceNodeTypeSpecificTypeReferenceNodeTypeCaveatReferenceNodeTypeUnionExpressionNodeTypeIntersectExpressionNodeTypeExclusionExpressionNodeTypeArrowExpressionNodeTypeIdentifierNodeTypeNilExpressionNodeTypeCaveatTypeReferenceNodeTypeImport" +const _NodeType_name = "NodeTypeErrorNodeTypeFileNodeTypeCommentNodeTypeDefinitionNodeTypeCaveatDefinitionNodeTypeCaveatParameterNodeTypeCaveatExpressionNodeTypeRelationNodeTypePermissionNodeTypeTypeReferenceNodeTypeSpecificTypeReferenceNodeTypeCaveatReferenceNodeTypeUnionExpressionNodeTypeIntersectExpressionNodeTypeExclusionExpressionNodeTypeArrowExpressionNodeTypeIdentifierNodeTypeNilExpressionNodeTypeCaveatTypeReferenceNodeTypeImportNodeTypePartialNodeTypePartialReference" -var _NodeType_index = [...]uint16{0, 13, 25, 40, 58, 82, 105, 129, 145, 163, 184, 213, 236, 259, 286, 313, 336, 354, 375, 402, 416} +var _NodeType_index = [...]uint16{0, 13, 25, 40, 58, 82, 105, 129, 145, 163, 184, 213, 236, 259, 286, 313, 336, 354, 375, 402, 416, 431, 455} func (i NodeType) String() string { if i < 0 || i >= NodeType(len(_NodeType_index)-1) { diff --git a/pkg/composableschemadsl/lexer/lex_def.go b/pkg/composableschemadsl/lexer/lex_def.go index 6c5be862a7..165759e8ad 100644 --- a/pkg/composableschemadsl/lexer/lex_def.go +++ b/pkg/composableschemadsl/lexer/lex_def.go @@ -81,6 +81,7 @@ var keywords = map[string]struct{}{ "import": {}, "all": {}, "any": {}, + "partial": {}, } // 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 eac715f836..2e02be67f7 100644 --- a/pkg/composableschemadsl/lexer/lex_test.go +++ b/pkg/composableschemadsl/lexer/lex_test.go @@ -65,6 +65,7 @@ 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", "partial", []Lexeme{{TokenTypeKeyword, 0, "partial", ""}, 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 82a6903328..a1650c2aca 100644 --- a/pkg/composableschemadsl/parser/parser.go +++ b/pkg/composableschemadsl/parser/parser.go @@ -65,6 +65,9 @@ Loop: case p.isKeyword("import"): rootNode.Connect(dslshape.NodePredicateChild, p.consumeImport()) + case p.isKeyword("partial"): + rootNode.Connect(dslshape.NodePredicateChild, p.consumePartial()) + default: p.emitErrorf("Unexpected token at root level: %v", p.currentToken.Kind) break Loop @@ -264,10 +267,39 @@ func (p *sourceParser) consumeDefinition() AstNode { defNode.MustDecorate(dslshape.NodeDefinitionPredicateName, definitionName) + p.consumeDefinitionOrPartialImpl(defNode) + + return defNode +} + +// consumePartial attempts to consume a single schema partial. +// ```partial somepartial { ... }``` +func (p *sourceParser) consumePartial() AstNode { + partialNode := p.startNode(dslshape.NodeTypePartial) + defer p.mustFinishNode() + + // partial ... + p.consumeKeyword("partial") + definitionName, ok := p.consumeTypePath() + if !ok { + return partialNode + } + + partialNode.MustDecorate(dslshape.NodePartialPredicateName, definitionName) + + p.consumeDefinitionOrPartialImpl(partialNode) + + return partialNode +} + +// consumeDefinitionOrPartialImpl does the work of parsing and consuming +// the internals of a given definition or partial. +// Definitions and partials have the same set of allowable internals. +func (p *sourceParser) consumeDefinitionOrPartialImpl(node AstNode) AstNode { // { - _, ok = p.consume(lexer.TokenTypeLeftBrace) + _, ok := p.consume(lexer.TokenTypeLeftBrace) if !ok { - return defNode + return node } // Relations and permissions. @@ -281,10 +313,13 @@ func (p *sourceParser) consumeDefinition() AstNode { // permission ... switch { case p.isKeyword("relation"): - defNode.Connect(dslshape.NodePredicateChild, p.consumeRelation()) + node.Connect(dslshape.NodePredicateChild, p.consumeRelation()) case p.isKeyword("permission"): - defNode.Connect(dslshape.NodePredicateChild, p.consumePermission()) + node.Connect(dslshape.NodePredicateChild, p.consumePermission()) + + case p.isToken(lexer.TokenTypeEllipsis): + node.Connect(dslshape.NodePredicateChild, p.consumePartialReference()) } ok := p.consumeStatementTerminator() @@ -293,7 +328,7 @@ func (p *sourceParser) consumeDefinition() AstNode { } } - return defNode + return node } // consumeRelation consumes a relation. @@ -597,6 +632,21 @@ func (p *sourceParser) tryConsumeNilExpression() (AstNode, bool) { return node, true } +func (p *sourceParser) consumePartialReference() AstNode { + partialReferenceNode := p.startNode(dslshape.NodeTypePartialReference) + defer p.mustFinishNode() + + p.consume(lexer.TokenTypeEllipsis) + identifier, ok := p.consumeIdentifier() + if !ok { + return partialReferenceNode + } + + partialReferenceNode.MustDecorate(dslshape.NodePartialReferencePredicateName, identifier) + + return partialReferenceNode +} + func (p *sourceParser) consumeImport() AstNode { importNode := p.startNode(dslshape.NodeTypeImport) defer p.mustFinishNode() diff --git a/pkg/composableschemadsl/parser/parser_test.go b/pkg/composableschemadsl/parser/parser_test.go index 63aa4265e9..ca33fbdb4d 100644 --- a/pkg/composableschemadsl/parser/parser_test.go +++ b/pkg/composableschemadsl/parser/parser_test.go @@ -128,6 +128,11 @@ func TestParser(t *testing.T) { {"local imports with unterminated string on import test", "localimport_with_unterminated_string"}, {"local imports with mismatched quotes on import test", "localimport_with_mismatched_quotes"}, {"local imports with keyword in import path test", "localimport_import_path_with_keyword"}, + {"top-level block with unrecognized keyword", "nonsense_top_level_block"}, + {"partials happy path", "partials"}, + {"partials with malformed partial reference", "partials_with_malformed_partial_reference"}, + {"partials with malformed reference splat", "partials_with_malformed_reference_splat"}, + {"partials with malformed partial block", "partials_with_malformed_partial_block"}, } for _, test := range parserTests { diff --git a/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed b/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed new file mode 100644 index 0000000000..b74da732b2 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed @@ -0,0 +1,4 @@ +nonsense thing { + relation: user + permission view = user +} diff --git a/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed.expected b/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed.expected new file mode 100644 index 0000000000..f58ac982aa --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/nonsense_top_level_block.zed.expected @@ -0,0 +1,11 @@ +NodeTypeFile + end-rune = -1 + input-source = top-level block with unrecognized keyword + start-rune = 0 + child-node => + NodeTypeError + end-rune = -1 + error-message = Unexpected token at root level: TokenTypeIdentifier + error-source = nonsense + input-source = top-level block with unrecognized keyword + start-rune = 0 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/partials.zed b/pkg/composableschemadsl/parser/tests/partials.zed new file mode 100644 index 0000000000..e9fafacd8d --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials.zed @@ -0,0 +1,10 @@ +partial view_partial { + relation user: user + permission view = user +} + +definition user {} + +definition resource { + ...view_partial +} diff --git a/pkg/composableschemadsl/parser/tests/partials.zed.expected b/pkg/composableschemadsl/parser/tests/partials.zed.expected new file mode 100644 index 0000000000..279ad45483 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials.zed.expected @@ -0,0 +1,54 @@ +NodeTypeFile + end-rune = 134 + input-source = partials happy path + start-rune = 0 + child-node => + NodeTypePartial + end-rune = 70 + input-source = partials happy path + partial-name = view_partial + start-rune = 0 + child-node => + NodeTypeRelation + end-rune = 43 + input-source = partials happy path + relation-name = user + start-rune = 25 + allowed-types => + NodeTypeTypeReference + end-rune = 43 + input-source = partials happy path + start-rune = 40 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 43 + input-source = partials happy path + start-rune = 40 + type-name = user + NodeTypePermission + end-rune = 68 + input-source = partials happy path + relation-name = view + start-rune = 47 + compute-expression => + NodeTypeIdentifier + end-rune = 68 + identifier-value = user + input-source = partials happy path + start-rune = 65 + NodeTypeDefinition + definition-name = user + end-rune = 90 + input-source = partials happy path + start-rune = 73 + NodeTypeDefinition + definition-name = resource + end-rune = 133 + input-source = partials happy path + start-rune = 93 + child-node => + NodeTypePartialReference + end-rune = 131 + input-source = partials happy path + partial-reference-name = view_partial + start-rune = 117 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed new file mode 100644 index 0000000000..d65073d83e --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed @@ -0,0 +1,10 @@ +partial view_partial { + relation user: user + permission view = user + + +definition user {} + +definition resource { + ...view_partial +} diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed.expected b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed.expected new file mode 100644 index 0000000000..6c3325c126 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_block.zed.expected @@ -0,0 +1,60 @@ +NodeTypeFile + end-rune = 133 + input-source = partials with malformed partial block + start-rune = 0 + child-node => + NodeTypePartial + end-rune = 69 + input-source = partials with malformed partial block + partial-name = view_partial + start-rune = 0 + child-node => + NodeTypeRelation + end-rune = 43 + input-source = partials with malformed partial block + relation-name = user + start-rune = 25 + allowed-types => + NodeTypeTypeReference + end-rune = 43 + input-source = partials with malformed partial block + start-rune = 40 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 43 + input-source = partials with malformed partial block + start-rune = 40 + type-name = user + NodeTypePermission + end-rune = 68 + input-source = partials with malformed partial block + relation-name = view + start-rune = 47 + compute-expression => + NodeTypeIdentifier + end-rune = 68 + identifier-value = user + input-source = partials with malformed partial block + start-rune = 65 + NodeTypeError + end-rune = 69 + error-message = Expected end of statement or definition, found: TokenTypeKeyword + error-source = definition + input-source = partials with malformed partial block + start-rune = 72 + NodeTypeDefinition + definition-name = user + end-rune = 89 + input-source = partials with malformed partial block + start-rune = 72 + NodeTypeDefinition + definition-name = resource + end-rune = 132 + input-source = partials with malformed partial block + start-rune = 92 + child-node => + NodeTypePartialReference + end-rune = 130 + input-source = partials with malformed partial block + partial-reference-name = view_partial + start-rune = 116 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed new file mode 100644 index 0000000000..7324fb55c9 --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed @@ -0,0 +1,10 @@ +partial view_partial { + relation user: user + permission view = user +} + +definition user {} + +definition resource { + ...view_partial and_something_else +} diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed.expected b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed.expected new file mode 100644 index 0000000000..de11b1268f --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_partial_reference.zed.expected @@ -0,0 +1,66 @@ +NodeTypeFile + end-rune = 131 + input-source = partials with malformed partial reference + start-rune = 0 + child-node => + NodeTypePartial + end-rune = 70 + input-source = partials with malformed partial reference + partial-name = view_partial + start-rune = 0 + child-node => + NodeTypeRelation + end-rune = 43 + input-source = partials with malformed partial reference + relation-name = user + start-rune = 25 + allowed-types => + NodeTypeTypeReference + end-rune = 43 + input-source = partials with malformed partial reference + start-rune = 40 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 43 + input-source = partials with malformed partial reference + start-rune = 40 + type-name = user + NodeTypePermission + end-rune = 68 + input-source = partials with malformed partial reference + relation-name = view + start-rune = 47 + compute-expression => + NodeTypeIdentifier + end-rune = 68 + identifier-value = user + input-source = partials with malformed partial reference + start-rune = 65 + NodeTypeDefinition + definition-name = user + end-rune = 90 + input-source = partials with malformed partial reference + start-rune = 73 + NodeTypeDefinition + definition-name = resource + end-rune = 131 + input-source = partials with malformed partial reference + start-rune = 93 + child-node => + NodeTypePartialReference + end-rune = 131 + input-source = partials with malformed partial reference + partial-reference-name = view_partial + start-rune = 117 + NodeTypeError + end-rune = 131 + error-message = Expected end of statement or definition, found: TokenTypeIdentifier + error-source = and_something_else + input-source = partials with malformed partial reference + start-rune = 133 + NodeTypeError + end-rune = 131 + error-message = Unexpected token at root level: TokenTypeIdentifier + error-source = and_something_else + input-source = partials with malformed partial reference + start-rune = 133 \ No newline at end of file diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed b/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed new file mode 100644 index 0000000000..829f1e881d --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed @@ -0,0 +1,10 @@ +partial view_partial { + relation user: user + permission view = user +} + +definition user {} + +definition resource { + ..view_partial +} diff --git a/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed.expected b/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed.expected new file mode 100644 index 0000000000..f78c1ecc1a --- /dev/null +++ b/pkg/composableschemadsl/parser/tests/partials_with_malformed_reference_splat.zed.expected @@ -0,0 +1,61 @@ +NodeTypeFile + end-rune = 113 + input-source = partials with malformed reference splat + start-rune = 0 + child-node => + NodeTypePartial + end-rune = 70 + input-source = partials with malformed reference splat + partial-name = view_partial + start-rune = 0 + child-node => + NodeTypeRelation + end-rune = 43 + input-source = partials with malformed reference splat + relation-name = user + start-rune = 25 + allowed-types => + NodeTypeTypeReference + end-rune = 43 + input-source = partials with malformed reference splat + start-rune = 40 + type-ref-type => + NodeTypeSpecificTypeReference + end-rune = 43 + input-source = partials with malformed reference splat + start-rune = 40 + type-name = user + NodeTypePermission + end-rune = 68 + input-source = partials with malformed reference splat + relation-name = view + start-rune = 47 + compute-expression => + NodeTypeIdentifier + end-rune = 68 + identifier-value = user + input-source = partials with malformed reference splat + start-rune = 65 + NodeTypeDefinition + definition-name = user + end-rune = 90 + input-source = partials with malformed reference splat + start-rune = 73 + NodeTypeDefinition + definition-name = resource + end-rune = 113 + input-source = partials with malformed reference splat + start-rune = 93 + child-node => + NodeTypeError + end-rune = 113 + error-message = Expected end of statement or definition, found: TokenTypePeriod + error-source = . + input-source = partials with malformed reference splat + start-rune = 117 + NodeTypeError + end-rune = 113 + error-message = Unexpected token at root level: TokenTypePeriod + error-source = . + input-source = partials with malformed reference splat + start-rune = 117 \ No newline at end of file