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

Corrected scim filter to support parenthesis/precedence filters #19

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
AND LogicalOperator = "and"
// OR is the logical operation or (||).
OR LogicalOperator = "or"

OPEN LogicalOperator = "("
)

// AttributeExpression represents an attribute expression/filter.
Expand Down Expand Up @@ -57,9 +59,10 @@ func (*AttributeExpression) exprNode() {}
// AttributePath represents an attribute path. Both URIPrefix and SubAttr are
// optional values and can be nil.
// e.g. urn:ietf:params:scim:schemas:core:2.0:User:name.givenName
// ^ ^ ^
// URIPrefix | SubAttribute
// AttributeName
//
// ^ ^ ^
// URIPrefix | SubAttribute
// AttributeName
type AttributePath struct {
URIPrefix *string
AttributeName string
Expand Down Expand Up @@ -100,10 +103,11 @@ type CompareOperator string

// Expression is a type to assign to implemented expressions.
// Valid expressions are:
// - ValuePath
// - AttributeExpression
// - LogicalExpression
// - NotExpression
// - ValuePath
// - AttributeExpression
// - LogicalExpression
// - NotExpression
// - PrecedenceExpression
type Expression interface {
exprNode()
}
Expand All @@ -123,6 +127,16 @@ func (*LogicalExpression) exprNode() {}
// LogicalOperator represents a logical operation such as 'and' / 'or'.
type LogicalOperator string

type PrecedenceExpression struct {
Expression Expression
}

func (e PrecedenceExpression) String() string {
return fmt.Sprintf("(%v)", e.Expression)
}

func (*PrecedenceExpression) exprNode() {}

// NotExpression represents an 'not' node.
type NotExpression struct {
Expression Expression
Expand All @@ -137,9 +151,10 @@ func (*NotExpression) exprNode() {}
// Path describes the target of a PATCH operation. Path can have an optional
// ValueExpression and SubAttribute.
// e.g. members[value eq "2819c223-7f76-453a-919d-413861904646"].displayName
// ^ ^ ^
// | ValueExpression SubAttribute
// AttributePath
//
// ^ ^ ^
// | ValueExpression SubAttribute
// AttributePath
type Path struct {
AttributePath AttributePath
ValueExpression Expression
Expand Down
13 changes: 13 additions & 0 deletions filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ func (p config) parseFilterValue(node *ast.Node) (Expression, error) {
return &NotExpression{
Expression: exp,
}, nil
case typ.FilterPrecedence:
children := node.Children()
if l := len(children); l != 1 {
return nil, invalidLengthError(typ.FilterPrecedence, 1, l)
}

exp, err := p.parseFilterOr(children[0])
if err != nil {
return nil, err
}
return &PrecedenceExpression{
Expression: exp,
}, nil
case typ.FilterOr:
return p.parseFilterOr(node)
default:
Expand Down
17 changes: 13 additions & 4 deletions filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package filter

import (
"fmt"
"strings"
"testing"
)

func ExampleParseFilter_and() {
fmt.Println(ParseFilter([]byte("title pr and userType eq \"Employee\"")))
ast, _ := ParseFilter([]byte("title pr and userType eq \"Employee\""))

fmt.Println(ast)
// Output:
// title pr and userType eq "Employee" <nil>
// title pr and userType eq "Employee"
}

func ExampleParseFilter_attrExp() {
Expand Down Expand Up @@ -38,7 +41,7 @@ func ExampleParseFilter_or() {
func ExampleParseFilter_parentheses() {
fmt.Println(ParseFilter([]byte("(emails.type eq \"work\")")))
// Output:
// emails.type eq "work" <nil>
// (emails.type eq "work") <nil>
}

func ExampleParseFilter_valuePath() {
Expand Down Expand Up @@ -99,7 +102,13 @@ func TestParseFilter(t *testing.T) {
"name pr or userName pr or title pr",
} {
t.Run(example, func(t *testing.T) {
if _, err := ParseFilter([]byte(example)); err != nil {

if strings.HasPrefix(example, "userType") {
fmt.Println("Test:" + example)
}
ast, err := ParseFilter([]byte(example))
fmt.Println(ast)
if err != nil {
t.Error(err)
}
})
Expand Down
45 changes: 35 additions & 10 deletions internal/grammar/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,21 @@ func FilterNot(p *ast.Parser) (*ast.Node, error) {
return p.Expect(ast.Capture{
Type: typ.FilterNot,
TypeStrings: typ.Stringer,
Value: op.And{
parser.CheckStringCI("not"),
op.MinZero(SP),
FilterParentheses,
Value: op.Or{
op.And{
parser.CheckStringCI("not"),
op.MinZero(SP),
'(',
FilterOr,
op.MinZero(SP),
')',
},
op.And{
parser.CheckStringCI("not"),
op.MinZero(SP),
// FilterValue,
FilterOr,
},
},
})
}
Expand All @@ -56,13 +67,27 @@ func FilterOr(p *ast.Parser) (*ast.Node, error) {
}

func FilterParentheses(p *ast.Parser) (*ast.Node, error) {
return p.Expect(op.And{
'(',
op.MinZero(SP),
FilterOr,
op.MinZero(SP),
')',
return p.Expect(ast.Capture{
Type: typ.FilterPrecedence,
TypeStrings: typ.Stringer,
Value: op.And{
'(',
op.MinZero(SP),
FilterOr,
op.MinZero(SP),
')',
},
})
/*
return p.Expect(op.And{
'(',
op.MinZero(SP),
FilterOr,
op.MinZero(SP),
')',
})

*/
}

func FilterValue(p *ast.Parser) (*ast.Node, error) {
Expand Down
4 changes: 2 additions & 2 deletions internal/grammar/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ func ExampleFilterParentheses() {
p("userType eq \"Employee\" and (emails.type eq \"work\")")
p("userType eq \"Employee\" and (emails co \"example.com\" or emails.value co \"example.org\")")
// Output:
// ["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","userType"]]],["CompareOp","eq"],["String","\"Employee\""]]],["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"],["AttrName","type"]]],["CompareOp","eq"],["String","\"work\""]]]]]]]]]]] <nil>
// ["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","userType"]]],["CompareOp","eq"],["String","\"Employee\""]]],["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"]]],["CompareOp","co"],["String","\"example.com\""]]]]],["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"],["AttrName","value"]]],["CompareOp","co"],["String","\"example.org\""]]]]]]]]]]] <nil>
// ["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","userType"]]],["CompareOp","eq"],["String","\"Employee\""]]],["FilterPrecedence",[["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"],["AttrName","type"]]],["CompareOp","eq"],["String","\"work\""]]]]]]]]]]]]] <nil>
// ["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","userType"]]],["CompareOp","eq"],["String","\"Employee\""]]],["FilterPrecedence",[["FilterOr",[["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"]]],["CompareOp","co"],["String","\"example.com\""]]]]],["FilterAnd",[["AttrExp",[["AttrPath",[["AttrName","emails"],["AttrName","value"]]],["CompareOp","co"],["String","\"example.org\""]]]]]]]]]]]]] <nil>
}
2 changes: 2 additions & 0 deletions internal/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
FilterOr
FilterAnd
FilterNot
FilterPrecedence

Path

Expand Down Expand Up @@ -42,6 +43,7 @@ var Stringer = []string{
"FilterOr",
"FilterAnd",
"FilterNot",
"FilterPrecedence",

"Path",

Expand Down