diff --git a/package.json b/package.json index 20e5f7b..573c03a 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,15 @@ "description": "OData query string parser", "main": "lib/index.js", "scripts": { + "build": "pegjs src/odata.pegjs lib/odata-parser.js", "test": "rm -f lib/odata-parser.js && npm run-script prepublish && mocha -R spec", - "prepublish": "pegjs src/odata.pegjs lib/odata-parser.js" + "postinstall": "npm run build", + "prepublish": "npm run build" + }, + "dependencies": { + "pegjs": "0.7.0" }, - "dependencies": {}, "devDependencies": { - "pegjs": "0.7.0", "mocha": "2.1.0" }, "repository": { diff --git a/src/odata.pegjs b/src/odata.pegjs index af60680..724724d 100644 --- a/src/odata.pegjs +++ b/src/odata.pegjs @@ -45,6 +45,7 @@ SQUOTE = "%x27" / "'" */ primitiveLiteral = null / binary / + dateTimeOffsetNoQuoutes / dateTime / dateTimeOffset / guid / @@ -79,6 +80,7 @@ byte = DIGIT DIGIT DIGIT dateTime = "datetime" SQUOTE a:dateTimeBody SQUOTE { return new Date(a); } +dateTimeOffsetNoQuoutes = a:dateTimeOffsetBody { return new Date(a); } dateTimeOffset = "datetimeoffset" SQUOTE a:dateTimeOffsetBody SQUOTE { return new Date(a); } dateTimeBodyA = a:year "-" b:month "-" c:day "T" d:hour ":" e:minute { @@ -204,23 +206,29 @@ top = "$top=" a:INT { return { '$top': ~~a }; } expand = "$expand=" list:expandList { return { "$expand": list }; } / "$expand=" .* { return {"error": 'invalid $expand parameter'}; } -expandList = i:identifierPath list:("," WSP? l:expandList {return l;})? { +expandList = i:identifierPath e:("(" e:exp ")" {return e;})? list:("," WSP? l:expandList {return l;})? { if (list === "") list = []; - if (require('util').isArray(list[0])) { - list = list[0]; + + var elem = i; + if (e) { + elem = [ i, e ]; } - list.unshift(i); + list.unshift(elem); + return list; } -//$skip +// $count +count = "$count=" a:boolean { return { '$count': a }; } + / "$count=" .* { return {"error": 'invalid $count parameter'}; } +// $skip skip = "$skip=" a:INT {return {'$skip': ~~a }; } / "$skip=" .* { return {"error": 'invalid $skip parameter'}; } -//$format +// $format format = "$format=" v:.+ { return {'$format': v.join('') }; } / "$format=" .* { return {"error": 'invalid $format parameter'}; } -//$inlinecount +// $inlinecount inlinecount = "$inlinecount=" v:("allpages" / "none") { return {'$inlinecount': v }; } / "$inlinecount=" .* { return {"error": 'invalid $inlinecount parameter'}; } @@ -244,7 +252,7 @@ orderbyList = i:(id:identifier ord:(WSP ("asc"/"desc"))? { return list; } -//$select +// $select select = "$select=" list:selectList { return { "$select":list }; } / "$select=" .* { return {"error": 'invalid $select parameter'}; } @@ -265,7 +273,7 @@ selectList = return list; } -//filter +// $filter filter = "$filter=" list:filterExpr { return { "$filter": list @@ -273,19 +281,19 @@ 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} })? { return filterExprHelper(left, right); } -booleanFunctions2Args = "substringof" / "endswith" / "startswith" / "IsOf" +booleanFunctions2Args = "substringof" / "endswith" / "startswith" / "IsOf" / "geo.intersects" booleanFunc = f:booleanFunctions2Args "(" arg0:part "," WSP? arg1:part ")" { return { @@ -304,7 +312,7 @@ booleanFunc = f:booleanFunctions2Args "(" arg0:part "," WSP? ar otherFunctions1Arg = "tolower" / "toupper" / "trim" / "length" / "year" / "month" / "day" / "hour" / "minute" / "second" / - "round" / "floor" / "ceiling" + "round" / "floor" / "ceiling" / "geography" otherFunc1 = f:otherFunctions1Arg "(" arg0:part ")" { return { @@ -312,6 +320,12 @@ otherFunc1 = f:otherFunctions1Arg "(" arg0:part ")" { func: f, args: [arg0] } + } / + "geography" arg0:string { + return { + type: 'literal', + value: arg0 + } } otherFunctions2Arg = "indexof" / "concat" / "substring" / "replace" @@ -374,6 +388,11 @@ op = "div" / "mod" +// $resultFormat +resultFormat = "$resultFormat=" "dataArray" { return { '$resultFormat': 'dataArray' }; } + / "$resultFormat=" .* { return { error: 'invalid $resultFormat parameter' }; } + + unsupported = "$" er:.* { return { error: "unsupported method: " + er }; } //end: OData query options @@ -396,6 +415,8 @@ exp = inlinecount / select / callback / + count / + resultFormat / unsupported query = list:expList { diff --git a/test/parser.specs.js b/test/parser.specs.js index 5720d29..7d79fea 100644 --- a/test/parser.specs.js +++ b/test/parser.specs.js @@ -3,429 +3,490 @@ var parser = require("../lib"); describe('odata.parser grammar', function () { - it('should parse $top and return the value', function () { + it('should parse $top and return the value', function () { - var ast = parser.parse('$top=40'); + var ast = parser.parse('$top=40'); - assert.equal(ast.$top, 40); - }); + assert.equal(ast.$top, 40); + }); - it('should parse two params', function () { + it('should parse two params', function () { - var ast = parser.parse('$top=4&$skip=5'); + var ast = parser.parse('$top=4&$skip=5'); - assert.equal(ast.$top, 4); - assert.equal(ast.$skip, 5); - }); + assert.equal(ast.$top, 4); + assert.equal(ast.$skip, 5); + }); - it('should parse three params', function () { + it('should parse three params', function () { - var ast = parser.parse('$top=4&$skip=5&$select=Rating'); + var ast = parser.parse('$top=4&$skip=5&$select=Rating'); - assert.equal(ast.$top, 4); - assert.equal(ast.$skip, 5); - assert.equal(ast.$select[0], "Rating"); - }); + assert.equal(ast.$top, 4); + assert.equal(ast.$skip, 5); + assert.equal(ast.$select[0], "Rating"); + }); - it('should parse string params', function () { + it('should parse string params', function () { - var ast = parser.parse('$select=Rating'); + var ast = parser.parse('$select=Rating'); - assert.equal(ast.$select[0], 'Rating'); - }); + assert.equal(ast.$select[0], 'Rating'); + }); - it('should accept * in $select', function () { + it('should accept * in $select', function () { - var ast = parser.parse('$select=*'); + var ast = parser.parse('$select=*'); - assert.equal(ast.$select[0], '*'); - }); + assert.equal(ast.$select[0], '*'); + }); - it('should accept * and , and / in $select', function () { + it('should accept * and , and / in $select', function () { - var ast = parser.parse('$select=*,Category/Name'); + var ast = parser.parse('$select=*,Category/Name'); - assert.equal(ast.$select[0], '*'); - assert.equal(ast.$select[1], 'Category/Name'); - }); + assert.equal(ast.$select[0], '*'); + assert.equal(ast.$select[1], 'Category/Name'); + }); - it('should accept more than two fields', function () { + it('should accept more than two fields', function () { - var ast = parser.parse('$select=Rating, Name,LastName'); + var ast = parser.parse('$select=Rating, Name,LastName'); - assert.equal(ast.$select[0], 'Rating'); - assert.equal(ast.$select[1], 'Name'); - assert.equal(ast.$select[2], 'LastName'); - }); + assert.equal(ast.$select[0], 'Rating'); + assert.equal(ast.$select[1], 'Name'); + assert.equal(ast.$select[2], 'LastName'); + }); - // This select parameter is not currently supported. - it('should accept * after . in $select', function () { + // This select parameter is not currently supported. + it('should accept * after . in $select', function () { - var ast = parser.parse('$select=DemoService.*'); + var ast = parser.parse('$select=DemoService.*'); - assert.equal(ast.$select[0], 'DemoService.*'); - }); + assert.equal(ast.$select[0], 'DemoService.*'); + }); - it('should accept single-char field in $select', function () { + it('should accept single-char field in $select', function () { - var ast = parser.parse('$select=r'); + var ast = parser.parse('$select=r'); - assert.equal(ast.$select[0], 'r'); - }); - - it('should parse order by', function () { + assert.equal(ast.$select[0], 'r'); + }); - var ast = parser.parse('$orderby=ReleaseDate desc, Rating'); + it('should parse order by', function () { - assert.equal(ast.$orderby[0].ReleaseDate, 'desc'); - assert.equal(ast.$orderby[1].Rating, 'asc'); + var ast = parser.parse('$orderby=ReleaseDate desc, Rating'); - }); + assert.equal(ast.$orderby[0].ReleaseDate, 'desc'); + assert.equal(ast.$orderby[1].Rating, 'asc'); - it('should parse $filter', function () { + }); - var ast = parser.parse("$filter=Name eq 'Jef'"); + it('should parse $filter', function () { - 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, "Jef"); - }); - - 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"); - }); + var ast = parser.parse("$filter=Name eq 'Jef'"); - 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 () { + 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, "Jef"); + }); - var ast = parser.parse("$filter=Name eq '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"); + 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=Name eq 'John' and LastName lt 'Doe'"); - - assert.equal(ast.$filter.type, "and"); - assert.equal(ast.$filter.left.type, "eq"); - assert.equal(ast.$filter.left.left.type, "property"); - assert.equal(ast.$filter.left.left.name, "Name"); - assert.equal(ast.$filter.left.right.type, "literal"); - assert.equal(ast.$filter.left.right.value, "John"); - assert.equal(ast.$filter.right.type, "lt"); - assert.equal(ast.$filter.right.left.type, "property"); - assert.equal(ast.$filter.right.left.name, "LastName"); - assert.equal(ast.$filter.right.right.type, "literal"); - assert.equal(ast.$filter.right.right.value, "Doe"); - }); + 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 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"); - assert.equal(ast.$filter.left.type, "eq"); - assert.equal(ast.$filter.left.left.type, "property"); - assert.equal(ast.$filter.left.left.name, "Name"); - assert.equal(ast.$filter.left.right.type, "literal"); - assert.equal(ast.$filter.left.right.value, "John"); - assert.equal(ast.$filter.right.type, "or"); - assert.equal(ast.$filter.right.left.type, "lt"); - assert.equal(ast.$filter.right.left.left.name, "LastName"); - assert.equal(ast.$filter.right.left.right.type, "literal"); - assert.equal(ast.$filter.right.left.right.value, "Doe"); - assert.equal(ast.$filter.right.right.type, "gt"); - assert.equal(ast.$filter.right.right.left.name, "LastName"); - assert.equal(ast.$filter.right.right.right.type, "literal"); - assert.equal(ast.$filter.right.right.right.value, "Aro"); - }); + it('should parse $filter containing quote', function () { - it('should parse substringof $filter', function () { + var ast = parser.parse("$filter=Name eq 'O''Neil'"); - var ast = parser.parse("$filter=substringof('nginx', Data)"); + 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, "functioncall"); - assert.equal(ast.$filter.func, "substringof"); + it('should parse geo.intersects', function() { + var ast = parser.parse("$filter=geo\.intersects(location, location2)"); + assert.equal(ast.$filter.type, "functioncall"); + assert.equal(ast.$filter.func, "geo.intersects"); + assert.equal(ast.$filter.args[0].type, "property"); + assert.equal(ast.$filter.args[0].name, "location"); + assert.equal(ast.$filter.args[1].type, "property"); + assert.equal(ast.$filter.args[1].name, "location2"); + }); - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "nginx"); + it('should parse geo.intersects with literal', function() { + var ast = parser.parse("$filter=geo\.intersects(location, geography'POLYGON((30 10, 10 20, 20 40, 40 40, 30 10))')"); + assert.equal(ast.$filter.type, "functioncall"); + assert.equal(ast.$filter.func, "geo.intersects"); + assert.equal(ast.$filter.args[0].type, "property"); + assert.equal(ast.$filter.args[0].name, "location"); + assert.equal(ast.$filter.args[1].type, "literal"); + assert.equal(ast.$filter.args[1].value, "POLYGON((30 10, 10 20, 20 40, 40 40, 30 10))"); + }); - assert.equal(ast.$filter.args[1].type, "property"); - assert.equal(ast.$filter.args[1].name, "Data"); + 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=Name eq 'John' and LastName lt 'Doe'"); + + assert.equal(ast.$filter.type, "and"); + assert.equal(ast.$filter.left.type, "eq"); + assert.equal(ast.$filter.left.left.type, "property"); + assert.equal(ast.$filter.left.left.name, "Name"); + assert.equal(ast.$filter.left.right.type, "literal"); + assert.equal(ast.$filter.left.right.value, "John"); + assert.equal(ast.$filter.right.type, "lt"); + assert.equal(ast.$filter.right.left.type, "property"); + assert.equal(ast.$filter.right.left.name, "LastName"); + assert.equal(ast.$filter.right.right.type, "literal"); + assert.equal(ast.$filter.right.right.value, "Doe"); + }); - it('should parse substringof $filter with empty string', 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"); + assert.equal(ast.$filter.left.type, "eq"); + assert.equal(ast.$filter.left.left.type, "property"); + assert.equal(ast.$filter.left.left.name, "Name"); + assert.equal(ast.$filter.left.right.type, "literal"); + assert.equal(ast.$filter.left.right.value, "John"); + assert.equal(ast.$filter.right.type, "or"); + assert.equal(ast.$filter.right.left.type, "lt"); + assert.equal(ast.$filter.right.left.left.name, "LastName"); + assert.equal(ast.$filter.right.left.right.type, "literal"); + assert.equal(ast.$filter.right.left.right.value, "Doe"); + assert.equal(ast.$filter.right.right.type, "gt"); + assert.equal(ast.$filter.right.right.left.name, "LastName"); + assert.equal(ast.$filter.right.right.right.type, "literal"); + assert.equal(ast.$filter.right.right.right.value, "Aro"); + }); - var ast = parser.parse("$filter=substringof('', Data)"); + it('should parse substringof $filter', function () { - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, ""); + var ast = parser.parse("$filter=substringof('nginx', Data)"); - }); + assert.equal(ast.$filter.type, "functioncall"); + assert.equal(ast.$filter.func, "substringof"); - it('should parse substringof $filter with string containing quote', function () { + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "nginx"); - 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[1].type, "property"); + assert.equal(ast.$filter.args[1].name, "Data"); - }); - - 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 empty string', function () { - }); - - it('should parse substringof $filter with string ending with quote', function () { + var ast = parser.parse("$filter=substringof('', Data)"); - var ast = parser.parse("$filter=substringof('nginx''', Data)"); - - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "nginx'"); + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, ""); - }); + }); - it('should parse substringof eq true in $filter', function () { + it('should parse substringof $filter with string containing quote', function () { - var ast = parser.parse("$filter=substringof('nginx', Data) eq true"); + 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.type, "eq"); + }); + it('should parse substringof $filter with string starting with quote', function () { - assert.equal(ast.$filter.left.type, "functioncall"); - assert.equal(ast.$filter.left.func, "substringof"); - assert.equal(ast.$filter.left.args[0].type, "literal"); - assert.equal(ast.$filter.left.args[0].value, "nginx"); - assert.equal(ast.$filter.left.args[1].type, "property"); - assert.equal(ast.$filter.left.args[1].name, "Data"); + var ast = parser.parse("$filter=substringof('''nginx', Data)"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, true); - }); + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "'nginx"); - it('should parse startswith $filter', function () { + }); - var ast = parser.parse("$filter=startswith('nginx', Data)"); + it('should parse substringof $filter with string ending with quote', function () { - assert.equal(ast.$filter.type, "functioncall"); - assert.equal(ast.$filter.func, "startswith"); + var ast = parser.parse("$filter=substringof('nginx''', Data)"); - assert.equal(ast.$filter.args[0].type, "literal"); - assert.equal(ast.$filter.args[0].value, "nginx"); + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "nginx'"); - assert.equal(ast.$filter.args[1].type, "property"); - assert.equal(ast.$filter.args[1].name, "Data"); + }); - }); + it('should parse substringof eq true in $filter', function () { - ['tolower', 'toupper', 'trim'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "(value) eq 'test'"); + var ast = parser.parse("$filter=substringof('nginx', Data) eq true"); - 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.right.type, "literal"); - assert.equal(ast.$filter.right.value, "test"); - }); - }); + assert.equal(ast.$filter.left.type, "functioncall"); + assert.equal(ast.$filter.left.func, "substringof"); + assert.equal(ast.$filter.left.args[0].type, "literal"); + assert.equal(ast.$filter.left.args[0].value, "nginx"); + assert.equal(ast.$filter.left.args[1].type, "property"); + assert.equal(ast.$filter.left.args[1].name, "Data"); - ['year', 'month', 'day', 'hour', 'minute', 'second'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "(value) gt 0"); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, true); + }); - assert.equal(ast.$filter.type, "gt"); + it('should parse startswith $filter', function () { - 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"); + var ast = parser.parse("$filter=startswith('nginx', Data)"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "0"); - }); - }); + assert.equal(ast.$filter.type, "functioncall"); + assert.equal(ast.$filter.func, "startswith"); + + assert.equal(ast.$filter.args[0].type, "literal"); + assert.equal(ast.$filter.args[0].value, "nginx"); + + assert.equal(ast.$filter.args[1].type, "property"); + assert.equal(ast.$filter.args[1].name, "Data"); - it('should parse year datetimeoffset $filter', function() { - var ast = parser.parse("$filter=my_year lt year(datetimeoffset'2016-01-01T01:01:01Z')"); + }); + + ['tolower', 'toupper', 'trim'].forEach(function (func) { + it('should parse ' + func + ' $filter', function () { + var ast = parser.parse("$filter=" + func + "(value) eq 'test'"); - assert.equal(ast.$filter.type, "lt"); + assert.equal(ast.$filter.type, "eq"); - assert.equal(ast.$filter.left.type, "property"); - assert.equal(ast.$filter.left.name, "my_year"); + 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, "functioncall"); - assert.equal(ast.$filter.right.func, "year"); - assert.equal(ast.$filter.right.args[0].type, "literal"); - assert.ok(ast.$filter.right.args[0].value instanceof Date); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); }); + }); - ['indexof', 'concat', 'substring', 'replace'].forEach(function (func) { - it('should parse ' + func + ' $filter', function () { - var ast = parser.parse("$filter=" + func + "('haystack', needle) eq '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"); - assert.equal(ast.$filter.type, "eq"); + 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, "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, "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, "0"); }); + }); - ['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 year datetimeoffset $filter', function() { + var ast = parser.parse("$filter=my_year lt year(datetimeoffset'2016-01-01T01:01:01Z')"); - assert.equal(ast.$filter.type, "eq"); + assert.equal(ast.$filter.type, "lt"); - 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, "property"); + assert.equal(ast.$filter.left.name, "my_year"); - assert.equal(ast.$filter.right.type, "literal"); - assert.equal(ast.$filter.right.value, "test"); - }); - }); + assert.equal(ast.$filter.right.type, "functioncall"); + assert.equal(ast.$filter.right.func, "year"); + assert.equal(ast.$filter.right.args[0].type, "literal"); + assert.ok(ast.$filter.right.args[0].value instanceof Date); + }); - it('should return an error if invalid value', function() { + it('should work with datetimeoffset without quotes', function () { + var ast = parser.parse("$filter=my_year gt 2016-01-01T01:01:01Z"); + assert.ok(ast.$filter.right.value instanceof Date); + }); - var ast = parser.parse("$top=foo"); + ['indexof', 'concat', 'substring', 'replace'].forEach(function (func) { + it('should parse ' + func + ' $filter', function () { + var ast = parser.parse("$filter=" + func + "('haystack', needle) eq 'test'"); - assert.equal(ast.error, "invalid $top parameter"); - }); + 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"); - 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); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); }); + }); - 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); - }); + ['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 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'); - assert.equal(ast.$filter.right.value, 34); - // Test number starting with 1 - problem of boolean rule order - ast = parser.parse('$filter=status eq 12'); - assert.equal(ast.$filter.right.value, 12); - }); + assert.equal(ast.$filter.type, "eq"); - 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'); - assert.equal(ast.$filter.right.value, -34); - }); + 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"); - 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'); - assert.equal(ast.$filter.right.value, '-3.4'); + assert.equal(ast.$filter.right.type, "literal"); + assert.equal(ast.$filter.right.value, "test"); }); + }); - 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'); - assert.equal(ast.$filter.right.value, '-3.4e-1'); - }); + it('should return an error if invalid value', function() { - 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'); - }); + var ast = parser.parse("$top=foo"); - it('should allow only valid values for $inlinecount', function () { - var ast = parser.parse('$inlinecount=allpages'); - assert.equal(ast.$inlinecount, 'allpages'); + assert.equal(ast.error, "invalid $top parameter"); + }); - ast = parser.parse('$inlinecount=none'); - assert.equal(ast.$inlinecount, 'none'); - ast = parser.parse('$inlinecount='); - assert.equal(ast.error, 'invalid $inlinecount parameter'); + 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); + }); - ast = parser.parse('$inlinecount=test'); - assert.equal(ast.error, 'invalid $inlinecount parameter'); - }); + 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 $format okay', function () { - var ast = parser.parse('$format=application/atom+xml'); - assert.equal(ast.$format, 'application/atom+xml'); + 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'); + assert.equal(ast.$filter.right.value, 34); + // Test number starting with 1 - problem of boolean rule order + ast = parser.parse('$filter=status eq 12'); + assert.equal(ast.$filter.right.value, 12); + }); + + 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'); + assert.equal(ast.$filter.right.value, -34); + }); + + 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'); + assert.equal(ast.$filter.right.value, '-3.4'); + }); + + 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'); + 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'); + assert.equal(ast.$inlinecount, 'none'); + + ast = parser.parse('$inlinecount='); + assert.equal(ast.error, 'invalid $inlinecount parameter'); + + ast = parser.parse('$inlinecount=test'); + assert.equal(ast.error, 'invalid $inlinecount parameter'); + }); - ast = parser.parse('$format='); - assert.equal(ast.error, 'invalid $format 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='); + assert.equal(ast.error, 'invalid $format parameter'); + }); + + it('should accept identifiers prefixed by _', function () { + var ast = parser.parse("$filter=_first_name eq 'John'"); + assert.equal(ast.$filter.left.name, "_first_name"); + }); + + describe('$count', function() { + [true, false].forEach(function(value) { + it('should parse $count=' + value, function() { + var ast = parser.parse('$count=' + value); + assert.equal(ast.$count, value); + }); }); - it('should accept identifiers prefixed by _', function () { - var ast = parser.parse("$filter=_first_name eq 'John'"); - assert.equal(ast.$filter.left.name, "_first_name"); + it('should error parsing $count=whatever', function() { + var ast = parser.parse('$count=whatever'); + assert.equal(ast.error, 'invalid $count parameter'); + assert.equal(ast.$count, undefined); }); + }); - // it('xxxxx', function () { - // var ast = parser.parse("$top=2&$filter=Date gt datetime'2012-09-27T21:12:59'"); + describe('$resultFormat', function() { + it('should parse $resultFormat=dataArray', function() { + var ast = parser.parse('$resultFormat=dataArray'); + assert.equal(ast.$resultFormat, 'dataArray'); + }); + + it('should error parsing $resultFormat=whatever', function() { + var ast = parser.parse('$resultFormat=whatever'); + assert.equal(ast.error, 'invalid $resultFormat parameter'); + assert.equal(ast.$count, undefined); + }); + }); - // console.log(JSON.stringify(ast, 0, 2)); - // }); + describe('Expression inside expression', function() { + it('should parse $expand=Sensors,Observations($select=result),Datastream', + function() { + var ast = parser.parse( + '$expand=Sensors,Observations($select=result),Datastream' + ); + assert.deepEqual(ast.$expand, [ + 'Sensors', + ['Observations', {'$select': ['result']}], + 'Datastream' + ]); + }); + }); });