Commit 14b24b7

mokha <mokha@cisco.com>
2019-05-10 21:36:55
update rules to match ABNF
1 parent ef48a24
Changed files (3)
lib
scim
spec
lib/scim/kit/v2/parser.rb
@@ -37,8 +37,42 @@ subAttr   = "." ATTRNAME
            ; a sub-attribute of a complex attribute
 =end
       class Parser < Parslet::Parser
+        root :filter
+        rule(:filter) { (attrExp | logExp | valuePath) | (lparen >> filter >> rparen) }
+        rule(:valuePath) { attrPath >> lsquare_bracket >> valFilter >> rsquare_bracket }
+        rule(:valFilter) { logExp | attrExp | (not_op? >> lparen >> valFilter >> rparen) }
+        rule(:attrExp) { (attrPath >> space >> presence) | (attrPath >> space >> compareOp >> space >> quote >> compValue >> quote) }
+        rule(:logExp) { filter >> space >> (and_op | or_op) >> space >> filter }
+        rule(:compValue) { falsey | null | truthy | number | string }
+        rule(:compareOp) { equal | not_equal | contains | starts_with | ends_with | greater_than | less_than | less_than_equals | greater_than_equals }
+        rule(:attrPath) { scim_schema_uri | attrname >> subAttr.maybe }
+        rule(:attrname) { nameChar.repeat(1) }
+        rule(:nameChar) { hyphen | underscore | digit | alpha }
+        rule(:subAttr) { dot >> attrname }
+        rule(:presence) { str('pr') }
+        rule(:and_op) { str('and') }
+        rule(:or_op) { str('or') }
+        rule(:not_op) { str('not') }
+        rule(:not_op?) { not_op.maybe }
+        rule(:falsey) { str('false') }
+        rule(:truthy) { str('true') }
+        rule(:null) { str('null') }
+        rule(:number) { digit.repeat(1) }
+        rule(:scim_schema_uri) { (alpha | digit | dot | colon).repeat(1) }
+        rule(:equal) { str("eq") }
+        rule(:not_equal) { str("ne") }
+        rule(:contains) { str("co") }
+        rule(:starts_with) { str("sw") }
+        rule(:ends_with) { str("ew") }
+        rule(:greater_than) { str("gt") }
+        rule(:less_than) { str("lt") }
+        rule(:greater_than_equals) { str("ge") }
+        rule(:less_than_equals) { str("le") }
+        rule(:string) { (alpha | single_quote).repeat(1) }
         rule(:lparen) { str('(') >> space? }
         rule(:rparen) { str(')') >> space? }
+        rule(:lsquare_bracket) { str('[') >> space? }
+        rule(:rsquare_bracket) { str(']') >> space? }
         rule(:digit) { match(/\d/) }
         rule(:quote) { str('"') }
         rule(:single_quote) { str("'") }
@@ -48,36 +82,7 @@ subAttr   = "." ATTRNAME
         rule(:dot) { str('.') }
         rule(:colon) { str(':') }
         rule(:hyphen) { str('-') }
-
-        rule(:attribute) { uri | attribute_name }
-        rule(:attribute_name) { alpha.repeat(1) | dot }
-        rule(:uri) { (alpha | digit | dot | colon).repeat(1) }
-        rule(:date) { (alpha | digit | dot | colon | hyphen).repeat(1) }
-        rule(:string) { (alpha | single_quote).repeat(1) }
-
-        rule(:operator) { equal | not_equal | contains | starts_with | ends_with | greater_than | less_than | less_than_equals | greater_than_equals }
-        rule(:equal) { str("eq") }
-        rule(:not_equal) { str("ne") }
-        rule(:contains) { str("co") }
-        rule(:starts_with) { str("sw") }
-        rule(:ends_with) { str("ew") }
-        rule(:greater_than) { str("gt") }
-        rule(:less_than) { str("lt") }
-        rule(:greater_than_equals) { str("ge") }
-        rule(:less_than_equals) { str("le") }
-
-        rule(:quoted_value) { quote >> value.as(:right) >> quote }
-        rule(:value) { (string | date | uri).repeat(1) }
-
-        rule(:filter) { attribute.as(:left) >> space >> operator.as(:operator) >> space >> quoted_value }
-        root :filter
-
-        def pretty_parse(*args)
-          puts *args.inspect
-          parse(*args)
-        rescue Parslet::ParseFailed => error
-          puts error.parse_failure_cause.ascii_tree
-        end
+        rule(:underscore) { str('_') }
       end
     end
   end
spec/scim/kit/v2/parser_spec.rb
@@ -28,53 +28,102 @@ RSpec.describe Scim::Kit::V2::Parser do
         "2011-05-13T04:42:34Z",
         "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
       ].each do |value|
