Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multi-line formatting for permission expressions #1629

Merged
21 changes: 21 additions & 0 deletions pkg/dsl/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type Parser struct {
l *lexer.Lexer
// the current token being processed
currentToken token.Token
// the token before currentToken
previousToken token.Token
// the next token after currentToken
peekToken token.Token
// a slice of error messages that are generated during parsing
Expand Down Expand Up @@ -82,6 +84,8 @@ func (p *Parser) next() {
peek := p.l.NextToken()
// if the token is not an ignored token (e.g. whitespace or comments), update the currentToken and peekToken fields and exit the loop
if !token.IsIgnores(peek.Type) {
// set the previousToken before changing currentToken
p.previousToken = p.currentToken
// set the currentToken field to the previous peekToken value
p.currentToken = p.peekToken
// set the peekToken field to the new peek value
Expand Down Expand Up @@ -118,6 +122,18 @@ func (p *Parser) currentTokenIs(tokens ...token.Type) bool {
return false
}

// previousTokenIs checks if the Parser's previousToken type is any of the given types
func (p *Parser) previousTokenIs(tokens ...token.Type) bool {
for _, t := range tokens {
if p.previousToken.Type == t {
// if a match is found, return true
return true
}
}
// if no match is found, return false
return false
}

// peekTokenIs checks if the Parser's peekToken is any of the given token types
func (p *Parser) peekTokenIs(tokens ...token.Type) bool {
// iterate through the given token types and check if any of them match the peekToken's type
Expand Down Expand Up @@ -588,6 +604,11 @@ func (p *Parser) parseExpression(precedence int) (ast.Expression, error) {
var exp ast.Expression
var err error

if p.currentTokenIs(token.NEWLINE) && p.previousTokenIs(token.LP, token.AND, token.OR, token.NOT, token.ASSIGN) {
// advance to the next token
p.next()
}

if p.currentTokenIs(token.LP) {
p.next() // Consume the left parenthesis.
exp, err = p.parseExpression(LOWEST)
Expand Down
152 changes: 152 additions & 0 deletions pkg/dsl/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -849,5 +849,157 @@ var _ = Describe("parser", func() {
Expect(es.Expression.(*ast.InfixExpression).Right.(*ast.InfixExpression).Left.(*ast.Identifier).String()).Should(Equal("parent.admin"))
Expect(es.Expression.(*ast.InfixExpression).Right.(*ast.InfixExpression).Right.(*ast.Identifier).String()).Should(Equal("parent.member"))
})

It("Case 24 - Multi-line Permission Expression w/ Rule", func() {
pr := NewParser(`
entity account {
relation owner @user
attribute balance float

permission withdraw = check_balance(request.amount, balance) and
owner
}

rule check_balance(amount float, balance float) {
balance >= amount && amount <= 5000
}
`)

schema, err := pr.Parse()
Expect(err).ShouldNot(HaveOccurred())

st := schema.Statements[0].(*ast.EntityStatement)

Expect(st.Name.Literal).Should(Equal("account"))

r1 := st.RelationStatements[0].(*ast.RelationStatement)
Expect(r1.Name.Literal).Should(Equal("owner"))

for _, a := range r1.RelationTypes {
Expect(a.Type.Literal).Should(Equal("user"))
}

a1 := st.AttributeStatements[0].(*ast.AttributeStatement)
Expect(a1.Name.Literal).Should(Equal("balance"))
Expect(a1.AttributeType.Type.Literal).Should(Equal("float"))

p1 := st.PermissionStatements[0].(*ast.PermissionStatement)
Expect(p1.Name.Literal).Should(Equal("withdraw"))

es1 := p1.ExpressionStatement.(*ast.ExpressionStatement)

Expect(es1.Expression.(*ast.InfixExpression).Left.(*ast.Call).String()).Should(Equal("check_balance(request.amount, balance)"))
Expect(es1.Expression.(*ast.InfixExpression).Right.(*ast.Identifier).String()).Should(Equal("owner"))

rs1 := schema.Statements[1].(*ast.RuleStatement)

Expect(rs1.Name.Literal).Should(Equal("check_balance"))
Expect(rs1.Expression).Should(Equal("\nbalance >= amount && amount <= 5000\n\t\t"))
})

It("Case 25 - Multi-line Permission Expression w/ Rule", func() {
pr := NewParser(`
entity account {
relation owner @user
attribute balance float

permission withdraw =
check_balance(request.amount, balance) and owner
}

rule check_balance(amount float, balance float) {
balance >= amount && amount <= 5000
}
`)

schema, err := pr.Parse()
Expect(err).ShouldNot(HaveOccurred())

st := schema.Statements[0].(*ast.EntityStatement)

Expect(st.Name.Literal).Should(Equal("account"))

r1 := st.RelationStatements[0].(*ast.RelationStatement)
Expect(r1.Name.Literal).Should(Equal("owner"))

for _, a := range r1.RelationTypes {
Expect(a.Type.Literal).Should(Equal("user"))
}

a1 := st.AttributeStatements[0].(*ast.AttributeStatement)
Expect(a1.Name.Literal).Should(Equal("balance"))
Expect(a1.AttributeType.Type.Literal).Should(Equal("float"))

p1 := st.PermissionStatements[0].(*ast.PermissionStatement)
Expect(p1.Name.Literal).Should(Equal("withdraw"))

es1 := p1.ExpressionStatement.(*ast.ExpressionStatement)

Expect(es1.Expression.(*ast.InfixExpression).Left.(*ast.Call).String()).Should(Equal("check_balance(request.amount, balance)"))
Expect(es1.Expression.(*ast.InfixExpression).Right.(*ast.Identifier).String()).Should(Equal("owner"))

rs1 := schema.Statements[1].(*ast.RuleStatement)

Expect(rs1.Name.Literal).Should(Equal("check_balance"))
Expect(rs1.Expression).Should(Equal("\nbalance >= amount && amount <= 5000\n\t\t"))
})

It("Case 26 - Multi-line Permission Expression w/ Rule - should fail", func() {
pr := NewParser(`
entity account {
relation owner @user
attribute balance float

permission withdraw = check_balance(request.amount, balance)
owner
}

rule check_balance(amount float, balance float) {
balance >= amount && amount <= 5000
}
`)

_, err := pr.Parse()
Expect(err).Should(HaveOccurred())
})
tolgaOzen marked this conversation as resolved.
Show resolved Hide resolved

It("Case 26 - Multi-line Permission Complex Expression w/ Rule", func() {
pr := NewParser(`
entity report {
relation parent @organization
relation team @team
attribute confidentiality_level double

permission view =
confidentiality_level_high(confidentiality_level) and
parent.director or
confidentiality_level_medium_high(confidentiality_level) and
(parent.director or team.lead) or
confidentiality_level_medium(confidentiality_level) and (team.lead or team.member) or
confidentiality_level_low(confidentiality_level) and
parent.member
permission edit = team.lead
}

rule confidentiality_level_high(confidentiality_level double) {
confidentiality_level == 4.0
}

rule confidentiality_level_medium_high(confidentiality_level double) {
confidentiality_level == 3.0
}

rule confidentiality_level_medium(confidentiality_level double) {
confidentiality_level == 2.0
}

rule confidentiality_level_low(confidentiality_level double) {
confidentiality_level == 1.0
}
`)

_, err := pr.Parse()
Expect(err).ShouldNot(HaveOccurred())
})
tolgaOzen marked this conversation as resolved.
Show resolved Hide resolved
})
})
Binary file modified playground/public/play.wasm
Binary file not shown.
Loading