diff --git a/README.md b/README.md index b2db818..7199544 100644 --- a/README.md +++ b/README.md @@ -256,27 +256,24 @@ The Server URL Templating is defined by the following [ABNF](https://tools.ietf. ```abnf ; OpenAPI Server URL templating ABNF syntax -; Aligned with RFC 6570 (https://www.rfc-editor.org/rfc/rfc6570) -server-url-template = 1*( literals / server-variable ) +server-url-template = 1*( literals / server-variable ) ; variant of https://www.rfc-editor.org/rfc/rfc6570#section-2 server-variable = "{" server-variable-name "}" -server-variable-name = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" ) -literals = 1*( %x21 / %x23-24 / %x26 / %x28-3B / %x3D / %x3F-5B +server-variable-name = 1*( %x00-7A / %x7C / %x7E-10FFFF ) ; every UTF8 character except { and } (from OpenAPI) + +; https://www.rfc-editor.org/rfc/rfc6570#section-2.1 +; https://www.rfc-editor.org/errata/eid6937 +literals = 1*( %x21 / %x23-24 / %x26-3B / %x3D / %x3F-5B / %x5D / %x5F / %x61-7A / %x7E / ucschar / iprivate / pct-encoded) - ; any Unicode character except: CTL, SP, - ; DQUOTE, "'", "%" (aside from pct-encoded), - ; "<", ">", "\", "^", "`", "{", "|", "}" + ; any Unicode character except: CTL, SP, + ; DQUOTE, "%" (aside from pct-encoded), + ; "<", ">", "\", "^", "`", "{", "|", "}" -; Characters definitions (from RFC 6570) -ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +; https://www.rfc-editor.org/rfc/rfc6570#section-1.5 DIGIT = %x30-39 ; 0-9 -HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - ; case-insensitive +HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" ; case-insensitive pct-encoded = "%" HEXDIG HEXDIG -unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - / "*" / "+" / "," / ";" / "=" ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD diff --git a/src/server-url-templating.bnf b/src/server-url-templating.bnf index e725151..989b865 100644 --- a/src/server-url-templating.bnf +++ b/src/server-url-templating.bnf @@ -1,25 +1,22 @@ ; OpenAPI Server URL templating ABNF syntax -; Aligned with RFC 6570 (https://www.rfc-editor.org/rfc/rfc6570) -server-url-template = 1*( literals / server-variable ) +server-url-template = 1*( literals / server-variable ) ; variant of https://www.rfc-editor.org/rfc/rfc6570#section-2 server-variable = "{" server-variable-name "}" -server-variable-name = 1*( unreserved / pct-encoded / sub-delims / ":" / "@" ) -literals = 1*( %x21 / %x23-24 / %x26 / %x28-3B / %x3D / %x3F-5B +server-variable-name = 1*( %x00-7A / %x7C / %x7E-10FFFF ) ; every UTF8 character except { and } (from OpenAPI) + +; https://www.rfc-editor.org/rfc/rfc6570#section-2.1 +; https://www.rfc-editor.org/errata/eid6937 +literals = 1*( %x21 / %x23-24 / %x26-3B / %x3D / %x3F-5B / %x5D / %x5F / %x61-7A / %x7E / ucschar / iprivate / pct-encoded) - ; any Unicode character except: CTL, SP, - ; DQUOTE, "'", "%" (aside from pct-encoded), - ; "<", ">", "\", "^", "`", "{", "|", "}" + ; any Unicode character except: CTL, SP, + ; DQUOTE, "%" (aside from pct-encoded), + ; "<", ">", "\", "^", "`", "{", "|", "}" -; Characters definitions (from RFC 6570) -ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +; https://www.rfc-editor.org/rfc/rfc6570#section-1.5 DIGIT = %x30-39 ; 0-9 -HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" - ; case-insensitive +HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" ; case-insensitive pct-encoded = "%" HEXDIG HEXDIG -unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - / "*" / "+" / "," / ";" / "=" ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD diff --git a/src/server-url-templating.js b/src/server-url-templating.js index e049dd8..d24965f 100644 --- a/src/server-url-templating.js +++ b/src/server-url-templating.js @@ -5,22 +5,22 @@ export default function grammar(){ // ``` // SUMMARY - // rules = 12 + // rules = 9 // udts = 0 - // opcodes = 87 + // opcodes = 62 // --- ABNF original opcodes - // ALT = 9 + // ALT = 6 // CAT = 2 // REP = 3 - // RNM = 14 - // TLS = 26 + // RNM = 9 + // TLS = 9 // TBS = 6 // TRG = 27 // --- SABNF superset opcodes // UDT = 0 // AND = 0 // NOT = 0 - // characters = [33 - 1114109] + // characters = [0 - 1114111] // ``` /* OBJECT IDENTIFIER (for internal parser use) */ this.grammarObject = 'grammarObject'; @@ -31,14 +31,11 @@ export default function grammar(){ this.rules[1] = { name: 'server-variable', lower: 'server-variable', index: 1, isBkr: false }; this.rules[2] = { name: 'server-variable-name', lower: 'server-variable-name', index: 2, isBkr: false }; this.rules[3] = { name: 'literals', lower: 'literals', index: 3, isBkr: false }; - this.rules[4] = { name: 'ALPHA', lower: 'alpha', index: 4, isBkr: false }; - this.rules[5] = { name: 'DIGIT', lower: 'digit', index: 5, isBkr: false }; - this.rules[6] = { name: 'HEXDIG', lower: 'hexdig', index: 6, isBkr: false }; - this.rules[7] = { name: 'pct-encoded', lower: 'pct-encoded', index: 7, isBkr: false }; - this.rules[8] = { name: 'unreserved', lower: 'unreserved', index: 8, isBkr: false }; - this.rules[9] = { name: 'sub-delims', lower: 'sub-delims', index: 9, isBkr: false }; - this.rules[10] = { name: 'ucschar', lower: 'ucschar', index: 10, isBkr: false }; - this.rules[11] = { name: 'iprivate', lower: 'iprivate', index: 11, isBkr: false }; + this.rules[4] = { name: 'DIGIT', lower: 'digit', index: 4, isBkr: false }; + this.rules[5] = { name: 'HEXDIG', lower: 'hexdig', index: 5, isBkr: false }; + this.rules[6] = { name: 'pct-encoded', lower: 'pct-encoded', index: 6, isBkr: false }; + this.rules[7] = { name: 'ucschar', lower: 'ucschar', index: 7, isBkr: false }; + this.rules[8] = { name: 'iprivate', lower: 'iprivate', index: 8, isBkr: false }; /* UDTS */ this.udts = []; @@ -61,137 +58,100 @@ export default function grammar(){ /* server-variable-name */ 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,4,5,6] };// ALT - this.rules[2].opcodes[2] = { type: 4, index: 8 };// RNM(unreserved) - this.rules[2].opcodes[3] = { type: 4, index: 7 };// RNM(pct-encoded) - this.rules[2].opcodes[4] = { type: 4, index: 9 };// RNM(sub-delims) - this.rules[2].opcodes[5] = { type: 7, string: [58] };// TLS - this.rules[2].opcodes[6] = { type: 7, string: [64] };// TLS + this.rules[2].opcodes[1] = { type: 1, children: [2,3,4] };// ALT + this.rules[2].opcodes[2] = { type: 5, min: 0, max: 122 };// TRG + this.rules[2].opcodes[3] = { type: 6, string: [124] };// TBS + this.rules[2].opcodes[4] = { type: 5, min: 126, max: 1114111 };// TRG /* literals */ this.rules[3].opcodes = []; this.rules[3].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[3].opcodes[1] = { type: 1, children: [2,3,4,5,6,7,8,9,10,11,12,13,14] };// ALT + this.rules[3].opcodes[1] = { type: 1, children: [2,3,4,5,6,7,8,9,10,11,12,13] };// ALT this.rules[3].opcodes[2] = { type: 6, string: [33] };// TBS this.rules[3].opcodes[3] = { type: 5, min: 35, max: 36 };// TRG - this.rules[3].opcodes[4] = { type: 6, string: [38] };// TBS - this.rules[3].opcodes[5] = { type: 5, min: 40, max: 59 };// TRG - this.rules[3].opcodes[6] = { type: 6, string: [61] };// TBS - this.rules[3].opcodes[7] = { type: 5, min: 63, max: 91 };// TRG - this.rules[3].opcodes[8] = { type: 6, string: [93] };// TBS - this.rules[3].opcodes[9] = { type: 6, string: [95] };// TBS - this.rules[3].opcodes[10] = { type: 5, min: 97, max: 122 };// TRG - this.rules[3].opcodes[11] = { type: 6, string: [126] };// TBS - this.rules[3].opcodes[12] = { type: 4, index: 10 };// RNM(ucschar) - this.rules[3].opcodes[13] = { type: 4, index: 11 };// RNM(iprivate) - this.rules[3].opcodes[14] = { type: 4, index: 7 };// RNM(pct-encoded) - - /* ALPHA */ - this.rules[4].opcodes = []; - this.rules[4].opcodes[0] = { type: 1, children: [1,2] };// ALT - this.rules[4].opcodes[1] = { type: 5, min: 65, max: 90 };// TRG - this.rules[4].opcodes[2] = { type: 5, min: 97, max: 122 };// TRG + this.rules[3].opcodes[4] = { type: 5, min: 38, max: 59 };// TRG + this.rules[3].opcodes[5] = { type: 6, string: [61] };// TBS + this.rules[3].opcodes[6] = { type: 5, min: 63, max: 91 };// TRG + this.rules[3].opcodes[7] = { type: 6, string: [93] };// TBS + this.rules[3].opcodes[8] = { type: 6, string: [95] };// TBS + this.rules[3].opcodes[9] = { type: 5, min: 97, max: 122 };// TRG + this.rules[3].opcodes[10] = { type: 6, string: [126] };// TBS + this.rules[3].opcodes[11] = { type: 4, index: 7 };// RNM(ucschar) + this.rules[3].opcodes[12] = { type: 4, index: 8 };// RNM(iprivate) + this.rules[3].opcodes[13] = { type: 4, index: 6 };// RNM(pct-encoded) /* DIGIT */ - this.rules[5].opcodes = []; - this.rules[5].opcodes[0] = { type: 5, min: 48, max: 57 };// TRG + this.rules[4].opcodes = []; + this.rules[4].opcodes[0] = { type: 5, min: 48, max: 57 };// TRG /* HEXDIG */ - this.rules[6].opcodes = []; - this.rules[6].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7] };// ALT - this.rules[6].opcodes[1] = { type: 4, index: 5 };// RNM(DIGIT) - this.rules[6].opcodes[2] = { type: 7, string: [97] };// TLS - this.rules[6].opcodes[3] = { type: 7, string: [98] };// TLS - this.rules[6].opcodes[4] = { type: 7, string: [99] };// TLS - this.rules[6].opcodes[5] = { type: 7, string: [100] };// TLS - this.rules[6].opcodes[6] = { type: 7, string: [101] };// TLS - this.rules[6].opcodes[7] = { type: 7, string: [102] };// TLS + this.rules[5].opcodes = []; + this.rules[5].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7] };// ALT + this.rules[5].opcodes[1] = { type: 4, index: 4 };// RNM(DIGIT) + this.rules[5].opcodes[2] = { type: 7, string: [97] };// TLS + this.rules[5].opcodes[3] = { type: 7, string: [98] };// TLS + this.rules[5].opcodes[4] = { type: 7, string: [99] };// TLS + this.rules[5].opcodes[5] = { type: 7, string: [100] };// TLS + this.rules[5].opcodes[6] = { type: 7, string: [101] };// TLS + this.rules[5].opcodes[7] = { type: 7, string: [102] };// TLS /* pct-encoded */ - this.rules[7].opcodes = []; - this.rules[7].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[7].opcodes[1] = { type: 7, string: [37] };// TLS - this.rules[7].opcodes[2] = { type: 4, index: 6 };// RNM(HEXDIG) - this.rules[7].opcodes[3] = { type: 4, index: 6 };// RNM(HEXDIG) - - /* unreserved */ - this.rules[8].opcodes = []; - this.rules[8].opcodes[0] = { type: 1, children: [1,2,3,4,5,6] };// ALT - this.rules[8].opcodes[1] = { type: 4, index: 4 };// RNM(ALPHA) - this.rules[8].opcodes[2] = { type: 4, index: 5 };// RNM(DIGIT) - this.rules[8].opcodes[3] = { type: 7, string: [45] };// TLS - this.rules[8].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[8].opcodes[5] = { type: 7, string: [95] };// TLS - this.rules[8].opcodes[6] = { type: 7, string: [126] };// TLS - - /* sub-delims */ - this.rules[9].opcodes = []; - this.rules[9].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7,8,9,10,11] };// ALT - this.rules[9].opcodes[1] = { type: 7, string: [33] };// TLS - this.rules[9].opcodes[2] = { type: 7, string: [36] };// TLS - this.rules[9].opcodes[3] = { type: 7, string: [38] };// TLS - this.rules[9].opcodes[4] = { type: 7, string: [39] };// TLS - this.rules[9].opcodes[5] = { type: 7, string: [40] };// TLS - this.rules[9].opcodes[6] = { type: 7, string: [41] };// TLS - this.rules[9].opcodes[7] = { type: 7, string: [42] };// TLS - this.rules[9].opcodes[8] = { type: 7, string: [43] };// TLS - this.rules[9].opcodes[9] = { type: 7, string: [44] };// TLS - this.rules[9].opcodes[10] = { type: 7, string: [59] };// TLS - this.rules[9].opcodes[11] = { type: 7, string: [61] };// TLS + this.rules[6].opcodes = []; + this.rules[6].opcodes[0] = { type: 2, children: [1,2,3] };// CAT + this.rules[6].opcodes[1] = { type: 7, string: [37] };// TLS + this.rules[6].opcodes[2] = { type: 4, index: 5 };// RNM(HEXDIG) + this.rules[6].opcodes[3] = { type: 4, index: 5 };// RNM(HEXDIG) /* ucschar */ - this.rules[10].opcodes = []; - this.rules[10].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] };// ALT - this.rules[10].opcodes[1] = { type: 5, min: 160, max: 55295 };// TRG - this.rules[10].opcodes[2] = { type: 5, min: 63744, max: 64975 };// TRG - this.rules[10].opcodes[3] = { type: 5, min: 65008, max: 65519 };// TRG - this.rules[10].opcodes[4] = { type: 5, min: 65536, max: 131069 };// TRG - this.rules[10].opcodes[5] = { type: 5, min: 131072, max: 196605 };// TRG - this.rules[10].opcodes[6] = { type: 5, min: 196608, max: 262141 };// TRG - this.rules[10].opcodes[7] = { type: 5, min: 262144, max: 327677 };// TRG - this.rules[10].opcodes[8] = { type: 5, min: 327680, max: 393213 };// TRG - this.rules[10].opcodes[9] = { type: 5, min: 393216, max: 458749 };// TRG - this.rules[10].opcodes[10] = { type: 5, min: 458752, max: 524285 };// TRG - this.rules[10].opcodes[11] = { type: 5, min: 524288, max: 589821 };// TRG - this.rules[10].opcodes[12] = { type: 5, min: 589824, max: 655357 };// TRG - this.rules[10].opcodes[13] = { type: 5, min: 655360, max: 720893 };// TRG - this.rules[10].opcodes[14] = { type: 5, min: 720896, max: 786429 };// TRG - this.rules[10].opcodes[15] = { type: 5, min: 786432, max: 851965 };// TRG - this.rules[10].opcodes[16] = { type: 5, min: 851968, max: 917501 };// TRG - this.rules[10].opcodes[17] = { type: 5, min: 921600, max: 983037 };// TRG + this.rules[7].opcodes = []; + this.rules[7].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] };// ALT + this.rules[7].opcodes[1] = { type: 5, min: 160, max: 55295 };// TRG + this.rules[7].opcodes[2] = { type: 5, min: 63744, max: 64975 };// TRG + this.rules[7].opcodes[3] = { type: 5, min: 65008, max: 65519 };// TRG + this.rules[7].opcodes[4] = { type: 5, min: 65536, max: 131069 };// TRG + this.rules[7].opcodes[5] = { type: 5, min: 131072, max: 196605 };// TRG + this.rules[7].opcodes[6] = { type: 5, min: 196608, max: 262141 };// TRG + this.rules[7].opcodes[7] = { type: 5, min: 262144, max: 327677 };// TRG + this.rules[7].opcodes[8] = { type: 5, min: 327680, max: 393213 };// TRG + this.rules[7].opcodes[9] = { type: 5, min: 393216, max: 458749 };// TRG + this.rules[7].opcodes[10] = { type: 5, min: 458752, max: 524285 };// TRG + this.rules[7].opcodes[11] = { type: 5, min: 524288, max: 589821 };// TRG + this.rules[7].opcodes[12] = { type: 5, min: 589824, max: 655357 };// TRG + this.rules[7].opcodes[13] = { type: 5, min: 655360, max: 720893 };// TRG + this.rules[7].opcodes[14] = { type: 5, min: 720896, max: 786429 };// TRG + this.rules[7].opcodes[15] = { type: 5, min: 786432, max: 851965 };// TRG + this.rules[7].opcodes[16] = { type: 5, min: 851968, max: 917501 };// TRG + this.rules[7].opcodes[17] = { type: 5, min: 921600, max: 983037 };// TRG /* iprivate */ - this.rules[11].opcodes = []; - this.rules[11].opcodes[0] = { type: 1, children: [1,2,3] };// ALT - this.rules[11].opcodes[1] = { type: 5, min: 57344, max: 63743 };// TRG - this.rules[11].opcodes[2] = { type: 5, min: 983040, max: 1048573 };// TRG - this.rules[11].opcodes[3] = { type: 5, min: 1048576, max: 1114109 };// TRG + this.rules[8].opcodes = []; + this.rules[8].opcodes[0] = { type: 1, children: [1,2,3] };// ALT + this.rules[8].opcodes[1] = { type: 5, min: 57344, max: 63743 };// TRG + this.rules[8].opcodes[2] = { type: 5, min: 983040, max: 1048573 };// TRG + this.rules[8].opcodes[3] = { type: 5, min: 1048576, max: 1114109 };// TRG // The `toString()` function will display the original grammar file(s) that produced these opcodes. this.toString = function toString(){ let str = ""; str += "; OpenAPI Server URL templating ABNF syntax\n"; - str += "; Aligned with RFC 6570 (https://www.rfc-editor.org/rfc/rfc6570)\n"; - str += "server-url-template = 1*( literals / server-variable )\n"; + str += "server-url-template = 1*( literals / server-variable ) ; variant of https://www.rfc-editor.org/rfc/rfc6570#section-2\n"; str += "server-variable = \"{\" server-variable-name \"}\"\n"; - str += "server-variable-name = 1*( unreserved / pct-encoded / sub-delims / \":\" / \"@\" )\n"; - str += "literals = 1*( %x21 / %x23-24 / %x26 / %x28-3B / %x3D / %x3F-5B\n"; + str += "server-variable-name = 1*( %x00-7A / %x7C / %x7E-10FFFF ) ; every UTF8 character except { and } (from OpenAPI)\n"; + str += "\n"; + str += "; https://www.rfc-editor.org/rfc/rfc6570#section-2.1\n"; + str += "; https://www.rfc-editor.org/errata/eid6937\n"; + str += "literals = 1*( %x21 / %x23-24 / %x26-3B / %x3D / %x3F-5B\n"; str += " / %x5D / %x5F / %x61-7A / %x7E / ucschar / iprivate\n"; str += " / pct-encoded)\n"; - str += " ; any Unicode character except: CTL, SP,\n"; - str += " ; DQUOTE, \"'\", \"%\" (aside from pct-encoded),\n"; - str += " ; \"<\", \">\", \"\\\", \"^\", \"`\", \"{\", \"|\", \"}\"\n"; + str += " ; any Unicode character except: CTL, SP,\n"; + str += " ; DQUOTE, \"%\" (aside from pct-encoded),\n"; + str += " ; \"<\", \">\", \"\\\", \"^\", \"`\", \"{\", \"|\", \"}\"\n"; str += "\n"; - str += "; Characters definitions (from RFC 6570)\n"; - str += "ALPHA = %x41-5A / %x61-7A ; A-Z / a-z\n"; + str += "; https://www.rfc-editor.org/rfc/rfc6570#section-1.5\n"; str += "DIGIT = %x30-39 ; 0-9\n"; - str += "HEXDIG = DIGIT / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\"\n"; - str += " ; case-insensitive\n"; + str += "HEXDIG = DIGIT / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\" ; case-insensitive\n"; str += "\n"; str += "pct-encoded = \"%\" HEXDIG HEXDIG\n"; - str += "unreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\n"; - str += "sub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\"\n"; - str += " / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n"; str += "\n"; str += "ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF\n"; str += " / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD\n"; diff --git a/test/test.js b/test/test.js index 6016d1b..fdad001 100644 --- a/test/test.js +++ b/test/test.js @@ -7,17 +7,61 @@ describe('test', function () { assert.isTrue(test('https://{username}.gigantic-server.com:{port}/{basePath}')); assert.isTrue(test('http://{username}.gigantic-server.com:{port}/{basePath}')); assert.isTrue(test('/{path}')); + assert.isTrue(test('/{path}/')); // trailing slash is allowed assert.isTrue(test('/pets/{petId}')); assert.isTrue(test('/{entity}/me')); assert.isTrue(test('/books/{id}')); assert.isTrue(test('/a{test}')); + assert.isTrue(test('/{test}a')); + assert.isTrue(test('/foo/bar/{baz}/test/{foo_id}/baz/{bar_id}')); + assert.isTrue(test('/range({x},{y})')); // parentheses are allowed + assert.isTrue(test('/range({x},{y})/secondRange({x},{y})')); // repeated parameter names are allowed assert.isTrue(test('/{entity}/{another-entity}/me')); + // some special characters in literals are allowed + assert.isTrue(test('/')); + assert.isTrue(test('/-/')); + assert.isTrue(test('/~/')); + assert.isTrue(test('/%20')); + assert.isTrue(test('/functions/t_Dist_2T')); + assert.isTrue(test('/❤️')); + assert.isTrue(test('/users/$count')); + assert.isTrue(test('/users/delta()')); + assert.isTrue(test('/directoryObjects/microsoft.graph.user')); + assert.isTrue(test("/applications(appId='{appId}')")); + assert.isTrue(test("/com/on/get(meetingOrganizerUserId='@meetingOrganizerUserId')")); + // some special characters in server variable names are allowed + assert.isTrue(test('/users/{user-id}')); + assert.isTrue(test('/{❤️}')); + assert.isTrue(test('/{%}')); + assert.isTrue(test('/{foo:}')); + assert.isTrue(test('/{foo:baz}')); + assert.isTrue(test('/{=baz}')); + assert.isTrue(test('/{$baz}')); + assert.isTrue(test('/{~baz}')); + assert.isTrue(test('/{#baz}')); + assert.isTrue(test('/{?baz}')); + assert.isTrue(test('/{/baz}')); + assert.isTrue(test('/{foo baz}')); + assert.isTrue(test('/{|baz}')); + assert.isTrue(test('/{^baz}')); + assert.isTrue(test('/{`baz}')); + // RFC 6570 operators are allowed + assert.isTrue(test('/{y,x}')); + assert.isTrue(test('/{count*}')); + assert.isTrue(test('/{;bar}')); + assert.isTrue(test('/{&bar}')); + assert.isTrue(test('/{.bar}')); + // relative reference, query and fragment are allowed assert.isTrue(test('/pets?offset=0&limit=10')); assert.isTrue(test('/')); assert.isTrue(test('1')); assert.isTrue(test('{petId}')); assert.isTrue(test('/pets?offset={offset}&limit={limit}')); assert.isTrue(test('/pets?offset{offset}limit={limit}')); + assert.isTrue(test('/pets?offset#{fragment}')); + // different protocols + assert.isTrue(test('ftp://{username}.gigantic-server.com:{port}/{basePath}')); + assert.isTrue(test('postgresql://sally:sallyspassword@dbserver.example:5555/userdata')); }); context('given server URL template is a valid absolute URL without variables', function () { @@ -26,6 +70,7 @@ describe('test', function () { assert.isTrue(test('http://gigantic-server.com:8000/basePath')); assert.isTrue(test('https://gigantic-server.com/basePath')); assert.isTrue(test('http://gigantic-server.com/basePath')); + assert.isTrue(test('http://gigantic-server.com/basePath/')); }); }); @@ -41,6 +86,7 @@ describe('test', function () { specify('should detect as server URL template', function () { assert.isTrue(test('basePath')); assert.isTrue(test('/basePath')); + assert.isTrue(test('/basePath/')); assert.isTrue(test('/basePath?query=1')); assert.isTrue(test('/basePath#fragment')); assert.isTrue(test('/basePath?query=1#fragment')); @@ -58,9 +104,11 @@ describe('test', function () { ); it('should not detect expression', function () { - assert.isTrue(test('ftp://{username}.gigantic-server.com:{port}/{basePath}')); assert.isFalse(test('/pet/{petId')); assert.isFalse(test('')); + assert.isFalse(test('//')); + assert.isFalse(test('/pet/{petId')); + // invalid types assert.isFalse(test(1)); assert.isFalse(test(null)); assert.isFalse(test(undefined)); @@ -75,25 +123,89 @@ describe('test', function () { test('http://{username}.gigantic-server.com:{port}/{basePath}', { strict: true }), ); assert.isTrue(test('/{path}', { strict: true })); + assert.isTrue(test('/{path}/', { strict: true })); // trailing slash is allowed assert.isTrue(test('/pets/{petId}', { strict: true })); assert.isTrue(test('/{entity}/me', { strict: true })); assert.isTrue(test('/books/{id}', { strict: true })); + assert.isTrue(test('/a{test}', { strict: true })); + assert.isTrue(test('/{test}a', { strict: true })); + assert.isTrue(test('/foo/bar/{baz}/test/{foo_id}/baz/{bar_id}', { strict: true })); + assert.isTrue(test('/range({x},{y})', { strict: true })); // parentheses are allowed + assert.isTrue(test('/range({x},{y})/secondRange({x},{y})', { strict: true })); // repeated parameter names are allowed assert.isTrue(test('/{entity}/{another-entity}/me', { strict: true })); - assert.isTrue(test('/pets/{petId}?offset={offset}&limit={limit}', { strict: true })); - assert.isTrue(test('1'), { strict: true }); - assert.isTrue(test('{petId}'), { strict: true }); + // some special characters in literals are allowed + assert.isTrue(test("/applications(appId='{appId}')"), { strict: true }); + // some special characters in server variable names are allowed + assert.isTrue(test('/users/{user-id}', { strict: true })); + assert.isTrue(test('/{❤️}', { strict: true })); + assert.isTrue(test('/{%}', { strict: true })); + assert.isTrue(test('/{foo:}', { strict: true })); + assert.isTrue(test('/{foo:baz}', { strict: true })); + assert.isTrue(test('/{=baz}', { strict: true })); + assert.isTrue(test('/{$baz}', { strict: true })); + assert.isTrue(test('/{~baz}', { strict: true })); + assert.isTrue(test('/{#baz}', { strict: true })); + assert.isTrue(test('/{?baz}', { strict: true })); + assert.isTrue(test('/{/baz}', { strict: true })); + assert.isTrue(test('/{foo baz}', { strict: true })); + assert.isTrue(test('/{|baz}', { strict: true })); + assert.isTrue(test('/{^baz}', { strict: true })); + assert.isTrue(test('/{`baz}', { strict: true })); + // RFC 6570 operators are allowed + assert.isTrue(test('/{y,x}', { strict: true })); + assert.isTrue(test('/{count*}', { strict: true })); + assert.isTrue(test('/{;bar}', { strict: true })); + assert.isTrue(test('/{&bar}', { strict: true })); + assert.isTrue(test('/{.bar}', { strict: true })); + // relative reference, query and fragment are allowed + assert.isTrue(test('{petId}', { strict: true })); + assert.isTrue(test('/pets?offset={offset}&limit={limit}', { strict: true })); + assert.isTrue(test('/pets?offset{offset}limit={limit}', { strict: true })); + assert.isTrue(test('/pets?offset#{fragment}', { strict: true })); + // different protocols + assert.isTrue( + test('ftp://{username}.gigantic-server.com:{port}/{basePath}', { strict: true }), + ); + assert.isTrue( + test('postgresql://sally:sallyspassword@dbserver.example:{port}/userdata', { + strict: true, + }), + ); }); specify('should not detect server URL template', function () { + // some special characters in literals are allowed + assert.isTrue(test('/')); + assert.isTrue(test('/-/')); + assert.isTrue(test('/~/')); + assert.isTrue(test('/%20')); + assert.isTrue(test('/functions/t_Dist_2T')); + assert.isTrue(test('/❤️')); + assert.isTrue(test('/users/$count')); + assert.isTrue(test('/users/delta()')); + assert.isTrue(test('/directoryObjects/microsoft.graph.user')); + assert.isTrue(test("/com/on/get(meetingOrganizerUserId='@meetingOrganizerUserId')")); + // relative reference, query and fragment are allowed + assert.isTrue(test('/pets?offset=0&limit=10')); + assert.isTrue(test('/')); + assert.isTrue(test('1')); + // different protocols + assert.isFalse( + test('postgresql://sally:sallyspassword@dbserver.example:5555/userdata', { + strict: true, + }), + ); + assert.isFalse(test('https://gigantic-server.com:8000/basePath', { strict: true })); 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('')); - assert.isFalse(test('/pet/{petId')); - assert.isFalse(test(1)); - assert.isFalse(test(null)); - assert.isFalse(test(undefined)); + assert.isFalse(test('/pets#fragment', { strict: true })); + assert.isFalse(test('', { strict: true })); + assert.isFalse(test('/pet/{petId', { strict: true })); + assert.isFalse(test(1, { strict: true })); + assert.isFalse(test(null, { strict: true })); + assert.isFalse(test(undefined, { strict: true })); }); }); });