-        context "#{attribute} #{operator} #{value}" do
-          let(:result) { subject.pretty_parse(%Q(#{attribute} #{operator} \"#{value}\")) }
-
-          specify { expect(result).to be_present }
-          specify { expect(result[:left].to_s).to eql(attribute) }
-          specify { expect(result[:operator].to_s).to eql(operator) }
-          specify { expect(result[:right].to_s).to eql(value) }
-        end
+        specify { expect(subject).to parse(%Q(#{attribute} #{operator} \"#{value}\")) }
       end
     end
   end
 
-  context "match uri" do
-    specify { expect(subject.uri.parse("urn:ietf:params:scim:schemas:core:2.0:User:userName")).to be_present }
-  end
-
-  context "title pr and userType eq \"Employee\"" do
-    let(:result) { subject.pretty_parse(%Q(title pr and userType eq "Employee")) }
+  specify { expect(subject).to parse(%Q(title pr and userType eq "Employee")) }
 
-    specify { expect(result).to be_present }
-    specify { expect(result[:left].to_s).to eql(attribute) }
-    specify { expect(result[:operator].to_s).to eql(operator) }
-    specify { expect(result[:right].to_s).to eql(value) }
+  [
+    'emails[type eq "work" and value co "@example.com"]',
+  ].each do |x|
+    specify { expect(subject.valuePath).to parse(x) }
   end
-end
 
+  [
+    'firstName eq "Tsuyoshi"',
+    'firstName pr',
+    'firstName eq "Tsuyoshi" and lastName eq "Garret"'
+  ].each do |x|
+    specify { expect(subject.valFilter).to parse(x) }
+  end
 
-=begin
-filter=title pr
+  [
+    'firstName eq "Tsuyoshi"',
+    'firstName pr',
+  ].each do |x|
+    specify { expect(subject.attrExp).to parse(x) }
+  end
 
-filter=title pr and userType eq "Employee"
+  specify { expect(subject.attrExp).not_to parse('firstName eq "Tsuyoshi" and lastName eq "Garret"') }
 
-filter=title pr or userType eq "Intern"
+  [
+    'firstName eq "Tsuyoshi" and lastName eq "Garret"'
+  ].each do |x|
+    specify { expect(subject.logExp).to parse(x) }
+  end
 
-filter=
+  ['false', 'null', 'true', '1', 'hello'].each do |x|
+    specify { expect(subject.compValue).to parse(x) }
+  end
 
-filter=userType eq "Employee" and (emails co "example.com" or
-  emails.value co "example.org")
+  ['eq', 'ne', 'co', 'sw', 'ew', 'gt', 'lt', 'ge', 'le'].each do |x|
+    specify { expect(subject.compareOp).to parse(x) }
+  end
 
-filter=userType ne "Employee" and not (emails co "example.com" or
-  emails.value co "example.org")
+  [
+    'userName',
+    'user_name',
+    'user-name',
+    'username1',
+    'name.familyName',
+    'urn:ietf:params:scim:schemas:core:2.0:User:userName',
+    'urn:ietf:params:scim:schemas:core:2.0:User:name.familyName',
+    'meta.lastModified',
+    'schemas',
+  ].each do |x|
+    specify { expect(subject.attrPath).to parse(x) }
+  end
 
-filter=userType eq "Employee" and (emails.type eq "work")
+  [
+    'userName',
+    'user_name',
+    'user-name',
+    'username1',
+    'schemas',
+  ].each do |x|
+    specify { expect(subject.attrname).to parse(x) }
+  end
+  ['-', '_', '0', 'a'].each { |x| specify { expect(subject.nameChar).to parse(x) } }
+  specify { expect(subject.subAttr).to parse('.name') }
+  specify { expect(subject.presence).to parse('pr') }
+  specify { expect(subject.and_op).to parse('and') }
+  specify { expect(subject.or_op).to parse('or') }
+  specify { expect(subject.not_op).to parse('not') }
+  specify { expect(subject.falsey).to parse('false') }
+  specify { expect(subject.truthy).to parse('true') }
+  specify { expect(subject.null).to parse('null') }
+  1.upto(100).each { |n| specify { expect(subject.number).to parse(n.to_s) } }
 
-filter=userType eq "Employee" and emails[type eq "work" and
-  value co "@example.com"]
+  [
+    'urn:ietf:params:scim:schemas:core:2.0:User:userName',
+    'urn:ietf:params:scim:schemas:core:2.0:User:name.familyName',
+  ].each do |x|
+    specify { expect(subject.scim_schema_uri).to parse(x) }
+  end
 
-filter=emails[type eq "work" and value co "@example.com"] or
-  ims[type eq "xmpp" and value co "@foo.com"]
-=end
+  [
+    'title pr',
+    'title pr and userType eq "Employee"',
+    'title pr or userType eq "Intern"',
+    '',
+    'userType eq "Employee" and (emails co "example.com" or emails.value co "example.org")',
+    'userType ne "Employee" and not (emails co "example.com" or emails.value co "example.org")',
+    'userType eq "Employee" and (emails.type eq "work") ',
+    'userType eq "Employee" and emails[type eq "work" and value co "@example.com"]',
+    'emails[type eq "work" and value co "@example.com"] or ims[type eq "xmpp" and value co "@foo.com"]',
+  ].each do |x|
+    specify { expect(subject).to parse(x) }
+  end
+end
spec/spec_helper.rb
@@ -5,6 +5,8 @@ require 'scim/kit'
 require 'ffaker'
 require 'json'
 require 'byebug'
+require 'parslet/convenience'
+require 'parslet/rig/rspec'
 require 'webmock/rspec'
 
 Scim::Kit.logger = Logger.new('/dev/null')