diff --git a/js/httpql/httpql.grammar b/js/httpql/httpql.grammar index c3b37df..b22020b 100644 --- a/js/httpql/httpql.grammar +++ b/js/httpql/httpql.grammar @@ -14,6 +14,8 @@ RightParen { ")" } Dot { "." } Colon { ":" } + LeftBracket { "[" } + RightBracket { "]" } Hex { $[0-9a-fA-F] } RegexDelim { "/" } @@ -40,6 +42,8 @@ RequestIntFieldName { "len" | "port" } RequestDateFieldName { "created_at" } RequestBoolFieldName { "tls" } + RequestHeaderFieldName { "header" } + RequestHeaderSubfieldName { "name" | "value" } // Response field names ResponseStringFieldName { "raw" } @@ -107,7 +111,9 @@ RequestClause { RequestNamespace Dot RequestIntFieldName Dot IntExpression | RequestNamespace Dot RequestStringFieldName Dot StringExpression | RequestNamespace Dot RequestDateFieldName Dot DateExpression | - RequestNamespace Dot RequestBoolFieldName Dot BoolExpression + RequestNamespace Dot RequestBoolFieldName Dot BoolExpression | + RequestNamespace Dot RequestHeaderFieldName Dot RequestHeaderSubfieldName Dot StringExpression | + RequestNamespace Dot RequestHeaderFieldName LeftBracket StringValue RightBracket Dot StringExpression } ResponseClause { diff --git a/js/httpql/src/deserialize/clause.request.ts b/js/httpql/src/deserialize/clause.request.ts index 1ad02f9..7e8f99b 100644 --- a/js/httpql/src/deserialize/clause.request.ts +++ b/js/httpql/src/deserialize/clause.request.ts @@ -4,18 +4,62 @@ import type { Result } from "neverthrow"; import { type HTTPQLError, InvalidQuery } from "../errors.js"; import { terms } from "../parser/index.js"; -import type { ClauseRequest } from "../primitives.js"; -import { getChildString, isPresent } from "../utils.js"; +import { OperatorString, type Query } from "../primitives.js"; +import { getChildString, isAbsent, isPresent } from "../utils.js"; import { deserializeBoolExpr } from "./expr.bool.js"; import { deserializeDateExpr } from "./expr.date.js"; import { deserializeIntExpr } from "./expr.int.js"; import { deserializeStringExpr } from "./expr.string.js"; +import { deserializeString } from "./string.js"; export const deserializeRequestClause = ( node: SyntaxNode, doc: string, -): Result => { +): Result => { + const headerShortExpr = (() => { + const headerNameResult = deserializeString(node, doc); + if (headerNameResult.isErr()) { + return; + } + + const { value: headerName, isRaw } = headerNameResult.value; + if (isRaw) { + return; + } + + const valueExprNode = node.getChild(terms.StringExpression); + if (isAbsent(valueExprNode)) return; + + return deserializeStringExpr(valueExprNode, doc).map((valueExpr) => { + return { + AND: [ + { + request: { + header: { + name: { + operator: OperatorString.Eq, + value: headerName, + isRaw: false, + }, + }, + }, + }, + { + request: { + header: { + value: valueExpr, + }, + }, + }, + ], + } satisfies Query; + }); + })(); + if (isPresent(headerShortExpr)) { + return headerShortExpr; + } + const stringField = (() => { const child = getChildString(node, terms.RequestStringFieldName, doc); @@ -36,6 +80,17 @@ export const deserializeRequestClause = ( } } })(); + const headerSubfield = (() => { + const child = getChildString(node, terms.RequestHeaderSubfieldName, doc); + if (isPresent(child)) { + switch (child) { + case "name": + return "name"; + case "value": + return "value"; + } + } + })(); const intField = (() => { const child = getChildString(node, terms.RequestIntFieldName, doc); @@ -95,7 +150,9 @@ export const deserializeRequestClause = ( if (isPresent(stringField) && isPresent(stringFilter)) { return stringFilter.map((filter) => { return { - [stringField]: filter, + request: { + [stringField]: filter, + }, }; }); } @@ -103,7 +160,9 @@ export const deserializeRequestClause = ( if (isPresent(intField) && isPresent(intFilter)) { return intFilter.map((filter) => { return { - [intField]: filter, + request: { + [intField]: filter, + }, }; }); } @@ -111,7 +170,9 @@ export const deserializeRequestClause = ( if (isPresent(dateField) && isPresent(dateFilter)) { return dateFilter.map((filter) => { return { - [dateField]: filter, + request: { + [dateField]: filter, + }, }; }); } @@ -119,7 +180,21 @@ export const deserializeRequestClause = ( if (isPresent(boolField) && isPresent(boolFilter)) { return boolFilter.map((filter) => { return { - [boolField]: filter, + request: { + [boolField]: filter, + }, + }; + }); + } + + if (isPresent(headerSubfield) && isPresent(stringFilter)) { + return stringFilter.map((filter) => { + return { + request: { + header: { + [headerSubfield]: filter, + }, + }, }; }); } diff --git a/js/httpql/src/deserialize/clause.ts b/js/httpql/src/deserialize/clause.ts index fed77b1..4c54bc0 100644 --- a/js/httpql/src/deserialize/clause.ts +++ b/js/httpql/src/deserialize/clause.ts @@ -24,9 +24,7 @@ export const deserializeClause = ( const requestClause = node.getChild(terms.RequestClause); if (isPresent(requestClause)) { - return deserializeRequestClause(requestClause, doc).map((request) => ({ - request, - })); + return deserializeRequestClause(requestClause, doc); } const responseClause = node.getChild(terms.ResponseClause); diff --git a/js/httpql/src/index.spec.ts b/js/httpql/src/index.spec.ts index 67594cc..2497034 100644 --- a/js/httpql/src/index.spec.ts +++ b/js/httpql/src/index.spec.ts @@ -26,7 +26,8 @@ const readFile = (path: string) => { describe("httpql", () => { describe("ast", () => { const cases = [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, ]; for (const c of cases) { it(`Case ${c}`, () => { diff --git a/js/httpql/src/parser/parser.terms.ts b/js/httpql/src/parser/parser.terms.ts index 4b69082..2b81d4c 100644 --- a/js/httpql/src/parser/parser.terms.ts +++ b/js/httpql/src/parser/parser.terms.ts @@ -1,5 +1,6 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. -export const BlockComment = 1, +export const + BlockComment = 1, LineComment = 2, HTTPQL = 3, Query = 4, @@ -33,21 +34,25 @@ export const BlockComment = 1, BoolExpression = 32, BoolOperator = 33, BoolValue = 34, - ResponseClause = 35, - ResponseNamespace = 36, - ResponseIntFieldName = 37, - ResponseStringFieldName = 38, - PresetClause = 39, - PresetNamespace = 40, - PresetNameExpression = 41, - PresetAliasExpression = 42, - SymbolValue = 43, - SourceClause = 44, - SourceNamespace = 45, - SourceNameExpression = 46, - RightParen = 47, - LeftParen = 48, - GroupQuery = 49, - LogicalQuery = 50, - And = 51, - Or = 52; + RequestHeaderFieldName = 35, + RequestHeaderSubfieldName = 36, + LeftBracket = 37, + RightBracket = 38, + ResponseClause = 39, + ResponseNamespace = 40, + ResponseIntFieldName = 41, + ResponseStringFieldName = 42, + PresetClause = 43, + PresetNamespace = 44, + PresetNameExpression = 45, + PresetAliasExpression = 46, + SymbolValue = 47, + SourceClause = 48, + SourceNamespace = 49, + SourceNameExpression = 50, + RightParen = 51, + LeftParen = 52, + GroupQuery = 53, + LogicalQuery = 54, + And = 55, + Or = 56 diff --git a/js/httpql/src/parser/parser.ts b/js/httpql/src/parser/parser.ts index 49678ac..3d2128e 100644 --- a/js/httpql/src/parser/parser.ts +++ b/js/httpql/src/parser/parser.ts @@ -1,39 +1,22 @@ // This file was generated by lezer-generator. You probably shouldn't edit it. -import { LocalTokenGroup, LRParser } from "@lezer/lr"; - -import { highlight } from "./props.js"; +import {LRParser, LocalTokenGroup} from "@lezer/lr" +import {highlight} from "./props.js" export const parser = LRParser.deserialize({ version: 14, - states: - "+^Q]QPOOPtOPOOOyOQO'#CcOOQO'#Cb'#CbO!ROPO'#CeO!WOPO'#CmO!]OPO'#DPO!bOPO'#DTO!gOPO'#DYOOQO'#Ca'#CaO!lQPO'#D_Q!sQPOOOOQO'#C`'#C`P!{O!bO'#C]POOO)C?])C?]OOOO'#Dd'#DdO#WOQO,58}OOQO,58},58}O#`OPO,59PO#eOPO,59XO#sOSO,59kO#{OWO,59oOoOPO,59tOOQO,59y,59yO$TQPO,59yO]QPO,59zO]QPO,59zPOOO'#Dc'#DcP$`O!bO,58wPOOO,58w,58wOOOO-E7b-E7bOOQO1G.i1G.iO$kOPO1G.kO$pOPO1G.sO$uOPO1G.sO$zOPO1G.sO%POPO1G.sO%UOPO1G/VO%ZOPO1G/VOOQO'#DV'#DVOOQO'#DW'#DWOOQO1G/Z1G/ZOOQO'#D['#D[OOQO1G/`1G/`OOQO1G/e1G/eOOQO1G/f1G/fO%`QPO1G/fPOOO-E7a-E7aPOOO1G.c1G.cO%nOPO7+$VO%nOPO7+$_O%sOSO7+$_O%{OSO7+$_O&QO`O7+$_O%nOPO7+$qO%sOSO7+$qO&VOPO'#CiOOQO<k#b#cAU#c#dE}#d#eF{#e#fLg#f#gNi#g#h!(a#h#i!+_#i#o%v#o#y%^#y#z%V#z$f%^$f$g%V$g#BY%^#BY#BZ%V#BZ$IS%^$IS$I_%V$I_$I|%^$I|$JO%V$JO$JT%^$JT$JU%V$JU$KV%^$KV$KW%V$KW&FU%^&FU&FV%V&FV;'S%^;'S;=`!-c<%lO%^S%VO![S[%^OWW![SW%cOWW~%hO!a~[%oO!QSWW[%vO!PSWWx%}SWW{p}!O&Z!Q![&Z#R#S&Z#T#o&Zp&`S{p}!O&Z!Q![&Z#R#S&Z#T#o&Z[&sOZSWWl&zQi`WWz{'Q!P!Q'VS'VO!]SS'[SQSOY'VZ;'S'V;'S;=`'h<%lO'VS'kP;=`<%l'V|'wS`SWW{p}!O&Z!Q![(T#R#S&Z#T#o&Zt([S`S{p}!O&Z!Q![(T#R#S&Z#T#o&Z[(oO_SWW[(tPWW!p!q(wS(zP!f!g(}S)SO!TS[)XPWW!t!u)[S)aO!USW)dXrs%^!P!Q%^#O#P%^#U#V%^#Y#Z%^#b#c%^#f#g%^#h#i%^#i#j*PW*SR!Q![*]!c!i*]#T#Z*]W*`R!Q![*i!c!i*i#T#Z*iW*lR!Q![*u!c!i*u#T#Z*uW*xR!Q![%^!c!i%^#T#Z%^|+YUWW{p}!O&Z!Q![&Z#R#S&Z#T#b&Z#b#c+l#c#o&Zt+qU{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X,T#X#o&Zt,[S!TS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!^,oWWW{p}!O&Z!Q![&Z#R#S&Z#T#c&Z#c#d-X#d#f&Z#f#g/n#g#o&Z!Q-^W{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X-v#X#b&Z#b#c.r#c#o&Z!Q-{U{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y._#Y#o&Z!Q.fSu`{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!Q.wU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i/Z#i#o&Z!Q/bSf`{p}!O&Z!Q![&Z#R#S&Z#T#o&Zt/sU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y0V#Y#o&Zt0[T{p}!O&Z!Q![&Z#R#S&Z#T#U0k#U#o&Zt0pU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i1S#i#o&Zt1XU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y1k#Y#o&Zt1pU{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X2S#X#o&Zt2XS{p}!O&Z!Q![&Z#R#S2e#T#o&Zt2jT{p}!O&Z!Q![&Z#R#S&Z#T#U2y#U#o&Zt3OU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i3b#i#o&Zt3iSlS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z#p3|WWW{p}!O&Z!Q![&Z#R#S&Z#T#e&Z#e#f4f#f#l&Z#l#m4}#m#o&Z#h4qSq!b^Sf`{p}!O&Z!Q![&Z#R#S&Z#T#o&Zt5SU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i5f#i#o&Zt5mSdS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|6QTWW{p}!O&Z!Q![&Z#R#S&Z#T#U6a#U#o&Zt6fU{p}!O&Z!Q![&Z#R#S&Z#T#`&Z#`#a6x#a#o&Zt6}U{p}!O&Z!Q![&Z#R#S&Z#T#g&Z#g#h7a#h#o&Zt7fU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y7x#Y#o&Zt8PSrS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!^8dUWW{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i8v#i#o&Z!U9PUn`^S{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y9c#Y#o&Zt9jS^S{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|9}UWW{p}!O&Z!Q![&Z#R#S&Z#T#c&Z#c#d:a#d#o&Zt:fU{p}!O&Z!Q![&Z#R#S&Z#T#g&Z#g#h4}#h#o&Z|;PUWW{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X;c#X#o&Zt;jS[S{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!^;}YWW{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YS#`#o&Z!Q>XU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y/Z#Y#o&Z|>rUWW{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y?U#Y#o&Zt?ZU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i?m#i#o&Zt?rU{p}!O&Z!Q![&Z#R#S&Z#T#[&Z#[#]@U#]#o&Zt@ZU{p}!O&Z!Q![&Z#R#S&Z#T#c&Z#c#d@m#d#o&Zt@rU{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X5f#X#o&Z#pA][WW{p}!O&Z!Q![&Z#R#S&Z#T#V&Z#V#WBR#W#X&Z#X#Y4f#Y#`&Z#`#aCR#a#f&Z#f#gCj#g#o&Z!QBWU{p}!O&Z!Q![&Z#R#S&Z#T#c&Z#c#dBj#d#o&Z!QBoU{p}!O&Z!Q![&Z#R#S&Z#T#b&Z#b#c.r#c#o&Z!QCWU{p}!O&Z!Q![&Z#R#S&Z#T#]&Z#]#^=k#^#o&Z!QCoU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YDR#Y#o&Z!QDWU{p}!O&Z!Q![&Z#R#S&Z#T#Z&Z#Z#[Dj#[#o&Z!QDoU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YER#Y#o&Z!QEWU{p}!O&Z!Q![&Z#R#S&Z#T#l&Z#l#mEj#m#o&Z!QEqSg`{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|FUUWW{p}!O&Z!Q![&Z#R#S&Z#T#f&Z#f#gFh#g#o&ZtFoS!US{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|GSXWW{p}!O&Z!Q![&Z#R#S&Z#T#UGo#U#c&Z#c#dHo#d#f&Z#f#gJS#g#o&ZtGtU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#iHW#i#o&ZtH]U{p}!O&Z!Q![&Z#R#S&Z#T#[&Z#[#]5f#]#o&ZtHtU{p}!O&Z!Q![&Z#R#S&Z#T#f&Z#f#gIW#g#o&ZtI]U{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#iIo#i#o&ZtIvScS{p}!O&Z!Q![&Z#R#S&Z#T#o&ZtJXU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YJk#Y#o&ZtJpU{p}!O&Z!Q![&Z#R#S&Z#T#g&Z#g#hKS#h#o&ZtKXU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YKk#Y#o&ZtKpU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#iLS#i#o&ZtLZSxS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|LnUWW{p}!O&Z!Q![&Z#R#S&Z#T#i&Z#i#jMQ#j#o&ZtMVU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#YMi#Y#o&ZtMnU{p}!O&Z!Q![&Z#R#S&Z#T#f&Z#f#gNQ#g#o&ZtNVU{p}!O&Z!Q![&Z#R#S&Z#T#m&Z#m#n5f#n#o&Z!^NpXWW{p}!O&Z!Q![&Z#R#S&Z#T#U! ]#U#X&Z#X#Y!!Z#Y#c&Z#c#d!$_#d#o&Z!U! bU{p}!O&Z!Q![&Z#R#S&Z#T#k&Z#k#l! t#l#o&Z!U! }SdSv`{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!U!!`Y{p}!O&Z!Q![&Z#R#S&Z#T#Z&Z#Z#[Dj#[#e&Z#e#f!#O#f#g&Z#g#h!#c#h#o&Zt!#VSbS{p}!O&Z!Q![&Z#R#S&Z#T#o&Zt!#hU{p}!O&Z!Q![&Z#R#S&Z#T#d&Z#d#e!#z#e#o&Zt!$RStS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z!U!$dW{p}!O&Z!Q![&Z#R#S&Z#T#i&Z#i#j!$|#j#k&Z#k#l!'|#l#o&Z!Q!%RU{p}!O&Z!Q![&Z#R#S&Z#T#b&Z#b#c!%e#c#o&Z!Q!%jU{p}!O&Z!Q![&Z#R#S&Z#T#W&Z#W#X!%|#X#o&Z!Q!&RU{p}!O&Z!Q![&Z#R#S&Z#T#h&Z#h#i!&e#i#o&Z!Q!&jU{p}!O&Z!Q![&Z#R#S&Z#T#f&Z#f#g!&|#g#o&Z!Q!'RU{p}!O&Z!Q![&Z#R#S&Z#T#]&Z#]#^!'e#^#o&Z!Q!'jU{p}!O&Z!Q![&Z#R#S&Z#T#d&Z#d#e._#e#o&Zt!(TSYS{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|!(hUWW{p}!O&Z!Q![&Z#R#S&Z#T#c&Z#c#d!(z#d#o&Zt!)PU{p}!O&Z!Q![&Z#R#S&Z#T#i&Z#i#j!)c#j#o&Zt!)hU{p}!O&Z!Q![&Z#R#S&Z#T#f&Z#f#g!)z#g#o&Zt!*PU{p}!O&Z!Q![&Z#R#S&Z#T#V&Z#V#W!*c#W#o&Zt!*hU{p}!O&Z!Q![&Z#R#S&Z#T#X&Z#X#Y!*z#Y#o&Zt!+RS}S{p}!O&Z!Q![&Z#R#S&Z#T#o&Z|!+fWWW{p}!O&Z!Q![&Z#R#S&Z#T#`&Z#`#a!,O#a#f&Z#f#g!,z#g#o&Zt!,TU{p}!O&Z!Q![&Z#R#S&Z#T#g&Z#g#h!,g#h#o&Zt!,nSoS{p}!O&Z!Q![&Z#R#S&Z#T#o&Zt!-PU{p}!O&Z!Q![&Z#R#S&Z#T#i&Z#i#j7a#j#o&ZW!-fP;=`<%l%^", - tokenizers: [ - 2, - 3, - 4, - 5, - 6, - new LocalTokenGroup( - "x~RQ!P!QX#O#P^~^Ok~~aRO;'Sj;'S;=`o;=`Oj~oO!c~~tP!c~;=`<%lj~", - 39, - 64, - ), - new LocalTokenGroup("u~RRYZ[]^az{i~aO!_~~fP!_~YZ[~lP!P!Qo~tO!`~~", 36, 60), - ], - topRules: { HTTPQL: [0, 3] }, - tokenPrec: 0, -}); + tokenData: "!3i~R!cX^%^pq%cqr%jrs%osx%jxy%tyz%{z}%j}!O&S!O!P&x!P!Q'P!Q!['z![!](t!]!c%j!c!d({!d!q%j!q!r)`!r!}%j!}#O)m#O#P)t#P#Q+f#Q#R%j#R#S&S#S#T%j#T#U+m#U#V&S#V#W-S#W#X&S#X#Y4a#Y#Z6e#Z#[8w#[#]:b#]#^=z#^#`&S#`#a>x#a#bAm#b#cDW#c#dJg#d#eKe#e#f!#P#f#g!%R#g#h!,y#h#i!/w#i#j&S#j#k!1{#k#o&S#o#y%j#y#z%c#z$f%j$f$g%c$g#BY%j#BY#BZ%c#BZ$IS%j$IS$I_%c$I_$I|%j$I|$JO%c$JO$JT%j$JT$JU%c$JU$KV%j$KV$KW%c$KW&FU%j&FU&FV%c&FV;'S%j;'S;=`!3c<%lO%jS%cO!`S[%jOWW!`SW%oOWW~%tO!e~[%{O!USWW[&SO!TSWWx&ZSWW!Pp}!O&g!Q![&g#R#S&g#T#o&gp&lS!Pp}!O&g!Q![&g#R#S&g#T#o&g['POZSWWl'WQi`WWz{'^!P!Q'cS'cO!aSS'hSQSOY'cZ;'S'c;'S;=`'t<%lO'cS'wP;=`<%l'c|(TS`SWW!Pp}!O&g!Q![(a#R#S&g#T#o>(hS`S!Pp}!O&g!Q![(a#R#S&g#T#o&g[({O_SWW[)QPWW!p!q)TS)WP!f!g)ZS)`O!XS[)ePWW!t!u)hS)mO!YS[)tOuSWWW)wXrs%j!P!Q%j#O#P%j#U#V%j#Y#Z%j#b#c%j#f#g%j#h#i%j#i#j*dW*gR!Q![*p!c!i*p#T#Z*pW*sR!Q![*|!c!i*|#T#Z*|W+PR!Q![+Y!c!i+Y#T#Z+YW+]R!Q![%j!c!i%j#T#Z%j[+mOvSWW|+tUWW!Pp}!O&g!Q![&g#R#S&g#T#b&g#b#c,W#c#o>,]U!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X,o#X#o>,vS!XS!Pp}!O&g!Q![&g#R#S&g#T#o&g!^-ZWWW!Pp}!O&g!Q![&g#R#S&g#T#c&g#c#d-s#d#f&g#f#g0Y#g#o&g!Q-xW!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X.b#X#b&g#b#c/^#c#o&g!Q.gU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y.y#Y#o&g!Q/QSy`!Pp}!O&g!Q![&g#R#S&g#T#o&g!Q/cU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i/u#i#o&g!Q/|Sf`!Pp}!O&g!Q![&g#R#S&g#T#o>0_U!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y0q#Y#o>0vT!Pp}!O&g!Q![&g#R#S&g#T#U1V#U#o>1[U!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i1n#i#o>1sU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y2V#Y#o>2[U!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X2n#X#o>2sS!Pp}!O&g!Q![&g#R#S3P#T#o>3UT!Pp}!O&g!Q![&g#R#S&g#T#U3e#U#o>3jU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i3|#i#o>4TSlS!Pp}!O&g!Q![&g#R#S&g#T#o&g#p4hWWW!Pp}!O&g!Q![&g#R#S&g#T#e&g#e#f5Q#f#l&g#l#m5i#m#o&g#h5]Sq!b^Sf`!Pp}!O&g!Q![&g#R#S&g#T#o>5nU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i6Q#i#o>6XSdS!Pp}!O&g!Q![&g#R#S&g#T#o&g|6lTWW!Pp}!O&g!Q![&g#R#S&g#T#U6{#U#o>7QU!Pp}!O&g!Q![&g#R#S&g#T#`&g#`#a7d#a#o>7iU!Pp}!O&g!Q![&g#R#S&g#T#g&g#g#h7{#h#o>8QU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y8d#Y#o>8kSrS!Pp}!O&g!Q![&g#R#S&g#T#o&g!^9OUWW!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i9b#i#o&g!U9kUn`^S!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y9}#Y#o>:US^S!Pp}!O&g!Q![&g#R#S&g#T#o&g|:iWWW!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y;R#Y#c&g#c#d=c#d#o>WT!Pp}!O&g!Q![&g#R#S&g#T#U;g#U#o>lU!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#XRUWW!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X>e#X#o>>lS[S!Pp}!O&g!Q![&g#R#S&g#T#o&g!^?PYWW!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y?o#Y#]&g#]#^@m#^#h&g#h#i9b#i#o&g!U?tU!Pp}!O&g!Q![&g#R#S&g#T#b&g#b#c@W#c#o&g!U@aScSy`!Pp}!O&g!Q![&g#R#S&g#T#o&g!Q@rU!Pp}!O&g!Q![&g#R#S&g#T#_&g#_#`AU#`#o&g!QAZU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y/u#Y#o&g|AtUWW!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#YBW#Y#o>B]U!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#iBo#i#o>BtU!Pp}!O&g!Q![&g#R#S&g#T#[&g#[#]CW#]#o>C]U!Pp}!O&g!Q![&g#R#S&g#T#c&g#c#dCo#d#o>CtU!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X6Q#X#o&g#pD_]WW!Pp}!O&g!Q![&g#R#S&g#T#UEW#U#V&g#V#WFk#W#X&g#X#Y5Q#Y#`&g#`#aGk#a#f&g#f#gHS#g#o>E]U!Pp}!O&g!Q![&g#R#S&g#T#a&g#a#bEo#b#o>EtU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#YFW#Y#o>F_StS!Pp}!O&g!Q![&g#R#S&g#T#o&g!QFpU!Pp}!O&g!Q![&g#R#S&g#T#c&g#c#dGS#d#o&g!QGXU!Pp}!O&g!Q![&g#R#S&g#T#b&g#b#c/^#c#o&g!QGpU!Pp}!O&g!Q![&g#R#S&g#T#]&g#]#^@m#^#o&g!QHXU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#YHk#Y#o&g!QHpU!Pp}!O&g!Q![&g#R#S&g#T#Z&g#Z#[IS#[#o&g!QIXU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#YIk#Y#o&g!QIpU!Pp}!O&g!Q![&g#R#S&g#T#l&g#l#mJS#m#o&g!QJZSg`!Pp}!O&g!Q![&g#R#S&g#T#o&g|JnUWW!Pp}!O&g!Q![&g#R#S&g#T#f&g#f#gKQ#g#o>KXS!YS!Pp}!O&g!Q![&g#R#S&g#T#o&g|KlXWW!Pp}!O&g!Q![&g#R#S&g#T#ULX#U#c&g#c#dMX#d#f&g#f#gNl#g#o>L^U!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#iLp#i#o>LuU!Pp}!O&g!Q![&g#R#S&g#T#[&g#[#]6Q#]#o>M^U!Pp}!O&g!Q![&g#R#S&g#T#f&g#f#gMp#g#o>MuU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#iNX#i#o>N`ScS!Pp}!O&g!Q![&g#R#S&g#T#o>NqU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y! T#Y#o>! YU!Pp}!O&g!Q![&g#R#S&g#T#g&g#g#h! l#h#o>! qU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y!!T#Y#o>!!YU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i!!l#i#o>!!sS|S!Pp}!O&g!Q![&g#R#S&g#T#o&g|!#WUWW!Pp}!O&g!Q![&g#R#S&g#T#i&g#i#j!#j#j#o>!#oU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y!$R#Y#o>!$WU!Pp}!O&g!Q![&g#R#S&g#T#f&g#f#g!$j#g#o>!$oU!Pp}!O&g!Q![&g#R#S&g#T#m&g#m#n6Q#n#o&g!^!%YXWW!Pp}!O&g!Q![&g#R#S&g#T#U!%u#U#X&g#X#Y!&s#Y#c&g#c#d!(w#d#o&g!U!%zU!Pp}!O&g!Q![&g#R#S&g#T#k&g#k#l!&^#l#o&g!U!&gSdSz`!Pp}!O&g!Q![&g#R#S&g#T#o&g!U!&xY!Pp}!O&g!Q![&g#R#S&g#T#Z&g#Z#[IS#[#e&g#e#f!'h#f#g&g#g#h!'{#h#o>!'oSbS!Pp}!O&g!Q![&g#R#S&g#T#o>!(QU!Pp}!O&g!Q![&g#R#S&g#T#d&g#d#e!(d#e#o>!(kSxS!Pp}!O&g!Q![&g#R#S&g#T#o&g!U!(|W!Pp}!O&g!Q![&g#R#S&g#T#i&g#i#j!)f#j#k&g#k#l!,f#l#o&g!Q!)kU!Pp}!O&g!Q![&g#R#S&g#T#b&g#b#c!)}#c#o&g!Q!*SU!Pp}!O&g!Q![&g#R#S&g#T#W&g#W#X!*f#X#o&g!Q!*kU!Pp}!O&g!Q![&g#R#S&g#T#h&g#h#i!*}#i#o&g!Q!+SU!Pp}!O&g!Q![&g#R#S&g#T#f&g#f#g!+f#g#o&g!Q!+kU!Pp}!O&g!Q![&g#R#S&g#T#]&g#]#^!+}#^#o&g!Q!,SU!Pp}!O&g!Q![&g#R#S&g#T#d&g#d#e.y#e#o>!,mSYS!Pp}!O&g!Q![&g#R#S&g#T#o&g|!-QUWW!Pp}!O&g!Q![&g#R#S&g#T#c&g#c#d!-d#d#o>!-iU!Pp}!O&g!Q![&g#R#S&g#T#i&g#i#j!-{#j#o>!.QU!Pp}!O&g!Q![&g#R#S&g#T#f&g#f#g!.d#g#o>!.iU!Pp}!O&g!Q![&g#R#S&g#T#V&g#V#W!.{#W#o>!/QU!Pp}!O&g!Q![&g#R#S&g#T#X&g#X#Y!/d#Y#o>!/kS!RS!Pp}!O&g!Q![&g#R#S&g#T#o&g|!0OWWW!Pp}!O&g!Q![&g#R#S&g#T#`&g#`#a!0h#a#f&g#f#g!1d#g#o>!0mU!Pp}!O&g!Q![&g#R#S&g#T#g&g#g#h!1P#h#o>!1WSoS!Pp}!O&g!Q![&g#R#S&g#T#o>!1iU!Pp}!O&g!Q![&g#R#S&g#T#i&g#i#j7{#j#o&g|!2STWW!Pp}!O&g!Q![&g#R#S&g#T#U!2c#U#o>!2hU!Pp}!O&g!Q![&g#R#S&g#T#`&g#`#a!2z#a#o>!3PU!Pp}!O&g!Q![&g#R#S&g#T#i&g#i#jEo#j#o&gW!3fP;=`<%l%j", + tokenizers: [2, 3, 4, 5, 6, new LocalTokenGroup("x~RQ!P!QX#O#P^~^Ok~~aRO;'Sj;'S;=`o;=`Oj~oO!g~~tP!g~;=`<%lj~", 39, 68), new LocalTokenGroup("u~RRYZ[]^az{i~aO!c~~fP!c~YZ[~lP!P!Qo~tO!d~~", 36, 64)], + topRules: {"HTTPQL":[0,3]}, + tokenPrec: 0 +}) diff --git a/js/httpql/src/primitives.ts b/js/httpql/src/primitives.ts index 2ae886f..1cae046 100644 --- a/js/httpql/src/primitives.ts +++ b/js/httpql/src/primitives.ts @@ -18,6 +18,7 @@ export type ClauseRow = { export type ClauseRequest = { createdAt?: Maybe; fileExtension?: Maybe; + header?: Maybe; host?: Maybe; isTLS?: Maybe; length?: Maybe; @@ -28,6 +29,11 @@ export type ClauseRequest = { raw?: Maybe; }; +export type ClauseRequestHeader = { + name?: Maybe; + value?: Maybe; +}; + export type ClauseResponse = { length?: Maybe; raw?: Maybe; diff --git a/js/httpql/src/serialize.ts b/js/httpql/src/serialize.ts index 986eb02..179d2ac 100644 --- a/js/httpql/src/serialize.ts +++ b/js/httpql/src/serialize.ts @@ -74,6 +74,16 @@ const serializeClauseRequest = ( if (isPresent(value.fileExtension)) { return serializeExprString(value.fileExtension).map((str) => `ext.${str}`); } + if (isPresent(value.header)) { + if (isPresent(value.header.name)) { + return serializeExprString(value.header.name).map((str) => `header.name.${str}`); + } + if (isPresent(value.header.value)) { + return serializeExprString(value.header.value).map( + (str) => `header.value.${str}`, + ); + } + } if (isPresent(value.host)) { return serializeExprString(value.host).map((str) => `host.${str}`); } diff --git a/rust/httpql/httpql.pest b/rust/httpql/httpql.pest index 000b9ad..284c9e2 100644 --- a/rust/httpql/httpql.pest +++ b/rust/httpql/httpql.pest @@ -6,6 +6,8 @@ LeftParen = _{ "(" } RightParen = _{ ")" } Dot = _{ "." } Colon = _{ ":" } +LeftBracket = _{ "[" } +RightBracket = _{ "]" } // Logical operators And = { "AND" | "and" } @@ -26,6 +28,8 @@ RequestStringFieldName = { "ext" | "host" | "method" | "path" | "query" | "raw" RequestIntFieldName = { "len" | "port" } RequestDateFieldName = { "created_at" } RequestBoolFieldName = { "tls" } +RequestHeaderFieldName = _{ "header" } +RequestHeaderSubfieldName = { "name" | "value" } // Response field names ResponseStringFieldName = { "raw" } @@ -82,6 +86,8 @@ RequestClause = ${ | RequestNamespace ~ Dot ~ RequestStringFieldName ~ Dot ~ StringExpression | RequestNamespace ~ Dot ~ RequestDateFieldName ~ Dot ~ DateExpression | RequestNamespace ~ Dot ~ RequestBoolFieldName ~ Dot ~ BoolExpression + | RequestNamespace ~ Dot ~ RequestHeaderFieldName ~ Dot ~ RequestHeaderSubfieldName ~ Dot ~ StringExpression + | RequestNamespace ~ Dot ~ RequestHeaderFieldName ~ LeftBracket ~ StringValue ~ RightBracket ~ Dot ~ StringExpression } ResponseClause = ${ diff --git a/rust/httpql/src/lib.rs b/rust/httpql/src/lib.rs index d3a2f58..cb6c2b4 100644 --- a/rust/httpql/src/lib.rs +++ b/rust/httpql/src/lib.rs @@ -48,6 +48,8 @@ mod tests { #[case(17)] #[case(18)] #[case(19)] + #[case(20)] + #[case(21)] fn test_ast(#[case] case: u32) { let input = std::fs::read_to_string(format!("../../tests/httpql/ast/{case}/input.httpql")).unwrap(); diff --git a/rust/httpql/src/parser.rs b/rust/httpql/src/parser.rs index 0476fb3..6cc103b 100644 --- a/rust/httpql/src/parser.rs +++ b/rust/httpql/src/parser.rs @@ -190,7 +190,7 @@ fn build_row_clause_ast(pair: Pair) -> Result { Ok(clause) } -fn build_request_clause_ast(pair: Pair) -> Result { +fn build_request_clause_ast(pair: Pair) -> Result { let mut clause = ClauseRequest::default(); let mut pair = pair.into_inner(); @@ -239,10 +239,68 @@ fn build_request_clause_ast(pair: Pair) -> Result { } t => unknown!("RequestBoolFieldName.{}", t), }, + Rule::RequestHeaderSubfieldName => match field.as_str() { + "name" => { + clause.header = Some(ClauseHeaderRequest { + name: Some(build_expr_string_ast(expr)?), + ..Default::default() + }); + } + "value" => { + clause.header = Some(ClauseHeaderRequest { + value: Some(build_expr_string_ast(expr)?), + ..Default::default() + }); + } + t => unknown!("RequestHeaderSubfieldName.{}", t), + }, + Rule::StringValue => { + let ParsedString { + value: header_name, + is_raw, + } = build_string_ast(field)?; + if is_raw { + invalid!("RequestClause.header_name"); + } + + let header_name_clause = Query { + request: Some(ClauseRequest { + header: Some(ClauseHeaderRequest { + name: Some(ExprString { + value: header_name, + operator: OperatorString::Eq, + is_raw: false, + }), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }; + + let header_value_clause = Query { + request: Some(ClauseRequest { + header: Some(ClauseHeaderRequest { + value: Some(build_expr_string_ast(expr)?), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }; + + return Ok(Query { + and: Some((Box::new(header_name_clause), Box::new(header_value_clause))), + ..Default::default() + }); + } t => unknown!("RequestClause.field.{:?}", t), }; - Ok(clause) + Ok(Query { + request: Some(clause), + ..Default::default() + }) } fn build_response_clause_ast(pair: Pair) -> Result { @@ -352,11 +410,7 @@ fn build_clause_ast(pair: Pair) -> Result { Ok(query) } Rule::RequestClause => { - let query = Query { - request: Some(build_request_clause_ast(pair)?), - ..Default::default() - }; - Ok(query) + build_request_clause_ast(pair) } Rule::ResponseClause => { let query = Query { diff --git a/rust/httpql/src/primitives.rs b/rust/httpql/src/primitives.rs index 411e9f5..dfc8d25 100644 --- a/rust/httpql/src/primitives.rs +++ b/rust/httpql/src/primitives.rs @@ -82,6 +82,7 @@ impl fmt::Display for ClauseResponse { pub struct ClauseRequest { pub created_at: Option, pub file_extension: Option, + pub header: Option, pub host: Option, pub is_tls: Option, pub length: Option, @@ -92,6 +93,24 @@ pub struct ClauseRequest { pub raw: Option, } +#[derive(Clone, Debug, Default)] +pub struct ClauseHeaderRequest { + pub name: Option, + pub value: Option, +} + +impl fmt::Display for ClauseHeaderRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(expr) = &self.name { + return write!(f, "name.{}", expr); + } + if let Some(expr) = &self.value { + return write!(f, "value.{}", expr); + } + Err(fmt::Error) + } +} + impl fmt::Display for ClauseRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(expr) = &self.created_at { @@ -100,6 +119,9 @@ impl fmt::Display for ClauseRequest { if let Some(expr) = &self.file_extension { return write!(f, "ext.{}", expr); } + if let Some(expr) = &self.header { + return write!(f, "header.{}", expr); + } if let Some(expr) = &self.host { return write!(f, "host.{}", expr); } diff --git a/tests/httpql/ast/20/input.httpql b/tests/httpql/ast/20/input.httpql new file mode 100644 index 0000000..b97e9cf --- /dev/null +++ b/tests/httpql/ast/20/input.httpql @@ -0,0 +1 @@ +req.header.name.eq:"Content-Type" and req.header.value.cont:"json" diff --git a/tests/httpql/ast/20/output.ast b/tests/httpql/ast/20/output.ast new file mode 100644 index 0000000..e6e0978 --- /dev/null +++ b/tests/httpql/ast/20/output.ast @@ -0,0 +1 @@ +(req.header.name.eq:"Content-Type" and req.header.value.cont:"json") diff --git a/tests/httpql/ast/21/input.httpql b/tests/httpql/ast/21/input.httpql new file mode 100644 index 0000000..dffc675 --- /dev/null +++ b/tests/httpql/ast/21/input.httpql @@ -0,0 +1 @@ +req.header["Content-Type"].cont:"json" diff --git a/tests/httpql/ast/21/output.ast b/tests/httpql/ast/21/output.ast new file mode 100644 index 0000000..e6e0978 --- /dev/null +++ b/tests/httpql/ast/21/output.ast @@ -0,0 +1 @@ +(req.header.name.eq:"Content-Type" and req.header.value.cont:"json")