odatafy-parser
Version:
generate odatafy ast from odata query parameters
199 lines (186 loc) • 8.47 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.processSelectOptionsUnprocessedNode = void 0;
const peggy_1 = __importDefault(require("peggy"));
const nodes_1 = require("../types/nodes");
const querystring_1 = __importDefault(require("querystring"));
const filterParser_1 = __importDefault(require("./filterParser"));
const orderbyParser_1 = __importDefault(require("./orderbyParser"));
const skipParser_1 = __importDefault(require("./skipParser"));
const topParser_1 = __importDefault(require("./topParser"));
const computeParser_1 = __importDefault(require("./computeParser"));
const expandParser_1 = __importDefault(require("./expandParser"));
const searchParser_1 = __importDefault(require("./searchParser"));
const utils_1 = require("../utils");
const errors_1 = require("../types/errors");
//TODO add annotations to path
const selectParser = peggy_1.default.generate(`
{
function SelectNodeHelper(value) {
return {
nodeType: "SelectNode",
value: value.filter(selectItem => selectItem != undefined)
}
}
function SelectPathNodeHelper(value, options) {
return options?{
nodeType: "SelectPathNodeWithOptions",
value: value,
options: options
} : {
nodeType: "SelectPathNode",
value: value
}
}
function SelectIdentifierNodeHelper(value, flag) {
return {
nodeType: "SelectIdentifierNode",
value: value,
...(flag && {flag: flag})
}
}
function SelectFunctionNodeHelper(func, args) {
return {
nodeType: "SelectFunctionNode",
func: func,
args: args
}
}
function SelectOptionsUnprocessedNodeHelper(selectOptionsString) {
return {
nodeType: "SelectOptionsUnprocessedNode",
value: selectOptionsString
}
}
}
//functionname and selectoption are not clashing, because function params need to have odataIdentifiers as params and selectoptions always contain EQs, which are not allowed in odataIdents
select = head:selectItem tail:( COMMA BWS @selectItem )* {return SelectNodeHelper([head, ...tail])}
selectItem = STAR {return undefined}
/ identNode:allOperationsInSchema {return SelectPathNodeHelper([identNode])}
/ (
identNode:qualifiedFunctionName {return SelectPathNodeHelper([identNode])}
/ selectPath
/ identNode:(selectPathPart) selOps:selectOptions? {return SelectPathNodeHelper([identNode], selOps) }
)
selectPath = head:(selectPathPart) tail:( "/" @(identNode: selectPathPart ) )* selOps:selectOptions? {return SelectPathNodeHelper([head, ...tail], selOps)}
selectPathPart = odataIdentifierWithNamespace / odataIdentifier / odataAnnotation
selectOptions = OPEN selectOptionString:$textUntilTerminator CLOSE {return SelectOptionsUnprocessedNodeHelper(selectOptionString)}
textUntilTerminator = ( &haveTerminatorAhead .)*
haveTerminatorAhead = . ( !")" . )* ")"
//avoid the matching with qualifiedFunctionName if the expression is actually an Identifier with selectOptions
qualifiedFunctionName = func:$odataIdentifierWithNamespace OPEN args:parameterNames CLOSE {return SelectFunctionNodeHelper(func, args)}
//selectOptionStart = ("$"? ("filter" / "search" / "count" / "orderby" / "skip" / "top" / "compute" / "select" / "expand") EQ .*)
parameterNames = head:odataIdentifier tail:( COMMA @odataIdentifier )* {return [head, ...tail]}
allOperationsInSchema = value:$(odataIdentifier ( "." odataIdentifier )*) "." STAR {return SelectIdentifierNodeHelper(value, "AllOperationsInSchema")}
odataAnnotation = AT identNode:(odataIdentifierWithNamespace / odataIdentifier) {return {...identNode, flag: "Annotation"}}
odataIdentifierWithNamespace = value:$(odataIdentifier ( "." odataIdentifier )+) {return SelectIdentifierNodeHelper(value)}
odataIdentifier = value:$(identifierLeadingCharacter identifierCharacter*) {return SelectIdentifierNodeHelper(value)}
identifierLeadingCharacter = ALPHA / "_"
identifierCharacter = ALPHA / "_" / DIGIT
AT = "@" / "%40"
ALPHA = [a-zA-Z]
DIGIT = [0-9]
COMMA = "," / "%2C"
STAR = "*" / "%2A"
EQ = "="
OPEN = "(" / "%28"
CLOSE = ")" / "%29"
RWS = ( SP / HTAB / "%20" / "%09" )+
BWS = ( SP / HTAB / "%20" / "%09" )*
SP = ' '
HTAB = ' '
`);
function parseSelect(expr) {
let ast;
try {
ast = selectParser.parse(expr);
}
catch (e) {
throw (0, utils_1.getOdatafyParserError)('malformed select expression', errors_1.OdatafyQueryOptions.Select);
}
try {
for (const selectPath of ast.value) {
if (selectPath.nodeType == nodes_1.NodeTypes.SelectPathNodeWithOptions) {
selectPath.options = processSelectOptionsUnprocessedNode(selectPath.options);
}
}
return ast;
}
catch (e) {
throw (0, utils_1.getOdatafyParserError)('malformed select options', errors_1.OdatafyQueryOptions.Select);
}
}
function processSelectOptionsUnprocessedNode(SelectOptionsUnprocessedNode) {
const parsedOptions = querystring_1.default.parse(SelectOptionsUnprocessedNode.value, ';');
const options = {};
//parse options wíth $
if (parsedOptions.$filter && typeof parsedOptions.$filter == 'string') {
options.filter = filterParser_1.default.parse(parsedOptions.$filter);
}
if (parsedOptions.$orderby && typeof parsedOptions.$orderby == 'string') {
options.orderby = orderbyParser_1.default.parse(parsedOptions.$orderby);
}
if (parsedOptions.$skip && typeof parsedOptions.$skip == 'string') {
options.skip = skipParser_1.default.parse(parsedOptions.$skip);
}
if (parsedOptions.$top && typeof parsedOptions.$top == 'string') {
options.top = topParser_1.default.parse(parsedOptions.$top);
}
if (parsedOptions.$select && typeof parsedOptions.$select == 'string') {
options.select = parseSelect(parsedOptions.$select);
}
if (parsedOptions.$compute && typeof parsedOptions.$compute == 'string') {
options.compute = computeParser_1.default.parse(parsedOptions.$compute);
}
if (parsedOptions.$expand && typeof parsedOptions.$expand == 'string') {
options.expand = expandParser_1.default.parse(parsedOptions.$expand);
}
if (parsedOptions.$count && typeof parsedOptions.$count == 'string') {
options.count = true;
}
if (parsedOptions.$search && typeof parsedOptions.$search == 'string') {
options.search = searchParser_1.default.parse(parsedOptions.$search);
}
// parse options without $
if (parsedOptions.filter && typeof parsedOptions.filter == 'string') {
options.filter = filterParser_1.default.parse(parsedOptions.filter);
}
if (parsedOptions.orderby && typeof parsedOptions.orderby == 'string') {
options.orderby = orderbyParser_1.default.parse(parsedOptions.orderby);
}
if (parsedOptions.skip && typeof parsedOptions.skip == 'string') {
options.skip = skipParser_1.default.parse(parsedOptions.skip);
}
if (parsedOptions.top && typeof parsedOptions.top == 'string') {
options.top = topParser_1.default.parse(parsedOptions.top);
}
if (parsedOptions.select && typeof parsedOptions.select == 'string') {
options.select = parseSelect(parsedOptions.select);
}
if (parsedOptions.compute && typeof parsedOptions.compute == 'string') {
options.compute = computeParser_1.default.parse(parsedOptions.compute);
}
if (parsedOptions.expand && typeof parsedOptions.expand == 'string') {
options.expand = expandParser_1.default.parse(parsedOptions.expand);
}
if (parsedOptions.count && typeof parsedOptions.count == 'string') {
options.count = true;
}
if (parsedOptions.search && typeof parsedOptions.search == 'string') {
options.search = searchParser_1.default.parse(parsedOptions.search);
}
return options;
}
exports.processSelectOptionsUnprocessedNode = processSelectOptionsUnprocessedNode;
exports.default = {
/**
* Parser for select expressions
* @param expr select expression as string
* @example selectParser.parse("Name,Age")
* @returns Abstract Syntax Tree (AST) of type SelectNode
*/
parse: parseSelect
};