You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

281 lines
9.0 KiB

  1. // Copyright 2016 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package uritemplates
  5. import (
  6. "fmt"
  7. "log"
  8. "net/url"
  9. "testing"
  10. )
  11. func ExampleExpand() {
  12. values := map[string]string{
  13. "user": "golang",
  14. "repo": "go",
  15. }
  16. expanded, _, err := Expand("https://api.github.com/repos{/user,repo}", values)
  17. if err != nil {
  18. log.Fatalf("Error expanding template: %v", err)
  19. }
  20. fmt.Println(expanded)
  21. // Output:
  22. // https://api.github.com/repos/golang/go
  23. }
  24. func TestExpand(t *testing.T) {
  25. testCases := []struct {
  26. tmpl string
  27. values map[string]string
  28. want string
  29. }{
  30. // These examples come from the RFC:
  31. // http://tools.ietf.org/html/rfc6570
  32. {
  33. tmpl: "http://www.example.com/foo{?query,number}",
  34. values: map[string]string{"query": "mycelium", "number": "100"},
  35. want: "http://www.example.com/foo?query=mycelium&number=100",
  36. },
  37. {
  38. tmpl: "http://www.example.com/foo{?query,number}",
  39. values: map[string]string{"query": "mycelium"},
  40. want: "http://www.example.com/foo?query=mycelium",
  41. },
  42. {
  43. tmpl: "http://www.example.com/foo{?query,number}",
  44. values: map[string]string{},
  45. want: "http://www.example.com/foo",
  46. },
  47. }
  48. for _, tt := range testCases {
  49. exp, _, err := Expand(tt.tmpl, tt.values)
  50. if err != nil {
  51. t.Errorf("Expand(%q, %v) error: %v", tt.tmpl, tt.values, err)
  52. continue
  53. }
  54. if exp != tt.want {
  55. t.Errorf("Expand(%q, %v)\ngot %q\nwant %q", tt.tmpl, tt.values, exp, tt.want)
  56. }
  57. }
  58. }
  59. func TestExpandRFCLevels(t *testing.T) {
  60. values := map[string]string{
  61. "dub": "me/too",
  62. "hello": "Hello World!",
  63. "half": "50%",
  64. "var": "value",
  65. "who": "fred",
  66. "base": "http://example.com/home/",
  67. "path": "/foo/bar",
  68. "semi": ";",
  69. "v": "6",
  70. "x": "1024",
  71. "y": "768",
  72. "empty": "",
  73. // undef not mapped.
  74. }
  75. testCases := []struct {
  76. tmpl, want string
  77. }{
  78. // These examples come from the RFC levels specification.
  79. // http://tools.ietf.org/html/rfc6570
  80. // Level 1 examples.
  81. {tmpl: "{var}", want: "value"},
  82. {tmpl: "{hello}", want: "Hello%20World%21"},
  83. // Level 2 examples.
  84. {tmpl: "{+var}", want: "value"},
  85. {tmpl: "{+hello}", want: "Hello%20World!"},
  86. {tmpl: "{+path}/here", want: "/foo/bar/here"},
  87. {tmpl: "here?ref={+path}", want: "here?ref=/foo/bar"},
  88. {tmpl: "X{#var}", want: "X#value"},
  89. {tmpl: "X{#hello}", want: "X#Hello%20World!"},
  90. // Level 3 examples.
  91. {tmpl: "map?{x,y}", want: "map?1024,768"},
  92. {tmpl: "{x,hello,y}", want: "1024,Hello%20World%21,768"},
  93. {tmpl: "{+x,hello,y}", want: "1024,Hello%20World!,768"},
  94. {tmpl: "{+path,x}/here", want: "/foo/bar,1024/here"},
  95. {tmpl: "{#x,hello,y}", want: "#1024,Hello%20World!,768"},
  96. {tmpl: "{#path,x}/here", want: "#/foo/bar,1024/here"},
  97. {tmpl: "X{.var}", want: "X.value"},
  98. {tmpl: "X{.x,y}", want: "X.1024.768"},
  99. {tmpl: "{/var}", want: "/value"},
  100. {tmpl: "{/var,x}/here", want: "/value/1024/here"},
  101. {tmpl: "{;x,y}", want: ";x=1024;y=768"},
  102. {tmpl: "{;x,y,empty}", want: ";x=1024;y=768;empty"},
  103. {tmpl: "{?x,y}", want: "?x=1024&y=768"},
  104. {tmpl: "{?x,y,empty}", want: "?x=1024&y=768&empty="},
  105. {tmpl: "?fixed=yes{&x}", want: "?fixed=yes&x=1024"},
  106. {tmpl: "{&x,y,empty}", want: "&x=1024&y=768&empty="},
  107. {tmpl: "{var:3}", want: "val"},
  108. {tmpl: "{var:30}", want: "value"},
  109. {tmpl: "{+path:6}/here", want: "/foo/b/here"},
  110. {tmpl: "{#path:6}/here", want: "#/foo/b/here"},
  111. {tmpl: "X{.var:3}", want: "X.val"},
  112. {tmpl: "{/var:1,var}", want: "/v/value"},
  113. {tmpl: "{;hello:5}", want: ";hello=Hello"},
  114. {tmpl: "{?var:3}", want: "?var=val"},
  115. {tmpl: "{&var:3}", want: "&var=val"},
  116. // 2.4.1 Prefix values.
  117. {tmpl: "{var}", want: "value"},
  118. {tmpl: "{var:20}", want: "value"},
  119. {tmpl: "{var:3}", want: "val"},
  120. {tmpl: "{semi}", want: "%3B"},
  121. {tmpl: "{semi:2}", want: "%3B"},
  122. // 3.2.2. Simple String Expansion: {var}
  123. {tmpl: "{var}", want: "value"},
  124. {tmpl: "{hello}", want: "Hello%20World%21"},
  125. {tmpl: "{half}", want: "50%25"},
  126. {tmpl: "O{empty}X", want: "OX"},
  127. {tmpl: "O{undef}X", want: "OX"},
  128. {tmpl: "{x,y}", want: "1024,768"},
  129. {tmpl: "{x,hello,y}", want: "1024,Hello%20World%21,768"},
  130. {tmpl: "?{x,empty}", want: "?1024,"},
  131. {tmpl: "?{x,undef}", want: "?1024"},
  132. {tmpl: "?{undef,y}", want: "?768"},
  133. {tmpl: "{var:3}", want: "val"},
  134. {tmpl: "{var:30}", want: "value"},
  135. // 3.2.3. Reserved Expansion: {+var}
  136. {tmpl: "{+var}", want: "value"},
  137. {tmpl: "{+hello}", want: "Hello%20World!"},
  138. {tmpl: "{+half}", want: "50%25"},
  139. {tmpl: "{base}index", want: "http%3A%2F%2Fexample.com%2Fhome%2Findex"},
  140. {tmpl: "{+base}index", want: "http://example.com/home/index"},
  141. {tmpl: "O{+empty}X", want: "OX"},
  142. {tmpl: "O{+undef}X", want: "OX"},
  143. {tmpl: "{+path}/here", want: "/foo/bar/here"},
  144. {tmpl: "here?ref={+path}", want: "here?ref=/foo/bar"},
  145. {tmpl: "up{+path}{var}/here", want: "up/foo/barvalue/here"},
  146. {tmpl: "{+x,hello,y}", want: "1024,Hello%20World!,768"},
  147. {tmpl: "{+path,x}/here", want: "/foo/bar,1024/here"},
  148. {tmpl: "{+path:6}/here", want: "/foo/b/here"},
  149. // 3.2.4. Fragment Expansion: {#var}
  150. {tmpl: "{#var}", want: "#value"},
  151. {tmpl: "{#hello}", want: "#Hello%20World!"},
  152. {tmpl: "{#half}", want: "#50%25"},
  153. {tmpl: "foo{#empty}", want: "foo#"},
  154. {tmpl: "foo{#undef}", want: "foo"},
  155. {tmpl: "{#x,hello,y}", want: "#1024,Hello%20World!,768"},
  156. {tmpl: "{#path,x}/here", want: "#/foo/bar,1024/here"},
  157. {tmpl: "{#path:6}/here", want: "#/foo/b/here"},
  158. // 3.2.5. Label Expansion with Dot-Prefix: {.var}
  159. {tmpl: "{.who}", want: ".fred"},
  160. {tmpl: "{.who,who}", want: ".fred.fred"},
  161. {tmpl: "{.half,who}", want: ".50%25.fred"},
  162. {tmpl: "X{.var}", want: "X.value"},
  163. {tmpl: "X{.empty}", want: "X."},
  164. {tmpl: "X{.undef}", want: "X"},
  165. {tmpl: "X{.var:3}", want: "X.val"},
  166. // 3.2.6. Path Segment Expansion: {/var}
  167. {tmpl: "{/who}", want: "/fred"},
  168. {tmpl: "{/who,who}", want: "/fred/fred"},
  169. {tmpl: "{/half,who}", want: "/50%25/fred"},
  170. {tmpl: "{/who,dub}", want: "/fred/me%2Ftoo"},
  171. {tmpl: "{/var}", want: "/value"},
  172. {tmpl: "{/var,empty}", want: "/value/"},
  173. {tmpl: "{/var,undef}", want: "/value"},
  174. {tmpl: "{/var,x}/here", want: "/value/1024/here"},
  175. {tmpl: "{/var:1,var}", want: "/v/value"},
  176. // 3.2.7. Path-Style Parameter Expansion: {;var}
  177. {tmpl: "{;who}", want: ";who=fred"},
  178. {tmpl: "{;half}", want: ";half=50%25"},
  179. {tmpl: "{;empty}", want: ";empty"},
  180. {tmpl: "{;v,empty,who}", want: ";v=6;empty;who=fred"},
  181. {tmpl: "{;v,bar,who}", want: ";v=6;who=fred"},
  182. {tmpl: "{;x,y}", want: ";x=1024;y=768"},
  183. {tmpl: "{;x,y,empty}", want: ";x=1024;y=768;empty"},
  184. {tmpl: "{;x,y,undef}", want: ";x=1024;y=768"},
  185. {tmpl: "{;hello:5}", want: ";hello=Hello"},
  186. // 3.2.8. Form-Style Query Expansion: {?var}
  187. {tmpl: "{?who}", want: "?who=fred"},
  188. {tmpl: "{?half}", want: "?half=50%25"},
  189. {tmpl: "{?x,y}", want: "?x=1024&y=768"},
  190. {tmpl: "{?x,y,empty}", want: "?x=1024&y=768&empty="},
  191. {tmpl: "{?x,y,undef}", want: "?x=1024&y=768"},
  192. {tmpl: "{?var:3}", want: "?var=val"},
  193. // 3.2.9. Form-Style Query Continuation: {&var}
  194. {tmpl: "{&who}", want: "&who=fred"},
  195. {tmpl: "{&half}", want: "&half=50%25"},
  196. {tmpl: "?fixed=yes{&x}", want: "?fixed=yes&x=1024"},
  197. {tmpl: "{&x,y,empty}", want: "&x=1024&y=768&empty="},
  198. {tmpl: "{&x,y,undef}", want: "&x=1024&y=768"},
  199. {tmpl: "{&var:3}", want: "&var=val"},
  200. }
  201. for _, tt := range testCases {
  202. esc, unesc, err := Expand(tt.tmpl, values)
  203. if err != nil {
  204. t.Errorf("Expand(%q) error: %v", tt.tmpl, err)
  205. continue
  206. }
  207. if esc != tt.want {
  208. t.Errorf("Expand(%q)\ngot %q\nwant %q", tt.tmpl, esc, tt.want)
  209. }
  210. // Check that the escaped form is equivalent to unescaped.
  211. urlUnesc, err := url.QueryUnescape(esc)
  212. if err != nil {
  213. t.Errorf("Expand(%q) gave invalid escaping %q: %v", tt.tmpl, esc, err)
  214. continue
  215. }
  216. if urlUnesc != unesc {
  217. t.Errorf("Expand(%q) gave inconsistent escaped/unescaped\nunescaped %q\nescaped %q\nwhich is %q", tt.tmpl, unesc, esc, urlUnesc)
  218. }
  219. }
  220. }
  221. func TestExpandUnescaped(t *testing.T) {
  222. testCases := []struct {
  223. tmpl, wantEsc, wantUnesc string
  224. values map[string]string
  225. }{
  226. {
  227. tmpl: "/foo/{bucket}/bar",
  228. values: map[string]string{
  229. "bucket": "simple",
  230. },
  231. wantEsc: "/foo/simple/bar",
  232. wantUnesc: "/foo/simple/bar",
  233. },
  234. {
  235. tmpl: "/foo/{bucket}/bar",
  236. values: map[string]string{
  237. "bucket": "path/with/slash",
  238. },
  239. wantEsc: "/foo/path%2Fwith%2Fslash/bar",
  240. wantUnesc: "/foo/path/with/slash/bar",
  241. },
  242. {
  243. tmpl: "/foo/{+bucket}/bar",
  244. values: map[string]string{
  245. "bucket": "path/with/slash",
  246. },
  247. wantEsc: "/foo/path/with/slash/bar",
  248. wantUnesc: "/foo/path/with/slash/bar",
  249. },
  250. {
  251. tmpl: "/foo/{bucket}/bar",
  252. values: map[string]string{
  253. "bucket": "double%2Fescaped",
  254. },
  255. wantEsc: "/foo/double%252Fescaped/bar",
  256. wantUnesc: "/foo/double%2Fescaped/bar",
  257. },
  258. }
  259. for _, tt := range testCases {
  260. esc, unesc, err := Expand(tt.tmpl, tt.values)
  261. if err != nil {
  262. t.Errorf("Expand(%q) error: %v", tt.tmpl, err)
  263. continue
  264. }
  265. if esc != tt.wantEsc || unesc != tt.wantUnesc {
  266. t.Errorf("Expand(%q)\ngot esc=%q, unesc=%q\nwant esc=%q, unesc=%q", tt.tmpl, esc, unesc, tt.wantEsc, tt.wantUnesc)
  267. }
  268. }
  269. }