diff --git a/src/odata.pegjs b/src/odata.pegjs index af60680..dbee3f6 100644 --- a/src/odata.pegjs +++ b/src/odata.pegjs @@ -218,7 +218,7 @@ skip = "$skip=" a:INT {return {'$skip': ~~a }; } / "$skip=" .* { return {"error": 'invalid $skip parameter'}; } //$format -format = "$format=" v:.+ { return {'$format': v.join('') }; } +format = "$format=" v:[^&]+ { return {'$format': v.join('') }; } / "$format=" .* { return {"error": 'invalid $format parameter'}; } //$inlinecount inlinecount = "$inlinecount=" v:("allpages" / "none") { return {'$inlinecount': v }; } @@ -273,12 +273,12 @@ filter = "$filter=" list:filterExpr { } / "$filter=" .* { return {"error": 'invalid $filter parameter'}; } -filterExpr = +filterExpr = left:("(" WSP? filter:filterExpr WSP? ")"{return filter}) right:( WSP type:("and"/"or") WSP value:filterExpr{ return { type: type, value: value} })? { return filterExprHelper(left, right); - } / + } / left:cond right:( WSP type:("and"/"or") WSP value:filterExpr{ return { type: type, value: value} })? { diff --git a/test/parser.specs.js b/test/parser.specs.js index 5720d29..61535e2 100644 --- a/test/parser.specs.js +++ b/test/parser.specs.js @@ -1,17 +1,14 @@ var assert = require('assert'); -var parser = require("../lib"); +var parser = require('../lib'); describe('odata.parser grammar', function () { - it('should parse $top and return the value', function () { - var ast = parser.parse('$top=40'); assert.equal(ast.$top, 40); }); it('should parse two params', function () { - var ast = parser.parse('$top=4&$skip=5'); assert.equal(ast.$top, 4); @@ -20,7 +17,6 @@ describe('odata.parser grammar', function () { it('should parse three params', function () { - var ast = parser.parse('$top=4&$skip=5&$select=Rating'); assert.equal(ast.$top, 4); @@ -29,21 +25,18 @@ describe('odata.parser grammar', function () { }); it('should parse string params', function () { - var ast = parser.parse('$select=Rating'); assert.equal(ast.$select[0], 'Rating'); }); it('should accept * in $select', function () { - var ast = parser.parse('$select=*'); assert.equal(ast.$select[0], '*'); }); it('should accept * and , and / in $select', function () { - var ast = parser.parse('$select=*,Category/Name'); assert.equal(ast.$select[0], '*'); @@ -51,7 +44,6 @@ describe('odata.parser grammar', function () { }); it('should accept more than two fields', function () { - var ast = parser.parse('$select=Rating, Name,LastName'); assert.equal(ast.$select[0], 'Rating'); @@ -61,21 +53,18 @@ describe('odata.parser grammar', function () { // This select parameter is not currently supported. it('should accept * after . in $select', function () { - var ast = parser.parse('$select=DemoService.*'); assert.equal(ast.$select[0], 'DemoService.*'); }); it('should accept single-char field in $select', function () { - var ast = parser.parse('$select=r'); assert.equal(ast.$select[0], 'r'); }); - - it('should parse order by', function () { + it('should parse order by', function () { var ast = parser.parse('$orderby=ReleaseDate desc, Rating'); assert.equal(ast.$orderby[0].ReleaseDate, 'desc'); @@ -84,7 +73,6 @@ describe('odata.parser grammar', function () { }); it('should parse $filter', function () { - var ast = parser.parse("$filter=Name eq 'Jef'"); assert.equal(ast.$filter.type, "eq"); @@ -93,48 +81,48 @@ describe('odata.parser grammar', function () { assert.equal(ast.$filter.right.type, "literal"); assert.equal(ast.$filter.right.value, "Jef"); }); - + it('should parse $filter containing quote', function () { - var ast = parser.parse("$filter=Name eq 'O''Neil'"); + var ast = parser.parse("$filter=Name eq 'O''Neil'"); - assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "property"); - assert.equal(ast.$filter.left.name, "Name"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "O'Neil"); + assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.left.type, "property"); + assert.equal(ast.$filter.left.name, "Name"); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "O'Neil"); }); it('should parse $filter with subproperty', function () { - var ast = parser.parse("$filter=User/Name eq 'Jef'"); - assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "property"); - assert.equal(ast.$filter.left.name, "User/Name"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "Jef"); - }); - - it('should parse $filter containing quote', function () { + var ast = parser.parse("$filter=User/Name eq 'Jef'"); - var ast = parser.parse("$filter=Name eq 'O''Neil'"); + assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.left.type, "property"); + assert.equal(ast.$filter.left.name, "User/Name"); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "Jef"); + }); - assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "property"); - assert.equal(ast.$filter.left.name, "Name"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "O'Neil"); - }); + it('should parse $filter containing quote', function () { + var ast = parser.parse("$filter=Name eq 'O''Neil'"); + + assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.left.type, "property"); + assert.equal(ast.$filter.left.name, "Name"); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "O'Neil"); + }); it('should parse $filter with subproperty', function () { - var ast = parser.parse("$filter=User/Name eq 'Jef'"); - assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "property"); - assert.equal(ast.$filter.left.name, "User/Name"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "Jef"); - }); - - it('should parse multiple conditions in a $filter', function () { + var ast = parser.parse("$filter=User/Name eq 'Jef'"); + + assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.left.type, "property"); + assert.equal(ast.$filter.left.name, "User/Name"); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "Jef"); + }); + it('should parse multiple conditions in a $filter', function () { var ast = parser.parse("$filter=Name eq 'John' and LastName lt 'Doe'"); assert.equal(ast.$filter.type, "and"); @@ -151,7 +139,6 @@ describe('odata.parser grammar', function () { }); it('should parse multiple complex conditions in a $filter', function () { - var ast = parser.parse("$filter=Name eq 'John' and (LastName lt 'Doe' or LastName gt 'Aro')"); assert.equal(ast.$filter.type, "and"); @@ -172,7 +159,6 @@ describe('odata.parser grammar', function () { }); it('should parse substringof $filter', function () { - var ast = parser.parse("$filter=substringof('nginx', Data)"); assert.equal(ast.$filter.type, "functioncall"); @@ -183,51 +169,41 @@ describe('odata.parser grammar', function () { assert.equal(ast.$filter.args[1].type, "property"); assert.equal(ast.$filter.args[1].name, "Data"); - }); it('should parse substringof $filter with empty string', function () { - var ast = parser.parse("$filter=substringof('', Data)"); assert.equal(ast.$filter.args[0].type, "literal"); assert.equal(ast.$filter.args[0].value, ""); - }); it('should parse substringof $filter with string containing quote', function () { + var ast = parser.parse("$filter=substringof('ng''inx', Data)"); - var ast = parser.parse("$filter=substringof('ng''inx', Data)"); - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "ng'inx"); - + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "ng'inx"); }); - - it('should parse substringof $filter with string starting with quote', function () { - var ast = parser.parse("$filter=substringof('''nginx', Data)"); - - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "'nginx"); + it('should parse substringof $filter with string starting with quote', function () { + var ast = parser.parse("$filter=substringof('''nginx', Data)"); + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "'nginx"); }); - - it('should parse substringof $filter with string ending with quote', function () { - var ast = parser.parse("$filter=substringof('nginx''', Data)"); - - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "nginx'"); + it('should parse substringof $filter with string ending with quote', function () { + var ast = parser.parse("$filter=substringof('nginx''', Data)"); + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "nginx'"); }); it('should parse substringof eq true in $filter', function () { - var ast = parser.parse("$filter=substringof('nginx', Data) eq true"); assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "functioncall"); assert.equal(ast.$filter.left.func, "substringof"); assert.equal(ast.$filter.left.args[0].type, "literal"); @@ -240,7 +216,6 @@ describe('odata.parser grammar', function () { }); it('should parse startswith $filter', function () { - var ast = parser.parse("$filter=startswith('nginx', Data)"); assert.equal(ast.$filter.type, "functioncall"); @@ -251,42 +226,41 @@ describe('odata.parser grammar', function () { assert.equal(ast.$filter.args[1].type, "property"); assert.equal(ast.$filter.args[1].name, "Data"); - }); ['tolower', 'toupper', 'trim'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "(value) eq 'test'"); + it('should parse ' + func + ' $filter', function () { + var ast = parser.parse("$filter=" + func + "(value) eq 'test'"); - assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "functioncall"); - assert.equal(ast.$filter.left.func, func); - assert.equal(ast.$filter.left.args[0].type, "property"); - assert.equal(ast.$filter.left.args[0].name, "value"); + assert.equal(ast.$filter.left.type, "functioncall"); + assert.equal(ast.$filter.left.func, func); + assert.equal(ast.$filter.left.args[0].type, "property"); + assert.equal(ast.$filter.left.args[0].name, "value"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "test"); - }); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); + }); }); ['year', 'month', 'day', 'hour', 'minute', 'second'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "(value) gt 0"); + it('should parse ' + func + ' $filter', function () { + var ast = parser.parse("$filter=" + func + "(value) gt 0"); - assert.equal(ast.$filter.type, "gt"); + assert.equal(ast.$filter.type, "gt"); - assert.equal(ast.$filter.left.type, "functioncall"); - assert.equal(ast.$filter.left.func, func); - assert.equal(ast.$filter.left.args[0].type, "property"); - assert.equal(ast.$filter.left.args[0].name, "value"); + assert.equal(ast.$filter.left.type, "functioncall"); + assert.equal(ast.$filter.left.func, func); + assert.equal(ast.$filter.left.args[0].type, "property"); + assert.equal(ast.$filter.left.args[0].name, "value"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "0"); - }); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "0"); + }); }); - it('should parse year datetimeoffset $filter', function() { + it('should parse year datetimeoffset $filter', function () { var ast = parser.parse("$filter=my_year lt year(datetimeoffset'2016-01-01T01:01:01Z')"); assert.equal(ast.$filter.type, "lt"); @@ -301,45 +275,44 @@ describe('odata.parser grammar', function () { }); ['indexof', 'concat', 'substring', 'replace'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "('haystack', needle) eq 'test'"); + it('should parse ' + func + ' $filter', function () { + var ast = parser.parse("$filter=" + func + "('haystack', needle) eq 'test'"); - assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "functioncall"); - assert.equal(ast.$filter.left.func, func); - assert.equal(ast.$filter.left.args[0].type, "literal"); - assert.equal(ast.$filter.left.args[0].value, "haystack"); - assert.equal(ast.$filter.left.args[1].type, "property"); - assert.equal(ast.$filter.left.args[1].name, "needle"); + assert.equal(ast.$filter.left.type, "functioncall"); + assert.equal(ast.$filter.left.func, func); + assert.equal(ast.$filter.left.args[0].type, "literal"); + assert.equal(ast.$filter.left.args[0].value, "haystack"); + assert.equal(ast.$filter.left.args[1].type, "property"); + assert.equal(ast.$filter.left.args[1].name, "needle"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "test"); - }); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); + }); }); ['substring', 'replace'].forEach(function (func) { - it('should parse ' + func + ' $filter with 3 args', function() { - var ast = parser.parse("$filter=" + func + "('haystack', needle, foo) eq 'test'"); + it('should parse ' + func + ' $filter with 3 args', function () { + var ast = parser.parse("$filter=" + func + "('haystack', needle, foo) eq 'test'"); - assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "functioncall"); - assert.equal(ast.$filter.left.func, func); - assert.equal(ast.$filter.left.args[0].type, "literal"); - assert.equal(ast.$filter.left.args[0].value, "haystack"); - assert.equal(ast.$filter.left.args[1].type, "property"); - assert.equal(ast.$filter.left.args[1].name, "needle"); - assert.equal(ast.$filter.left.args[2].type, "property"); - assert.equal(ast.$filter.left.args[2].name, "foo"); + assert.equal(ast.$filter.left.type, "functioncall"); + assert.equal(ast.$filter.left.func, func); + assert.equal(ast.$filter.left.args[0].type, "literal"); + assert.equal(ast.$filter.left.args[0].value, "haystack"); + assert.equal(ast.$filter.left.args[1].type, "property"); + assert.equal(ast.$filter.left.args[1].name, "needle"); + assert.equal(ast.$filter.left.args[2].type, "property"); + assert.equal(ast.$filter.left.args[2].name, "foo"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "test"); - }); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); + }); }); - it('should return an error if invalid value', function() { - + it('should return an error if invalid value', function () { var ast = parser.parse("$top=foo"); assert.equal(ast.error, "invalid $top parameter"); @@ -348,78 +321,110 @@ describe('odata.parser grammar', function () { it('should convert dates to javascript Date', function () { var ast = parser.parse("$top=2&$filter=Date gt datetime'2012-09-27T21:12:59'"); + assert.ok(ast.$filter.right.value instanceof Date); }); - it('should parse boolean okay', function(){ + it('should parse boolean okay', function () { var ast = parser.parse('$filter=status eq true'); + assert.equal(ast.$filter.right.value, true); + var ast = parser.parse('$filter=status eq false'); + assert.equal(ast.$filter.right.value, false); }); - it('should parse numbers okay', function(){ + it('should parse numbers okay', function () { var ast = parser.parse('$filter=status eq 3'); + assert.equal(ast.$filter.right.value, 3); + // Test multiple digits - problem of not joining digits to array - ast = parser.parse('$filter=status eq 34'); + var ast = parser.parse('$filter=status eq 34'); + assert.equal(ast.$filter.right.value, 34); + // Test number starting with 1 - problem of boolean rule order - ast = parser.parse('$filter=status eq 12'); + var ast = parser.parse('$filter=status eq 12'); + assert.equal(ast.$filter.right.value, 12); }); - it('should parse negative numbers okay', function(){ + it('should parse negative numbers okay', function () { var ast = parser.parse('$filter=status eq -3'); + assert.equal(ast.$filter.right.value, -3); - ast = parser.parse('$filter=status eq -34'); + + var ast = parser.parse('$filter=status eq -34'); + assert.equal(ast.$filter.right.value, -34); }); - it('should parse decimal numbers okay', function(){ + it('should parse decimal numbers okay', function () { var ast = parser.parse('$filter=status eq 3.4'); + assert.equal(ast.$filter.right.value, '3.4'); - ast = parser.parse('$filter=status eq -3.4'); + + var ast = parser.parse('$filter=status eq -3.4'); + assert.equal(ast.$filter.right.value, '-3.4'); }); - it('should parse double numbers okay', function(){ + it('should parse double numbers okay', function () { var ast = parser.parse('$filter=status eq 3.4e1'); + assert.equal(ast.$filter.right.value, '3.4e1'); - ast = parser.parse('$filter=status eq -3.4e-1'); + + var ast = parser.parse('$filter=status eq -3.4e-1'); + assert.equal(ast.$filter.right.value, '-3.4e-1'); }); it('should parse $expand and return an array of identifier paths', function () { var ast = parser.parse('$expand=Category,Products/Suppliers'); + assert.equal(ast.$expand[0], 'Category'); assert.equal(ast.$expand[1], 'Products/Suppliers'); }); it('should allow only valid values for $inlinecount', function () { var ast = parser.parse('$inlinecount=allpages'); + assert.equal(ast.$inlinecount, 'allpages'); - ast = parser.parse('$inlinecount=none'); + var ast = parser.parse('$inlinecount=none'); + assert.equal(ast.$inlinecount, 'none'); - ast = parser.parse('$inlinecount='); + var ast = parser.parse('$inlinecount='); + assert.equal(ast.error, 'invalid $inlinecount parameter'); - ast = parser.parse('$inlinecount=test'); + var ast = parser.parse('$inlinecount=test'); + assert.equal(ast.error, 'invalid $inlinecount parameter'); }); it('should parse $format okay', function () { var ast = parser.parse('$format=application/atom+xml'); + assert.equal(ast.$format, 'application/atom+xml'); - ast = parser.parse('$format='); + var ast = parser.parse('$format='); + assert.equal(ast.error, 'invalid $format parameter'); }); + it('should parse $format followed by other param okay', function () { + var ast = parser.parse('$format=application/atom+xml&$top=1'); + + assert.equal(ast.$format, 'application/atom+xml'); + }); + it('should accept identifiers prefixed by _', function () { var ast = parser.parse("$filter=_first_name eq 'John'"); + assert.equal(ast.$filter.left.name, "_first_name"); });