From 16f894f81b9e4679f313c55db2abd59854131796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Tue, 2 Jan 2024 12:54:37 +0100 Subject: [PATCH] feat: add support for fragments (#21) --- README.md | 12 +- src/parse/callbacks/fragment-marker.js | 12 + .../{query-literal.js => fragment.js} | 6 +- src/parse/index.js | 6 +- src/path-templating.bnf | 7 +- src/path-templating.js | 233 ++++++++++-------- test/parse.js | 53 ++-- test/resolve.js | 10 +- test/test.js | 8 +- 9 files changed, 204 insertions(+), 143 deletions(-) create mode 100644 src/parse/callbacks/fragment-marker.js rename src/parse/callbacks/{query-literal.js => fragment.js} (51%) diff --git a/README.md b/README.md index fed168e..cb149a4 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,9 @@ parseResult.result.success; // => true 'path-template': [Function: pathTemplate], path: [Function: path], query: [Function: query], - 'query-literal': [Function: queryLiteral], 'query-marker': [Function: queryMarker], + fragment: [Function: fragment], + 'fragment-marker': [Function: fragmentMarker], slash: [Function: slash], 'path-literal': [Function: pathLiteral], 'template-expression': [Function: templateExpression], @@ -171,7 +172,7 @@ import { test } from 'openapi-path-templating'; test('/pets/{petId}'); // => true test('/a{petId}'); // => true test('/pets'); // => true -test('/pets', { strict: true }); // => false (doesn't contain template-expression) +test('/pets', { strict: true }); // => false (doesn't contain any template-expression) ``` #### Resolution @@ -212,12 +213,15 @@ The Path Templating is defined by the following [ABNF](https://tools.ietf.org/ht ```abnf ; OpenAPI Path Templating ABNF syntax -path-template = path [ query-marker query ] +path-template = path [ query-marker query ] [ fragment-marker fragment ] path = slash *( path-segment slash ) [ path-segment ] path-segment = 1*( path-literal / template-expression ) -query = *( query-literal / template-expression ) +query = *( query-literal ) query-literal = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" / "&" / "=" ) query-marker = "?" +fragment = *( fragment-literal ) +fragment-literal = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" ) +fragment-marker = "#" slash = "/" path-literal = 1*( unreserved / pct-encoded / sub-delims-no-slash / ":" / "@" ) template-expression = "{" template-expression-param-name "}" diff --git a/src/parse/callbacks/fragment-marker.js b/src/parse/callbacks/fragment-marker.js new file mode 100644 index 0000000..7e76829 --- /dev/null +++ b/src/parse/callbacks/fragment-marker.js @@ -0,0 +1,12 @@ +import { identifiers, utilities } from '#apg-lite'; + +const fragmentMarker = (state, chars, phraseIndex, phraseLength, data) => { + if (state === identifiers.SEM_PRE) { + data.push(['fragment-marker', utilities.charsToString(chars, phraseIndex, phraseLength)]); + } else if (state === identifiers.SEM_POST) { + /* not used in this example */ + } + return identifiers.SEM_OK; +}; + +export default fragmentMarker; diff --git a/src/parse/callbacks/query-literal.js b/src/parse/callbacks/fragment.js similarity index 51% rename from src/parse/callbacks/query-literal.js rename to src/parse/callbacks/fragment.js index b48c3fe..b6127d4 100644 --- a/src/parse/callbacks/query-literal.js +++ b/src/parse/callbacks/fragment.js @@ -1,12 +1,12 @@ import { identifiers, utilities } from '#apg-lite'; -const queryLiteral = (state, chars, phraseIndex, phraseLength, data) => { +const fragment = (state, chars, phraseIndex, phraseLength, data) => { if (state === identifiers.SEM_PRE) { - data.push(['query-literal', utilities.charsToString(chars, phraseIndex, phraseLength)]); + data.push(['fragment', utilities.charsToString(chars, phraseIndex, phraseLength)]); } else if (state === identifiers.SEM_POST) { /* not used in this example */ } return identifiers.SEM_OK; }; -export default queryLiteral; +export default fragment; diff --git a/src/parse/index.js b/src/parse/index.js index 13a1bcc..dfc650d 100644 --- a/src/parse/index.js +++ b/src/parse/index.js @@ -6,8 +6,9 @@ import pathTemplateCallback from './callbacks/path-template.js'; import pathCallback from './callbacks/path.js'; import pathLiteralCallback from './callbacks/path-literal.js'; import queryCallback from './callbacks/query.js'; -import queryLiteralCallback from './callbacks/query-literal.js'; import queryMarkerCallback from './callbacks/query-marker.js'; +import fragmentCallback from './callbacks/fragment.js'; +import fragmentMarkerCallback from './callbacks/fragment-marker.js'; import templateExpressionCallback from './callbacks/template-expression.js'; import templateExpressionParamNameCallback from './callbacks/template-expression-param-name.js'; @@ -20,8 +21,9 @@ const parse = (str) => { parser.ast.callbacks['path-template'] = pathTemplateCallback; parser.ast.callbacks['path'] = pathCallback; parser.ast.callbacks['query'] = queryCallback; - parser.ast.callbacks['query-literal'] = queryLiteralCallback; parser.ast.callbacks['query-marker'] = queryMarkerCallback; + parser.ast.callbacks['fragment'] = fragmentCallback; + parser.ast.callbacks['fragment-marker'] = fragmentMarkerCallback; parser.ast.callbacks['slash'] = slashCallback; parser.ast.callbacks['path-literal'] = pathLiteralCallback; parser.ast.callbacks['template-expression'] = templateExpressionCallback; diff --git a/src/path-templating.bnf b/src/path-templating.bnf index 4f6ade8..3e3441d 100644 --- a/src/path-templating.bnf +++ b/src/path-templating.bnf @@ -1,10 +1,13 @@ ; OpenAPI Path Templating ABNF syntax -path-template = path [ query-marker query ] +path-template = path [ query-marker query ] [ fragment-marker fragment ] path = slash *( path-segment slash ) [ path-segment ] path-segment = 1*( path-literal / template-expression ) -query = *( query-literal / template-expression ) +query = *( query-literal ) query-literal = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" / "&" / "=" ) query-marker = "?" +fragment = *( fragment-literal ) +fragment-literal = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" / "/" / "?" ) +fragment-marker = "#" slash = "/" path-literal = 1*( unreserved / pct-encoded / sub-delims-no-slash / ":" / "@" ) template-expression = "{" template-expression-param-name "}" diff --git a/src/path-templating.js b/src/path-templating.js index 361607c..7f782ee 100644 --- a/src/path-templating.js +++ b/src/path-templating.js @@ -5,15 +5,15 @@ export default function grammar(){ // ``` // SUMMARY - // rules = 17 + // rules = 20 // udts = 0 - // opcodes = 99 + // opcodes = 113 // --- ABNF original opcodes // ALT = 10 - // CAT = 6 - // REP = 8 - // RNM = 26 - // TLS = 46 + // CAT = 7 + // REP = 11 + // RNM = 31 + // TLS = 51 // TBS = 0 // TRG = 3 // --- SABNF superset opcodes @@ -31,17 +31,20 @@ export default function grammar(){ this.rules[3] = {name: 'query', lower: 'query', index: 3, isBkr: false}; this.rules[4] = {name: 'query-literal', lower: 'query-literal', index: 4, isBkr: false}; this.rules[5] = {name: 'query-marker', lower: 'query-marker', index: 5, isBkr: false}; - this.rules[6] = {name: 'slash', lower: 'slash', index: 6, isBkr: false}; - this.rules[7] = {name: 'path-literal', lower: 'path-literal', index: 7, isBkr: false}; - this.rules[8] = {name: 'template-expression', lower: 'template-expression', index: 8, isBkr: false}; - this.rules[9] = {name: 'template-expression-param-name', lower: 'template-expression-param-name', index: 9, isBkr: false}; - this.rules[10] = {name: 'unreserved', lower: 'unreserved', index: 10, isBkr: false}; - this.rules[11] = {name: 'pct-encoded', lower: 'pct-encoded', index: 11, isBkr: false}; - this.rules[12] = {name: 'sub-delims', lower: 'sub-delims', index: 12, isBkr: false}; - this.rules[13] = {name: 'sub-delims-no-slash', lower: 'sub-delims-no-slash', index: 13, isBkr: false}; - this.rules[14] = {name: 'ALPHA', lower: 'alpha', index: 14, isBkr: false}; - this.rules[15] = {name: 'DIGIT', lower: 'digit', index: 15, isBkr: false}; - this.rules[16] = {name: 'HEXDIG', lower: 'hexdig', index: 16, isBkr: false}; + this.rules[6] = {name: 'fragment', lower: 'fragment', index: 6, isBkr: false}; + this.rules[7] = {name: 'fragment-literal', lower: 'fragment-literal', index: 7, isBkr: false}; + this.rules[8] = {name: 'fragment-marker', lower: 'fragment-marker', index: 8, isBkr: false}; + this.rules[9] = {name: 'slash', lower: 'slash', index: 9, isBkr: false}; + this.rules[10] = {name: 'path-literal', lower: 'path-literal', index: 10, isBkr: false}; + this.rules[11] = {name: 'template-expression', lower: 'template-expression', index: 11, isBkr: false}; + this.rules[12] = {name: 'template-expression-param-name', lower: 'template-expression-param-name', index: 12, isBkr: false}; + this.rules[13] = {name: 'unreserved', lower: 'unreserved', index: 13, isBkr: false}; + this.rules[14] = {name: 'pct-encoded', lower: 'pct-encoded', index: 14, isBkr: false}; + this.rules[15] = {name: 'sub-delims', lower: 'sub-delims', index: 15, isBkr: false}; + this.rules[16] = {name: 'sub-delims-no-slash', lower: 'sub-delims-no-slash', index: 16, isBkr: false}; + this.rules[17] = {name: 'ALPHA', lower: 'alpha', index: 17, isBkr: false}; + this.rules[18] = {name: 'DIGIT', lower: 'digit', index: 18, isBkr: false}; + this.rules[19] = {name: 'HEXDIG', lower: 'hexdig', index: 19, isBkr: false}; /* UDTS */ this.udts = []; @@ -49,21 +52,25 @@ export default function grammar(){ /* OPCODES */ /* path-template */ this.rules[0].opcodes = []; - this.rules[0].opcodes[0] = {type: 2, children: [1,2]};// CAT + this.rules[0].opcodes[0] = {type: 2, children: [1,2,6]};// CAT this.rules[0].opcodes[1] = {type: 4, index: 1};// RNM(path) this.rules[0].opcodes[2] = {type: 3, min: 0, max: 1};// REP this.rules[0].opcodes[3] = {type: 2, children: [4,5]};// CAT this.rules[0].opcodes[4] = {type: 4, index: 5};// RNM(query-marker) this.rules[0].opcodes[5] = {type: 4, index: 3};// RNM(query) + this.rules[0].opcodes[6] = {type: 3, min: 0, max: 1};// REP + this.rules[0].opcodes[7] = {type: 2, children: [8,9]};// CAT + this.rules[0].opcodes[8] = {type: 4, index: 8};// RNM(fragment-marker) + this.rules[0].opcodes[9] = {type: 4, index: 6};// RNM(fragment) /* path */ this.rules[1].opcodes = []; this.rules[1].opcodes[0] = {type: 2, children: [1,2,6]};// CAT - this.rules[1].opcodes[1] = {type: 4, index: 6};// RNM(slash) + this.rules[1].opcodes[1] = {type: 4, index: 9};// RNM(slash) this.rules[1].opcodes[2] = {type: 3, min: 0, max: Infinity};// REP this.rules[1].opcodes[3] = {type: 2, children: [4,5]};// CAT this.rules[1].opcodes[4] = {type: 4, index: 2};// RNM(path-segment) - this.rules[1].opcodes[5] = {type: 4, index: 6};// RNM(slash) + this.rules[1].opcodes[5] = {type: 4, index: 9};// RNM(slash) this.rules[1].opcodes[6] = {type: 3, min: 0, max: 1};// REP this.rules[1].opcodes[7] = {type: 4, index: 2};// RNM(path-segment) @@ -71,23 +78,21 @@ export default function grammar(){ this.rules[2].opcodes = []; this.rules[2].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP this.rules[2].opcodes[1] = {type: 1, children: [2,3]};// ALT - this.rules[2].opcodes[2] = {type: 4, index: 7};// RNM(path-literal) - this.rules[2].opcodes[3] = {type: 4, index: 8};// RNM(template-expression) + this.rules[2].opcodes[2] = {type: 4, index: 10};// RNM(path-literal) + this.rules[2].opcodes[3] = {type: 4, index: 11};// RNM(template-expression) /* query */ this.rules[3].opcodes = []; this.rules[3].opcodes[0] = {type: 3, min: 0, max: Infinity};// REP - this.rules[3].opcodes[1] = {type: 1, children: [2,3]};// ALT - this.rules[3].opcodes[2] = {type: 4, index: 4};// RNM(query-literal) - this.rules[3].opcodes[3] = {type: 4, index: 8};// RNM(template-expression) + this.rules[3].opcodes[1] = {type: 4, index: 4};// RNM(query-literal) /* query-literal */ this.rules[4].opcodes = []; this.rules[4].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP this.rules[4].opcodes[1] = {type: 1, children: [2,3,4,5,6,7,8,9,10]};// ALT - this.rules[4].opcodes[2] = {type: 4, index: 10};// RNM(unreserved) - this.rules[4].opcodes[3] = {type: 4, index: 11};// RNM(pct-encoded) - this.rules[4].opcodes[4] = {type: 4, index: 12};// RNM(sub-delims) + this.rules[4].opcodes[2] = {type: 4, index: 13};// RNM(unreserved) + this.rules[4].opcodes[3] = {type: 4, index: 14};// RNM(pct-encoded) + this.rules[4].opcodes[4] = {type: 4, index: 15};// RNM(sub-delims) this.rules[4].opcodes[5] = {type: 7, string: [58]};// TLS this.rules[4].opcodes[6] = {type: 7, string: [64]};// TLS this.rules[4].opcodes[7] = {type: 7, string: [47]};// TLS @@ -99,114 +104,138 @@ export default function grammar(){ this.rules[5].opcodes = []; this.rules[5].opcodes[0] = {type: 7, string: [63]};// TLS - /* slash */ + /* fragment */ this.rules[6].opcodes = []; - this.rules[6].opcodes[0] = {type: 7, string: [47]};// TLS + this.rules[6].opcodes[0] = {type: 3, min: 0, max: Infinity};// REP + this.rules[6].opcodes[1] = {type: 4, index: 7};// RNM(fragment-literal) - /* path-literal */ + /* fragment-literal */ this.rules[7].opcodes = []; this.rules[7].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP - this.rules[7].opcodes[1] = {type: 1, children: [2,3,4,5,6]};// ALT - this.rules[7].opcodes[2] = {type: 4, index: 10};// RNM(unreserved) - this.rules[7].opcodes[3] = {type: 4, index: 11};// RNM(pct-encoded) - this.rules[7].opcodes[4] = {type: 4, index: 13};// RNM(sub-delims-no-slash) + this.rules[7].opcodes[1] = {type: 1, children: [2,3,4,5,6,7,8]};// ALT + this.rules[7].opcodes[2] = {type: 4, index: 13};// RNM(unreserved) + this.rules[7].opcodes[3] = {type: 4, index: 14};// RNM(pct-encoded) + this.rules[7].opcodes[4] = {type: 4, index: 15};// RNM(sub-delims) this.rules[7].opcodes[5] = {type: 7, string: [58]};// TLS this.rules[7].opcodes[6] = {type: 7, string: [64]};// TLS + this.rules[7].opcodes[7] = {type: 7, string: [47]};// TLS + this.rules[7].opcodes[8] = {type: 7, string: [63]};// TLS - /* template-expression */ + /* fragment-marker */ this.rules[8].opcodes = []; - this.rules[8].opcodes[0] = {type: 2, children: [1,2,3]};// CAT - this.rules[8].opcodes[1] = {type: 7, string: [123]};// TLS - this.rules[8].opcodes[2] = {type: 4, index: 9};// RNM(template-expression-param-name) - this.rules[8].opcodes[3] = {type: 7, string: [125]};// TLS + this.rules[8].opcodes[0] = {type: 7, string: [35]};// TLS - /* template-expression-param-name */ + /* slash */ this.rules[9].opcodes = []; - this.rules[9].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP - this.rules[9].opcodes[1] = {type: 1, children: [2,3,4,5,6]};// ALT - this.rules[9].opcodes[2] = {type: 4, index: 10};// RNM(unreserved) - this.rules[9].opcodes[3] = {type: 4, index: 11};// RNM(pct-encoded) - this.rules[9].opcodes[4] = {type: 4, index: 13};// RNM(sub-delims-no-slash) - this.rules[9].opcodes[5] = {type: 7, string: [58]};// TLS - this.rules[9].opcodes[6] = {type: 7, string: [64]};// TLS + this.rules[9].opcodes[0] = {type: 7, string: [47]};// TLS - /* unreserved */ + /* path-literal */ this.rules[10].opcodes = []; - this.rules[10].opcodes[0] = {type: 1, children: [1,2,3,4,5,6]};// ALT - this.rules[10].opcodes[1] = {type: 4, index: 14};// RNM(ALPHA) - this.rules[10].opcodes[2] = {type: 4, index: 15};// RNM(DIGIT) - this.rules[10].opcodes[3] = {type: 7, string: [45]};// TLS - this.rules[10].opcodes[4] = {type: 7, string: [46]};// TLS - this.rules[10].opcodes[5] = {type: 7, string: [95]};// TLS - this.rules[10].opcodes[6] = {type: 7, string: [126]};// TLS + this.rules[10].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP + this.rules[10].opcodes[1] = {type: 1, children: [2,3,4,5,6]};// ALT + this.rules[10].opcodes[2] = {type: 4, index: 13};// RNM(unreserved) + this.rules[10].opcodes[3] = {type: 4, index: 14};// RNM(pct-encoded) + this.rules[10].opcodes[4] = {type: 4, index: 16};// RNM(sub-delims-no-slash) + this.rules[10].opcodes[5] = {type: 7, string: [58]};// TLS + this.rules[10].opcodes[6] = {type: 7, string: [64]};// TLS - /* pct-encoded */ + /* template-expression */ this.rules[11].opcodes = []; this.rules[11].opcodes[0] = {type: 2, children: [1,2,3]};// CAT - this.rules[11].opcodes[1] = {type: 7, string: [37]};// TLS - this.rules[11].opcodes[2] = {type: 4, index: 16};// RNM(HEXDIG) - this.rules[11].opcodes[3] = {type: 4, index: 16};// RNM(HEXDIG) + this.rules[11].opcodes[1] = {type: 7, string: [123]};// TLS + this.rules[11].opcodes[2] = {type: 4, index: 12};// RNM(template-expression-param-name) + this.rules[11].opcodes[3] = {type: 7, string: [125]};// TLS - /* sub-delims */ + /* template-expression-param-name */ this.rules[12].opcodes = []; - this.rules[12].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7,8,9,10,11]};// ALT - this.rules[12].opcodes[1] = {type: 7, string: [33]};// TLS - this.rules[12].opcodes[2] = {type: 7, string: [36]};// TLS - this.rules[12].opcodes[3] = {type: 7, string: [38]};// TLS - this.rules[12].opcodes[4] = {type: 7, string: [39]};// TLS - this.rules[12].opcodes[5] = {type: 7, string: [40]};// TLS - this.rules[12].opcodes[6] = {type: 7, string: [41]};// TLS - this.rules[12].opcodes[7] = {type: 7, string: [42]};// TLS - this.rules[12].opcodes[8] = {type: 7, string: [43]};// TLS - this.rules[12].opcodes[9] = {type: 7, string: [44]};// TLS - this.rules[12].opcodes[10] = {type: 7, string: [59]};// TLS - this.rules[12].opcodes[11] = {type: 7, string: [61]};// TLS + this.rules[12].opcodes[0] = {type: 3, min: 1, max: Infinity};// REP + this.rules[12].opcodes[1] = {type: 1, children: [2,3,4,5,6]};// ALT + this.rules[12].opcodes[2] = {type: 4, index: 13};// RNM(unreserved) + this.rules[12].opcodes[3] = {type: 4, index: 14};// RNM(pct-encoded) + this.rules[12].opcodes[4] = {type: 4, index: 16};// RNM(sub-delims-no-slash) + this.rules[12].opcodes[5] = {type: 7, string: [58]};// TLS + this.rules[12].opcodes[6] = {type: 7, string: [64]};// TLS - /* sub-delims-no-slash */ + /* unreserved */ this.rules[13].opcodes = []; - this.rules[13].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7,8,9,10]};// ALT - this.rules[13].opcodes[1] = {type: 7, string: [33]};// TLS - this.rules[13].opcodes[2] = {type: 7, string: [36]};// TLS - this.rules[13].opcodes[3] = {type: 7, string: [38]};// TLS - this.rules[13].opcodes[4] = {type: 7, string: [39]};// TLS - this.rules[13].opcodes[5] = {type: 7, string: [40]};// TLS - this.rules[13].opcodes[6] = {type: 7, string: [41]};// TLS - this.rules[13].opcodes[7] = {type: 7, string: [42]};// TLS - this.rules[13].opcodes[8] = {type: 7, string: [43]};// TLS - this.rules[13].opcodes[9] = {type: 7, string: [44]};// TLS - this.rules[13].opcodes[10] = {type: 7, string: [59]};// TLS + this.rules[13].opcodes[0] = {type: 1, children: [1,2,3,4,5,6]};// ALT + this.rules[13].opcodes[1] = {type: 4, index: 17};// RNM(ALPHA) + this.rules[13].opcodes[2] = {type: 4, index: 18};// RNM(DIGIT) + this.rules[13].opcodes[3] = {type: 7, string: [45]};// TLS + this.rules[13].opcodes[4] = {type: 7, string: [46]};// TLS + this.rules[13].opcodes[5] = {type: 7, string: [95]};// TLS + this.rules[13].opcodes[6] = {type: 7, string: [126]};// TLS - /* ALPHA */ + /* pct-encoded */ this.rules[14].opcodes = []; - this.rules[14].opcodes[0] = {type: 1, children: [1,2]};// ALT - this.rules[14].opcodes[1] = {type: 5, min: 65, max: 90};// TRG - this.rules[14].opcodes[2] = {type: 5, min: 97, max: 122};// TRG + this.rules[14].opcodes[0] = {type: 2, children: [1,2,3]};// CAT + this.rules[14].opcodes[1] = {type: 7, string: [37]};// TLS + this.rules[14].opcodes[2] = {type: 4, index: 19};// RNM(HEXDIG) + this.rules[14].opcodes[3] = {type: 4, index: 19};// RNM(HEXDIG) - /* DIGIT */ + /* sub-delims */ this.rules[15].opcodes = []; - this.rules[15].opcodes[0] = {type: 5, min: 48, max: 57};// TRG + this.rules[15].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7,8,9,10,11]};// ALT + this.rules[15].opcodes[1] = {type: 7, string: [33]};// TLS + this.rules[15].opcodes[2] = {type: 7, string: [36]};// TLS + this.rules[15].opcodes[3] = {type: 7, string: [38]};// TLS + this.rules[15].opcodes[4] = {type: 7, string: [39]};// TLS + this.rules[15].opcodes[5] = {type: 7, string: [40]};// TLS + this.rules[15].opcodes[6] = {type: 7, string: [41]};// TLS + this.rules[15].opcodes[7] = {type: 7, string: [42]};// TLS + this.rules[15].opcodes[8] = {type: 7, string: [43]};// TLS + this.rules[15].opcodes[9] = {type: 7, string: [44]};// TLS + this.rules[15].opcodes[10] = {type: 7, string: [59]};// TLS + this.rules[15].opcodes[11] = {type: 7, string: [61]};// TLS - /* HEXDIG */ + /* sub-delims-no-slash */ this.rules[16].opcodes = []; - this.rules[16].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7]};// ALT - this.rules[16].opcodes[1] = {type: 4, index: 15};// RNM(DIGIT) - this.rules[16].opcodes[2] = {type: 7, string: [97]};// TLS - this.rules[16].opcodes[3] = {type: 7, string: [98]};// TLS - this.rules[16].opcodes[4] = {type: 7, string: [99]};// TLS - this.rules[16].opcodes[5] = {type: 7, string: [100]};// TLS - this.rules[16].opcodes[6] = {type: 7, string: [101]};// TLS - this.rules[16].opcodes[7] = {type: 7, string: [102]};// TLS + this.rules[16].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7,8,9,10]};// ALT + this.rules[16].opcodes[1] = {type: 7, string: [33]};// TLS + this.rules[16].opcodes[2] = {type: 7, string: [36]};// TLS + this.rules[16].opcodes[3] = {type: 7, string: [38]};// TLS + this.rules[16].opcodes[4] = {type: 7, string: [39]};// TLS + this.rules[16].opcodes[5] = {type: 7, string: [40]};// TLS + this.rules[16].opcodes[6] = {type: 7, string: [41]};// TLS + this.rules[16].opcodes[7] = {type: 7, string: [42]};// TLS + this.rules[16].opcodes[8] = {type: 7, string: [43]};// TLS + this.rules[16].opcodes[9] = {type: 7, string: [44]};// TLS + this.rules[16].opcodes[10] = {type: 7, string: [59]};// TLS + + /* ALPHA */ + this.rules[17].opcodes = []; + this.rules[17].opcodes[0] = {type: 1, children: [1,2]};// ALT + this.rules[17].opcodes[1] = {type: 5, min: 65, max: 90};// TRG + this.rules[17].opcodes[2] = {type: 5, min: 97, max: 122};// TRG + + /* DIGIT */ + this.rules[18].opcodes = []; + this.rules[18].opcodes[0] = {type: 5, min: 48, max: 57};// TRG + + /* HEXDIG */ + this.rules[19].opcodes = []; + this.rules[19].opcodes[0] = {type: 1, children: [1,2,3,4,5,6,7]};// ALT + this.rules[19].opcodes[1] = {type: 4, index: 18};// RNM(DIGIT) + this.rules[19].opcodes[2] = {type: 7, string: [97]};// TLS + this.rules[19].opcodes[3] = {type: 7, string: [98]};// TLS + this.rules[19].opcodes[4] = {type: 7, string: [99]};// TLS + this.rules[19].opcodes[5] = {type: 7, string: [100]};// TLS + this.rules[19].opcodes[6] = {type: 7, string: [101]};// TLS + this.rules[19].opcodes[7] = {type: 7, string: [102]};// TLS // The `toString()` function will display the original grammar file(s) that produced these opcodes. this.toString = function toString(){ let str = ""; str += "; OpenAPI Path Templating ABNF syntax\n"; - str += "path-template = path [ query-marker query ]\n"; + str += "path-template = path [ query-marker query ] [ fragment-marker fragment ]\n"; str += "path = slash *( path-segment slash ) [ path-segment ]\n"; str += "path-segment = 1*( path-literal / template-expression )\n"; - str += "query = *( query-literal / template-expression )\n"; + str += "query = *( query-literal )\n"; str += "query-literal = 1*( unreserved / pct-encoded / sub-delims / \":\" / \"@\" / \"/\" / \"?\" / \"&\" / \"=\" )\n"; str += "query-marker = \"?\"\n"; + str += "fragment = *( fragment-literal )\n"; + str += "fragment-literal = 1*( unreserved / pct-encoded / sub-delims / \":\" / \"@\" / \"/\" / \"?\" )\n"; + str += "fragment-marker = \"#\"\n"; str += "slash = \"/\"\n"; str += "path-literal = 1*( unreserved / pct-encoded / sub-delims-no-slash / \":\" / \"@\" )\n"; str += "template-expression = \"{\" template-expression-param-name \"}\"\n"; diff --git a/test/parse.js b/test/parse.js index ec191c5..4bf272d 100644 --- a/test/parse.js +++ b/test/parse.js @@ -5,7 +5,7 @@ import { parse } from '../src/index.js'; describe('parse', function () { context('given valid source string', function () { context('/pets/{petId}', function () { - specify.only('should parse and translate', function () { + specify('should parse and translate', function () { const parseResult = parse('/pets/{petId}'); const parts = []; @@ -76,57 +76,62 @@ describe('parse', function () { ['path-literal', 'pets'], ['query-marker', '?'], ['query', 'offset=0&limit=10'], - ['query-literal', 'offset=0&limit=10'], ]); }); }); context('/pets?offset{offset}limit={limit}', function () { - specify('should parse and translate', function () { - const parseResult = parse('/pets?offset{offset}limit={limit}'); + specify('should not parse with template expressions in query', function () { + const parseResult = parse('/pets?limit={limit}'); + + assert.isFalse(parseResult.result.success); + }); + }); + + context('/pets?offset={offset}&limit={limit}', function () { + specify('should not parse with template expressions in query', function () { + const parseResult = parse('/pets?offset={offset}&limit={limit}'); + + assert.isFalse(parseResult.result.success); + }); + }); + + context('/pets#fragment', function () { + specify('should parse', function () { + const parseResult = parse('/pets#fragment'); const parts = []; parseResult.ast.translate(parts); assert.isTrue(parseResult.result.success); assert.deepEqual(parts, [ - ['path-template', '/pets?offset{offset}limit={limit}'], + ['path-template', '/pets#fragment'], ['path', '/pets'], ['slash', '/'], ['path-literal', 'pets'], - ['query-marker', '?'], - ['query', 'offset{offset}limit={limit}'], - ['query-literal', 'offset'], - ['template-expression', '{offset}'], - ['template-expression-param-name', 'offset'], - ['query-literal', 'limit='], - ['template-expression', '{limit}'], - ['template-expression-param-name', 'limit'], + ['fragment-marker', '#'], + ['fragment', 'fragment'], ]); }); }); - context('/pets?offset={offset}&limit={limit}', function () { - specify('should parse and translate', function () { - const parseResult = parse('/pets?offset={offset}&limit={limit}'); + context('/pets?offset=0#fragment', function () { + specify('should parse', function () { + const parseResult = parse('/pets?offset=0#fragment'); const parts = []; parseResult.ast.translate(parts); assert.isTrue(parseResult.result.success); assert.deepEqual(parts, [ - ['path-template', '/pets?offset={offset}&limit={limit}'], + ['path-template', '/pets?offset=0#fragment'], ['path', '/pets'], ['slash', '/'], ['path-literal', 'pets'], ['query-marker', '?'], - ['query', 'offset={offset}&limit={limit}'], - ['query-literal', 'offset='], - ['template-expression', '{offset}'], - ['template-expression-param-name', 'offset'], - ['query-literal', '&limit='], - ['template-expression', '{limit}'], - ['template-expression-param-name', 'limit'], + ['query', 'offset=0'], + ['fragment-marker', '#'], + ['fragment', 'fragment'], ]); }); }); diff --git a/test/resolve.js b/test/resolve.js index 4e8fbb1..73244f7 100644 --- a/test/resolve.js +++ b/test/resolve.js @@ -45,9 +45,15 @@ describe('resolve', function () { assert.equal(result, '/pets/1'); }); - it('should resolve path template with query string template expressions', function () { + it('should not resolve path template with query string template expressions', function () { const result = resolve('/pets?offset={offset}&limit={limit}', { offset: 0, limit: 10 }); - assert.equal(result, '/pets?offset=0&limit=10'); + assert.equal(result, '/pets?offset={offset}&limit={limit}'); + }); + + it('should not resolve path template with fragment template expressions', function () { + const result = resolve('/pets#{fragment}', { fragment: 'value' }); + + assert.equal(result, '/pets#{fragment}'); }); }); diff --git a/test/test.js b/test/test.js index 699823a..e1b38d3 100644 --- a/test/test.js +++ b/test/test.js @@ -10,10 +10,8 @@ describe('test', function () { assert.isTrue(test('/books/{id}')); assert.isTrue(test('/a{test}')); assert.isTrue(test('/{entity}/{another-entity}/me')); - assert.isTrue(test('/')); - assert.isTrue(test('/pets?offset={offset}&limit={limit}')); assert.isTrue(test('/pets?offset=0&limit=10')); - assert.isTrue(test('/pets?offset{offset}limit={limit}')); + assert.isTrue(test('/')); }); it('should not detect expression', function () { @@ -21,6 +19,8 @@ describe('test', function () { assert.isFalse(test('1')); assert.isFalse(test('{petId}')); assert.isFalse(test('/pet/{petId')); + assert.isFalse(test('/pets?offset={offset}&limit={limit}')); + assert.isFalse(test('/pets?offset{offset}limit={limit}')); assert.isFalse(test(1)); assert.isFalse(test(null)); assert.isFalse(test(undefined)); @@ -33,13 +33,13 @@ describe('test', function () { assert.isTrue(test('/{entity}/me', { strict: true })); assert.isTrue(test('/books/{id}', { strict: true })); assert.isTrue(test('/{entity}/{another-entity}/me')); - assert.isTrue(test('/pets/{petId}?offset={offset}&limit={limit}', { strict: true })); }); specify('should not detect expression', function () { assert.isFalse(test('/', { strict: true })); assert.isFalse(test('/pets/mine', { strict: true })); assert.isFalse(test('/pets?offset=0&limit=10', { strict: true })); + assert.isFalse(test('/pets/{petId}?offset={offset}&limit={limit}', { strict: true })); assert.isFalse(test('')); assert.isFalse(test('1')); assert.isFalse(test('{petId}'));