{-# OPTIONS_GHC -Wno-orphans #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE TypeFamilies #-} module Main ( main ) where import qualified Burrito import qualified Burrito.Type.LitChar as LitChar import qualified Burrito.Type.Expression as Expression import qualified Burrito.Type.Literal as Literal import qualified Burrito.Type.Modifier as Modifier import qualified Burrito.Type.Name as Name import qualified Burrito.Type.NonEmpty as NonEmpty import qualified Burrito.Type.Operator as Operator import qualified Burrito.Type.Template as Template import qualified Burrito.Type.Token as Token import qualified Burrito.Type.VarChar as VarChar import qualified Burrito.Type.Variable as Variable import qualified Data.Char as Char import qualified Data.Either as Either import qualified Data.Maybe as Maybe import qualified Data.String as String import qualified GHC.Exts as Exts import qualified GHC.Stack as Stack import qualified Test.Hspec as Test import qualified Test.QuickCheck as QC main :: IO () main = Test.hspec . Test.describe "Burrito" $ do Test.it "accepts empty templates" $ do test "" [] "" Test.it "ignores extra variables" $ do test "" ["extra" =: "ignored"] "" Test.it "accepts ascii literals" $ do test "!" [] "!" test "#" [] "#" test "$" [] "$" test "&" [] "&" test "(" [] "(" test ")" [] ")" test "*" [] "*" test "+" [] "+" test "," [] "," test "-" [] "-" test "." [] "." test "/" [] "/" test "0" [] "0" test "9" [] "9" test ":" [] ":" test ";" [] ";" test "=" [] "=" test "?" [] "?" test "@" [] "@" test "A" [] "A" test "Z" [] "Z" test "[" [] "[" test "]" [] "]" test "_" [] "_" test "a" [] "a" test "z" [] "z" test "~" [] "~" Test.it "accepts unicode literals" $ do test "\xa0" [] "%C2%A0" test "\xd7ff" [] "%ED%9F%BF" test "\xf900" [] "%EF%A4%80" test "\xfdcf" [] "%EF%B7%8F" test "\xfdf0" [] "%EF%B7%B0" test "\xffef" [] "%EF%BF%AF" test "\x10000" [] "%F0%90%80%80" test "\x1fffd" [] "%F0%9F%BF%BD" test "\x20000" [] "%F0%A0%80%80" test "\x2fffd" [] "%F0%AF%BF%BD" test "\x30000" [] "%F0%B0%80%80" test "\x3fffd" [] "%F0%BF%BF%BD" test "\x40000" [] "%F1%80%80%80" test "\x4fffd" [] "%F1%8F%BF%BD" test "\x50000" [] "%F1%90%80%80" test "\x5fffd" [] "%F1%9F%BF%BD" test "\x60000" [] "%F1%A0%80%80" test "\x6fffd" [] "%F1%AF%BF%BD" test "\x70000" [] "%F1%B0%80%80" test "\x7fffd" [] "%F1%BF%BF%BD" test "\x80000" [] "%F2%80%80%80" test "\x8fffd" [] "%F2%8F%BF%BD" test "\x90000" [] "%F2%90%80%80" test "\x9fffd" [] "%F2%9F%BF%BD" test "\xa0000" [] "%F2%A0%80%80" test "\xafffd" [] "%F2%AF%BF%BD" test "\xb0000" [] "%F2%B0%80%80" test "\xbfffd" [] "%F2%BF%BF%BD" test "\xc0000" [] "%F3%80%80%80" test "\xcfffd" [] "%F3%8F%BF%BD" test "\xd0000" [] "%F3%90%80%80" test "\xdfffd" [] "%F3%9F%BF%BD" test "\xe1000" [] "%F3%A1%80%80" test "\xefffd" [] "%F3%AF%BF%BD" Test.it "accepts private literals" $ do test "\xe000" [] "%EE%80%80" test "\xf8ff" [] "%EF%A3%BF" test "\xf0000" [] "%F3%B0%80%80" test "\xffffd" [] "%F3%BF%BF%BD" test "\x100000" [] "%F4%80%80%80" test "\x10fffd" [] "%F4%8F%BF%BD" Test.it "passes percent encoded literals through" $ do test "%00" [] "%00" Test.it "normalizes percent encodings to uppercase" $ do test "%AA" [] "%AA" test "%Aa" [] "%AA" test "%aA" [] "%AA" test "%aa" [] "%AA" Test.it "rejects invalid percent encodings" $ do test "%" [] Failure test "%0" [] Failure test "%0z" [] Failure test "%z" [] Failure Test.it "does not decode percent encoded literals" $ do test "%30" [] "%30" Test.it "rejects invalid literals" $ do test " " [] Failure test "\"" [] Failure test "'" [] Failure test "%" [] Failure test "<" [] Failure test ">" [] Failure test "\\" [] Failure test "^" [] Failure test "`" [] Failure test "{" [] Failure test "|" [] Failure test "}" [] Failure Test.it "rejects empty variable names" $ do test "{}" [] Failure test "{,}" [] Failure test "{a,,b}" [] Failure test "{+}" [] Failure test "{:1}" [] Failure test "{*}" [] Failure Test.it "accepts uppercase variable names" $ do test "{AZ}" [] "" Test.it "accepts lowercase variable names" $ do test "{az}" [] "" Test.it "accepts decimal variable names" $ do test "{09}" [] "" Test.it "accepts underscores in variable names" $ do test "{_a}" [] "" test "{a_}" [] "" test "{_}" [] "" Test.it "accepts dots in variable names" $ do test "{A.A}" [] "" test "{a.a}" [] "" test "{0.0}" [] "" test "{_._}" [] "" test "{%aa.%aa}" [] "" Test.it "rejects invalid dots in variable names" $ do test "{.}" [] Failure test "{a.}" [] Failure test "{+.a}" [] Failure test "{a..b}" [] Failure Test.it "accepts percent encoded variable names" $ do test "{%00}" [] "" -- It's unclear if percent encoded triplets in variable names should be case -- sensitive or not. Section 2.3 says: "Variable names are case-sensitive -- because the name might be expanded within a case-sensitive URI component." -- But the HEXDIG rule in section 1.5 says: "; case-insensitive". I think -- it's safe to assume they are case sensitive because they can appear in an -- expansion. For example: -- -- render "{;%aa}" [("%aa", "A")] ==> ";%aa=A" -- -- However percent encoded triplets that only differ by case would be decoded -- into the same octet anyway. Test.it "does not normalize percent encoded variable names" $ do test "{%AA}" ["%AA" =: "upper-upper"] "upper-upper" test "{%Aa}" ["%Aa" =: "upper-lower"] "upper-lower" test "{%aA}" ["%aA" =: "lower-upper"] "lower-upper" test "{%aa}" ["%aa" =: "lower-lower"] "lower-lower" Test.it "rejects invalid percent encoded variable names" $ do test "{%}" [] Failure test "{%0}" [] Failure test "{%0z}" [] Failure test "{%z}" [] Failure Test.it "rejects invalid variable names" $ do test "{!}" [] Failure Test.it "rejects invalid expressions" $ do test "{" [] Failure test "{{}" [] Failure test "}" [] Failure test "{}}" [] Failure Test.it "accepts multiple variables in one expression" $ do test "{a,b}" [] "" test "{a,b,c,d}" [] "" test "{a,a}" [] "" Test.it "accepts prefix modifiers" $ do test "{a:5}" [] "" test "{a:67}" [] "" test "{a:801}" [] "" test "{a:234}" [] "" test "{a:9999}" [] "" Test.it "applies prefix modifiers" $ do let values = ["a" =: "abcdefghijklmnopqrstuvwxyz"] :: Values test "{a:1}" values "a" test "{a:5}" values "abcde" test "{a:10}" values "abcdefghij" test "{a:15}" values "abcdefghijklmno" test "{a:20}" values "abcdefghijklmnopqrst" test "{a:25}" values "abcdefghijklmnopqrstuvwxy" test "{a:30}" values "abcdefghijklmnopqrstuvwxyz" Test.it "rejects invalid prefix modifiers" $ do test "{a:}" [] Failure test "{a:0}" [] Failure test "{a:10000}" [] Failure test "{a:-1}" [] Failure Test.it "accepts explode modifiers" $ do test "{a*}" [] "" Test.it "rejects both prefix and explode modifiers" $ do test "{a:1*}" [] Failure test "{a*:1}" [] Failure Test.it "accepts different modifiers on different variables" $ do test "{a,b:1,c*}" [] "" Test.it "accepts allowed operators" $ do test "{+a}" [] "" test "{#a}" [] "" test "{.a}" [] "" test "{/a}" [] "" test "{;a}" [] "" test "{?a}" [] "" test "{&a}" [] "" Test.it "rejects reserved operators" $ do test "{=a}" [] Failure test "{,a}" [] Failure test "{!a}" [] Failure test "{@a}" [] Failure test "{|a}" [] Failure Test.it "rejects multiple operators" $ do test "{+#a}" [] Failure Test.it "rejects different operators for different variables" $ do test "{+a,#b}" [] Failure Test.it "accepts operators and modifiers" $ do test "{+a:1}" [] "" test "{#a*}" [] "" Test.it "accepts multiple variables with an operator" $ do test "{+a,b}" [] "" test "{#a,b}" [] "" test "{.a,b}" [] "" test "{/a,b}" [] "" test "{;a,b}" [] "" test "{?a,b}" [] "" test "{&a,b}" [] "" Test.it "accepts multiple expressions" $ do test "{a}{b}" [] "" test "{a}{b}{c}{d}" [] "" test "{a}{a}" [] "" Test.it "rejects nested expressions" $ do test "{{}}" [] Failure test "{a{b}}" [] Failure test "{{a}b}" [] Failure test "{a{b}c}" [] Failure Test.it "accepts literals and expressions together" $ do test "a{b}" [] "a" test "{a}b" [] "b" test "a{b}c" [] "ac" test "{a}b{c}" [] "b" Test.it "handles missing values" $ do test "{a}" [] "" test "{+a}" [] "" test "{#a}" [] "" test "{.a}" [] "" test "{/a}" [] "" test "{;a}" [] "" test "{?a}" [] "" test "{&a}" [] "" Test.it "handles empty list values" $ do let values = ["a" =: emptyList] :: Values test "{a}" values "" test "{+a}" values "" test "{#a}" values "" test "{.a}" values "" test "{/a}" values "" test "{;a}" values "" test "{?a}" values "" test "{&a}" values "" Test.it "handles empty dictionary values" $ do let values = ["a" =: emptyDictionary] :: Values test "{a}" values "" test "{+a}" values "" test "{#a}" values "" test "{.a}" values "" test "{/a}" values "" test "{;a}" values "" test "{?a}" values "" test "{&a}" values "" Test.it "handles empty string values" $ do let values = ["a" =: ""] :: Values test "{a}" values "" test "{+a}" values "" test "{#a}" values "#" test "{.a}" values "." test "{/a}" values "/" test "{;a}" values ";a" test "{?a}" values "?a=" test "{&a}" values "&a=" Test.it "handles nonempty string values" $ do let values = ["a" =: "A"] :: Values test "{a}" values "A" test "{+a}" values "A" test "{#a}" values "#A" test "{.a}" values ".A" test "{/a}" values "/A" test "{;a}" values ";a=A" test "{?a}" values "?a=A" test "{&a}" values "&a=A" Test.it "handles a mix of defined and undefined values" $ do let values = ["b" =: "B"] :: Values test "{a,b}" values "B" test "{+a,b}" values "B" test "{#a,b}" values "#B" test "{.a,b}" values ".B" test "{/a,b}" values "/B" test "{;a,b}" values ";b=B" test "{?a,b}" values "?b=B" test "{&a,b}" values "&b=B" Test.it "handles multiple empty string values" $ do let values = ["a" =: ""] :: Values test "{a,a}" values "," test "{+a,a}" values "," test "{#a,a}" values "#," test "{.a,a}" values ".." test "{/a,a}" values "//" test "{;a,a}" values ";a;a" test "{?a,a}" values "?a=&a=" test "{&a,a}" values "&a=&a=" Test.it "handles multiple non-empty string values" $ do let values = ["a" =: "A", "b" =: "B"] :: Values test "{a,b}" values "A,B" test "{+a,b}" values "A,B" test "{#a,b}" values "#A,B" test "{.a,b}" values ".A.B" test "{/a,b}" values "/A/B" test "{;a,b}" values ";a=A;b=B" test "{?a,b}" values "?a=A&b=B" test "{&a,b}" values "&a=A&b=B" Test.it "escapes characters in composite dictionaries" $ do let values = ["a" =: ["K! \xa0\xd7ff\x10000" =: "V! \xa0\xd7ff\x10000"]] :: Values test "{a*}" values "K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{+a*}" values "K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{#a*}" values "#K!%20%C2%A0%ED%9F%BF%F0%90%80%80=V!%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{.a*}" values ".K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{/a*}" values "/K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{;a*}" values ";K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{?a*}" values "?K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" test "{&a*}" values "&K%21%20%C2%A0%ED%9F%BF%F0%90%80%80=V%21%20%C2%A0%ED%9F%BF%F0%90%80%80" Test.it "prefers the first variable" $ do test "{a}" ["a" =: "A", "a" =: "B"] "A" test "{a}" ["a" =: "B", "a" =: "A"] "B" Test.describe "passes test from rfc" $ do Test.it "section 1.1" $ do test "http://example.com/~{username}/" ["username" =: "fred"] "http://example.com/~fred/" test "http://example.com/~{username}/" ["username" =: "mark"] "http://example.com/~mark/" test "http://example.com/dictionary/{term:1}/{term}" ["term" =: "cat"] "http://example.com/dictionary/c/cat" test "http://example.com/dictionary/{term:1}/{term}" ["term" =: "dog"] "http://example.com/dictionary/d/dog" test "http://example.com/search{?q,lang}" ["q" =: "cat", "lang" =: "en"] "http://example.com/search?q=cat&lang=en" test "http://example.com/search{?q,lang}" ["q" =: "chien", "lang" =: "fr"] "http://example.com/search?q=chien&lang=fr" test "http://www.example.com/foo{?query,number}" ["query" =: "mycelium", "number" =: "100"] "http://www.example.com/foo?query=mycelium&number=100" test "http://www.example.com/foo{?query,number}" ["number" =: "100"] "http://www.example.com/foo?number=100" test "http://www.example.com/foo{?query,number}" [] "http://www.example.com/foo" Test.describe "section 1.2" $ do let values = [ "empty" =: "" , "hello" =: "Hello World!" , "keys" =: ["semi" =: ";", "dot" =: ".", "comma" =: ","] , "list" =: ["red", "green", "blue"] , "path" =: "/foo/bar" , "var" =: "value" , "x" =: "1024" , "y" =: "768" ] :: Values Test.it "level 1" $ do test "{var}" values "value" test "{hello}" values "Hello%20World%21" Test.it "level 2" $ do test "{+var}" values "value" test "{+hello}" values "Hello%20World!" test "{+path}/here" values "/foo/bar/here" test "here?ref={+path}" values "here?ref=/foo/bar" test "X{#var}" values "X#value" test "X{#hello}" values "X#Hello%20World!" Test.it "level 3" $ do test "map?{x,y}" values "map?1024,768" test "{x,hello,y}" values "1024,Hello%20World%21,768" test "{+x,hello,y}" values "1024,Hello%20World!,768" test "{+path,x}/here" values "/foo/bar,1024/here" test "{#x,hello,y}" values "#1024,Hello%20World!,768" test "{#path,x}/here" values "#/foo/bar,1024/here" test "X{.var}" values "X.value" test "X{.x,y}" values "X.1024.768" test "{/var}" values "/value" test "{/var,x}/here" values "/value/1024/here" test "{;x,y}" values ";x=1024;y=768" test "{;x,y,empty}" values ";x=1024;y=768;empty" test "{?x,y}" values "?x=1024&y=768" test "{?x,y,empty}" values "?x=1024&y=768&empty=" test "?fixed=yes{&x}" values "?fixed=yes&x=1024" test "{&x,y,empty}" values "&x=1024&y=768&empty=" Test.it "level 4" $ do test "{var:3}" values "val" test "{var:30}" values "value" test "{list}" values "red,green,blue" test "{list*}" values "red,green,blue" test "{keys}" values "semi,%3B,dot,.,comma,%2C" test "{keys*}" values "semi=%3B,dot=.,comma=%2C" test "{+path:6}/here" values "/foo/b/here" test "{+list}" values "red,green,blue" test "{+list*}" values "red,green,blue" test "{+keys}" values "semi,;,dot,.,comma,," test "{+keys*}" values "semi=;,dot=.,comma=," test "{#path:6}/here" values "#/foo/b/here" test "{#list}" values "#red,green,blue" test "{#list*}" values "#red,green,blue" test "{#keys}" values "#semi,;,dot,.,comma,," test "{#keys*}" values "#semi=;,dot=.,comma=," test "X{.var:3}" values "X.val" test "X{.list}" values "X.red,green,blue" test "X{.list*}" values "X.red.green.blue" test "X{.keys}" values "X.semi,%3B,dot,.,comma,%2C" test "X{.keys*}" values "X.semi=%3B.dot=..comma=%2C" test "{/var:1,var}" values "/v/value" test "{/list}" values "/red,green,blue" test "{/list*}" values "/red/green/blue" test "{/list*,path:4}" values "/red/green/blue/%2Ffoo" test "{/keys}" values "/semi,%3B,dot,.,comma,%2C" test "{/keys*}" values "/semi=%3B/dot=./comma=%2C" test "{;hello:5}" values ";hello=Hello" test "{;list}" values ";list=red,green,blue" test "{;list*}" values ";list=red;list=green;list=blue" test "{;keys}" values ";keys=semi,%3B,dot,.,comma,%2C" test "{;keys*}" values ";semi=%3B;dot=.;comma=%2C" test "{?var:3}" values "?var=val" test "{?list}" values "?list=red,green,blue" test "{?list*}" values "?list=red&list=green&list=blue" test "{?keys}" values "?keys=semi,%3B,dot,.,comma,%2C" test "{?keys*}" values "?semi=%3B&dot=.&comma=%2C" test "{&var:3}" values "&var=val" test "{&list}" values "&list=red,green,blue" test "{&list*}" values "&list=red&list=green&list=blue" test "{&keys}" values "&keys=semi,%3B,dot,.,comma,%2C" test "{&keys*}" values "&semi=%3B&dot=.&comma=%2C" Test.it "section 2.4.1" $ do let values = ["var" =: "value", "semi" =: ";"] :: Values test "{var}" values "value" test "{var:20}" values "value" test "{var:3}" values "val" test "{semi}" values "%3B" test "{semi:2}" values "%3B" Test.it "section 2.4.2" $ do let values = ["year" =: ["1965", "2000", "2012"], "dom" =: ["example", "com"]] :: Values test "find{?year*}" values "find?year=1965&year=2000&year=2012" test "www{.dom*}" values "www.example.com" Test.describe "section 3.1" $ do let values = [ "base" =: "http://example.com/home/" , "count" =: ["one", "two", "three"] , "dom" =: ["example", "com"] , "dub" =: "me/too" , "empty_keys" =: emptyDictionary , "empty" =: "" , "half" =: "50%" , "hello" =: "Hello World!" , "keys" =: ["semi" =: ";", "dot" =: ".", "comma" =: ","] , "list" =: ["red", "green", "blue"] , "path" =: "/foo/bar" , "v" =: "6" , "var" =: "value" , "who" =: "fred" , "x" =: "1024" , "y" =: "768" ] :: Values Test.it "subsection 1" $ do test "{count}" values "one,two,three" test "{count*}" values "one,two,three" test "{/count}" values "/one,two,three" test "{/count*}" values "/one/two/three" test "{;count}" values ";count=one,two,three" test "{;count*}" values ";count=one;count=two;count=three" test "{?count}" values "?count=one,two,three" test "{?count*}" values "?count=one&count=two&count=three" test "{&count*}" values "&count=one&count=two&count=three" Test.it "subsection 2" $ do test "{var}" values "value" test "{hello}" values "Hello%20World%21" test "{half}" values "50%25" test "O{empty}X" values "OX" test "O{undef}X" values "OX" test "{x,y}" values "1024,768" test "{x,hello,y}" values "1024,Hello%20World%21,768" test "?{x,empty}" values "?1024," test "?{x,undef}" values "?1024" test "?{undef,y}" values "?768" test "{var:3}" values "val" test "{var:30}" values "value" test "{list}" values "red,green,blue" test "{list*}" values "red,green,blue" test "{keys}" values "semi,%3B,dot,.,comma,%2C" test "{keys*}" values "semi=%3B,dot=.,comma=%2C" Test.it "subsection 3" $ do test "{+var}" values "value" test "{+hello}" values "Hello%20World!" test "{+half}" values "50%25" test "{base}index" values "http%3A%2F%2Fexample.com%2Fhome%2Findex" test "{+base}index" values "http://example.com/home/index" test "O{+empty}X" values "OX" test "O{+undef}X" values "OX" test "{+path}/here" values "/foo/bar/here" test "here?ref={+path}" values "here?ref=/foo/bar" test "up{+path}{var}/here" values "up/foo/barvalue/here" test "{+x,hello,y}" values "1024,Hello%20World!,768" test "{+path,x}/here" values "/foo/bar,1024/here" test "{+path:6}/here" values "/foo/b/here" test "{+list}" values "red,green,blue" test "{+list*}" values "red,green,blue" test "{+keys}" values "semi,;,dot,.,comma,," test "{+keys*}" values "semi=;,dot=.,comma=," Test.it "subsection 4" $ do test "{#var}" values "#value" test "{#hello}" values "#Hello%20World!" test "{#half}" values "#50%25" test "foo{#empty}" values "foo#" test "foo{#undef}" values "foo" test "{#x,hello,y}" values "#1024,Hello%20World!,768" test "{#path,x}/here" values "#/foo/bar,1024/here" test "{#path:6}/here" values "#/foo/b/here" test "{#list}" values "#red,green,blue" test "{#list*}" values "#red,green,blue" test "{#keys}" values "#semi,;,dot,.,comma,," test "{#keys*}" values "#semi=;,dot=.,comma=," Test.it "subsection 5" $ do test "{.who}" values ".fred" test "{.who,who}" values ".fred.fred" test "{.half,who}" values ".50%25.fred" test "www{.dom*}" values "www.example.com" test "X{.var}" values "X.value" test "X{.empty}" values "X." test "X{.undef}" values "X" test "X{.var:3}" values "X.val" test "X{.list}" values "X.red,green,blue" test "X{.list*}" values "X.red.green.blue" test "X{.keys}" values "X.semi,%3B,dot,.,comma,%2C" test "X{.keys*}" values "X.semi=%3B.dot=..comma=%2C" test "X{.empty_keys}" values "X" test "X{.empty_keys*}" values "X" Test.it "subsection 6" $ do test "{/who}" values "/fred" test "{/who,who}" values "/fred/fred" test "{/half,who}" values "/50%25/fred" test "{/who,dub}" values "/fred/me%2Ftoo" test "{/var}" values "/value" test "{/var,empty}" values "/value/" test "{/var,undef}" values "/value" test "{/var,x}/here" values "/value/1024/here" test "{/var:1,var}" values "/v/value" test "{/list}" values "/red,green,blue" test "{/list*}" values "/red/green/blue" test "{/list*,path:4}" values "/red/green/blue/%2Ffoo" test "{/keys}" values "/semi,%3B,dot,.,comma,%2C" test "{/keys*}" values "/semi=%3B/dot=./comma=%2C" Test.it "subsection 7" $ do test "{;who}" values ";who=fred" test "{;half}" values ";half=50%25" test "{;empty}" values ";empty" test "{;v,empty,who}" values ";v=6;empty;who=fred" test "{;v,bar,who}" values ";v=6;who=fred" test "{;x,y}" values ";x=1024;y=768" test "{;x,y,empty}" values ";x=1024;y=768;empty" test "{;x,y,undef}" values ";x=1024;y=768" test "{;hello:5}" values ";hello=Hello" test "{;list}" values ";list=red,green,blue" test "{;list*}" values ";list=red;list=green;list=blue" test "{;keys}" values ";keys=semi,%3B,dot,.,comma,%2C" test "{;keys*}" values ";semi=%3B;dot=.;comma=%2C" Test.it "subsection 8" $ do test "{?who}" values "?who=fred" test "{?half}" values "?half=50%25" test "{?x,y}" values "?x=1024&y=768" test "{?x,y,empty}" values "?x=1024&y=768&empty=" test "{?x,y,undef}" values "?x=1024&y=768" test "{?var:3}" values "?var=val" test "{?list}" values "?list=red,green,blue" test "{?list*}" values "?list=red&list=green&list=blue" test "{?keys}" values "?keys=semi,%3B,dot,.,comma,%2C" test "{?keys*}" values "?semi=%3B&dot=.&comma=%2C" Test.it "subsection 9" $ do test "{&who}" values "&who=fred" test "{&half}" values "&half=50%25" test "?fixed=yes{&x}" values "?fixed=yes&x=1024" test "{&x,y,empty}" values "&x=1024&y=768&empty=" test "{&x,y,undef}" values "&x=1024&y=768" test "{&var:3}" values "&var=val" test "{&list}" values "&list=red,green,blue" test "{&list*}" values "&list=red&list=green&list=blue" test "{&keys}" values "&keys=semi,%3B,dot,.,comma,%2C" test "{&keys*}" values "&semi=%3B&dot=.&comma=%2C" Test.it "handles simple expansion" $ do test "{a}" [] "" test "{a}" ["a" =: emptyList] "" test "{a}" ["a" =: emptyDictionary] "" test "{a}" ["a" =: ""] "" test "{a}" ["a" =: "A"] "A" test "{a}" ["a" =: "~"] "~" test "{a}" ["a" =: "%"] "%25" test "{a}" ["a" =: "?"] "%3F" test "{a}" ["a" =: "&"] "%26" test "{a}" ["a" =: "\xa0"] "%C2%A0" test "{a}" ["a" =: "\xd7ff"] "%ED%9F%BF" test "{a}" ["a" =: "\x10000"] "%F0%90%80%80" test "{a}" ["a" =: ["A"]] "A" test "{a}" ["a" =: ["A", "B"]] "A,B" test "{a}" ["a" =: ["%"]] "%25" test "{a}" ["a" =: ["\xa0"]] "%C2%A0" test "{a}" ["a" =: ["A" =: "1"]] "A,1" test "{a}" ["a" =: ["A" =: "1", "B" =: "2"]] "A,1,B,2" test "{a}" ["a" =: ["A" =: "%"]] "A,%25" test "{a}" ["a" =: ["A" =: "\xa0"]] "A,%C2%A0" test "{a}" ["a" =: ["%" =: "1"]] "%25,1" test "{a*}" [] "" test "{a*}" ["a" =: ""] "" test "{a*}" ["a" =: "A"] "A" test "{a*}" ["a" =: emptyList] "" test "{a*}" ["a" =: ["A"]] "A" test "{a*}" ["a" =: ["A", "B"]] "A,B" test "{a*}" ["a" =: emptyDictionary] "" test "{a*}" ["a" =: ["A" =: "1"]] "A=1" test "{a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "A=1,B=2" test "{a:1}" [] "" test "{a:1}" ["a" =: ""] "" test "{a:1}" ["a" =: "A"] "A" test "{a:1}" ["a" =: "AB"] "A" test "{a:1}" ["a" =: "%B"] "%25" test "{a:1}" ["a" =: "\xa0\&B"] "%C2%A0" test "{a:1}" ["a" =: "\xd7ff\&B"] "%ED%9F%BF" test "{a:1}" ["a" =: "\x10000\&B"] "%F0%90%80%80" test "{a:1}" ["a" =: emptyList] "" test "{a:1}" ["a" =: ["AB"]] "AB" test "{a:1}" ["a" =: ["AB", "CD"]] "AB,CD" test "{a:1}" ["a" =: emptyDictionary] "" test "{a:1}" ["a" =: ["AB" =: "12"]] "AB,12" test "{a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34" test "{a,a}" [] "" test "{a,a}" ["a" =: emptyList] "" test "{a,a}" ["a" =: emptyDictionary] "" test "{a,a}" ["a" =: ""] "," test "{a,b}" ["a" =: ""] "" test "{a,b}" ["b" =: ""] "" test "{%aa}" ["%aa" =: "A"] "A" test "{%aa}" ["%aa" =: ["A", "B"]] "A,B" test "{%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "A,1,B,2" test "{%aa*}" ["%aa" =: "A"] "A" test "{%aa*}" ["%aa" =: ["A", "B"]] "A,B" test "{%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "A=1,B=2" Test.it "handles reserved expansion" $ do test "{+a}" [] "" test "{+a}" ["a" =: emptyList] "" test "{+a}" ["a" =: emptyDictionary] "" test "{+a}" ["a" =: ""] "" test "{+a}" ["a" =: "A"] "A" test "{+a}" ["a" =: "~"] "~" test "{+a}" ["a" =: "%"] "%25" test "{+a}" ["a" =: "?"] "?" test "{+a}" ["a" =: "&"] "&" test "{+a}" ["a" =: "\xa0"] "%C2%A0" test "{+a}" ["a" =: "\xd7ff"] "%ED%9F%BF" test "{+a}" ["a" =: "\x10000"] "%F0%90%80%80" test "{+a}" ["a" =: ["A"]] "A" test "{+a}" ["a" =: ["A", "B"]] "A,B" test "{+a}" ["a" =: ["%"]] "%25" test "{+a}" ["a" =: ["\xa0"]] "%C2%A0" test "{+a}" ["a" =: ["A" =: "1"]] "A,1" test "{+a}" ["a" =: ["A" =: "1", "B" =: "2"]] "A,1,B,2" test "{+a}" ["a" =: ["A" =: "%"]] "A,%25" test "{+a}" ["a" =: ["A" =: "\xa0"]] "A,%C2%A0" test "{+a}" ["a" =: ["%" =: "1"]] "%25,1" test "{+a*}" [] "" test "{+a*}" ["a" =: ""] "" test "{+a*}" ["a" =: "A"] "A" test "{+a*}" ["a" =: emptyList] "" test "{+a*}" ["a" =: ["A"]] "A" test "{+a*}" ["a" =: ["A", "B"]] "A,B" test "{+a*}" ["a" =: emptyDictionary] "" test "{+a*}" ["a" =: ["A" =: "1"]] "A=1" test "{+a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "A=1,B=2" test "{+a:1}" [] "" test "{+a:1}" ["a" =: ""] "" test "{+a:1}" ["a" =: "A"] "A" test "{+a:1}" ["a" =: "AB"] "A" test "{+a:1}" ["a" =: "%B"] "%25" test "{+a:1}" ["a" =: "\xa0\&B"] "%C2%A0" test "{+a:1}" ["a" =: "\xd7ff\&B"] "%ED%9F%BF" test "{+a:1}" ["a" =: "\x10000\&B"] "%F0%90%80%80" test "{+a:1}" ["a" =: emptyList] "" test "{+a:1}" ["a" =: ["AB"]] "AB" test "{+a:1}" ["a" =: ["AB", "CD"]] "AB,CD" test "{+a:1}" ["a" =: emptyDictionary] "" test "{+a:1}" ["a" =: ["AB" =: "12"]] "AB,12" test "{+a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "AB,12,CD,34" test "{+a,a}" [] "" test "{+a,a}" ["a" =: emptyList] "" test "{+a,a}" ["a" =: emptyDictionary] "" test "{+a,a}" ["a" =: ""] "," test "{+a,b}" ["a" =: ""] "" test "{+a,b}" ["b" =: ""] "" test "{+%aa}" ["%aa" =: "A"] "A" test "{+%aa}" ["%aa" =: ["A", "B"]] "A,B" test "{+%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "A,1,B,2" test "{+%aa*}" ["%aa" =: "A"] "A" test "{+%aa*}" ["%aa" =: ["A", "B"]] "A,B" test "{+%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "A=1,B=2" Test.it "handles fragment expansion" $ do test "{#a}" [] "" test "{#a}" ["a" =: emptyList] "" test "{#a}" ["a" =: emptyDictionary] "" test "{#a}" ["a" =: ""] "#" test "{#a}" ["a" =: "A"] "#A" test "{#a}" ["a" =: "~"] "#~" test "{#a}" ["a" =: "%"] "#%25" test "{#a}" ["a" =: "?"] "#?" test "{#a}" ["a" =: "&"] "#&" test "{#a}" ["a" =: "\xa0"] "#%C2%A0" test "{#a}" ["a" =: "\xd7ff"] "#%ED%9F%BF" test "{#a}" ["a" =: "\x10000"] "#%F0%90%80%80" test "{#a}" ["a" =: ["A"]] "#A" test "{#a}" ["a" =: ["A", "B"]] "#A,B" test "{#a}" ["a" =: ["%"]] "#%25" test "{#a}" ["a" =: ["\xa0"]] "#%C2%A0" test "{#a}" ["a" =: ["A" =: "1"]] "#A,1" test "{#a}" ["a" =: ["A" =: "1", "B" =: "2"]] "#A,1,B,2" test "{#a}" ["a" =: ["A" =: "%"]] "#A,%25" test "{#a}" ["a" =: ["A" =: "\xa0"]] "#A,%C2%A0" test "{#a}" ["a" =: ["%" =: "1"]] "#%25,1" test "{#a*}" [] "" test "{#a*}" ["a" =: ""] "#" test "{#a*}" ["a" =: "A"] "#A" test "{#a*}" ["a" =: emptyList] "" test "{#a*}" ["a" =: ["A"]] "#A" test "{#a*}" ["a" =: ["A", "B"]] "#A,B" test "{#a*}" ["a" =: emptyDictionary] "" test "{#a*}" ["a" =: ["A" =: "1"]] "#A=1" test "{#a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "#A=1,B=2" test "{#a:1}" [] "" test "{#a:1}" ["a" =: ""] "#" test "{#a:1}" ["a" =: "A"] "#A" test "{#a:1}" ["a" =: "AB"] "#A" test "{#a:1}" ["a" =: "%B"] "#%25" test "{#a:1}" ["a" =: "\xa0\&B"] "#%C2%A0" test "{#a:1}" ["a" =: "\xd7ff\&B"] "#%ED%9F%BF" test "{#a:1}" ["a" =: "\x10000\&B"] "#%F0%90%80%80" test "{#a:1}" ["a" =: emptyList] "" test "{#a:1}" ["a" =: ["AB"]] "#AB" test "{#a:1}" ["a" =: ["AB", "CD"]] "#AB,CD" test "{#a:1}" ["a" =: emptyDictionary] "" test "{#a:1}" ["a" =: ["AB" =: "12"]] "#AB,12" test "{#a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "#AB,12,CD,34" test "{#a,a}" [] "" test "{#a,a}" ["a" =: emptyList] "" test "{#a,a}" ["a" =: emptyDictionary] "" test "{#a,a}" ["a" =: ""] "#," test "{#a,b}" ["a" =: ""] "#" test "{#a,b}" ["b" =: ""] "#" test "{#%aa}" ["%aa" =: "A"] "#A" test "{#%aa}" ["%aa" =: ["A", "B"]] "#A,B" test "{#%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "#A,1,B,2" test "{#%aa*}" ["%aa" =: "A"] "#A" test "{#%aa*}" ["%aa" =: ["A", "B"]] "#A,B" test "{#%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "#A=1,B=2" Test.it "handles Test.it expansion" $ do test "{.a}" [] "" test "{.a}" ["a" =: emptyList] "" test "{.a}" ["a" =: emptyDictionary] "" test "{.a}" ["a" =: ""] "." test "{.a}" ["a" =: "A"] ".A" test "{.a}" ["a" =: "~"] ".~" test "{.a}" ["a" =: "%"] ".%25" test "{.a}" ["a" =: "?"] ".%3F" test "{.a}" ["a" =: "&"] ".%26" test "{.a}" ["a" =: "\xa0"] ".%C2%A0" test "{.a}" ["a" =: "\xd7ff"] ".%ED%9F%BF" test "{.a}" ["a" =: "\x10000"] ".%F0%90%80%80" test "{.a}" ["a" =: ["A"]] ".A" test "{.a}" ["a" =: ["A", "B"]] ".A,B" test "{.a}" ["a" =: ["%"]] ".%25" test "{.a}" ["a" =: ["\xa0"]] ".%C2%A0" test "{.a}" ["a" =: ["A" =: "1"]] ".A,1" test "{.a}" ["a" =: ["A" =: "1", "B" =: "2"]] ".A,1,B,2" test "{.a}" ["a" =: ["A" =: "%"]] ".A,%25" test "{.a}" ["a" =: ["A" =: "\xa0"]] ".A,%C2%A0" test "{.a}" ["a" =: ["%" =: "1"]] ".%25,1" test "{.a*}" [] "" test "{.a*}" ["a" =: ""] "." test "{.a*}" ["a" =: "A"] ".A" test "{.a*}" ["a" =: emptyList] "" test "{.a*}" ["a" =: ["A"]] ".A" test "{.a*}" ["a" =: ["A", "B"]] ".A.B" test "{.a*}" ["a" =: emptyDictionary] "" test "{.a*}" ["a" =: ["A" =: "1"]] ".A=1" test "{.a*}" ["a" =: ["A" =: "1", "B" =: "2"]] ".A=1.B=2" test "{.a:1}" [] "" test "{.a:1}" ["a" =: ""] "." test "{.a:1}" ["a" =: "A"] ".A" test "{.a:1}" ["a" =: "AB"] ".A" test "{.a:1}" ["a" =: "%B"] ".%25" test "{.a:1}" ["a" =: "\xa0\&B"] ".%C2%A0" test "{.a:1}" ["a" =: "\xd7ff\&B"] ".%ED%9F%BF" test "{.a:1}" ["a" =: "\x10000\&B"] ".%F0%90%80%80" test "{.a:1}" ["a" =: emptyList] "" test "{.a:1}" ["a" =: ["AB"]] ".AB" test "{.a:1}" ["a" =: ["AB", "CD"]] ".AB,CD" test "{.a:1}" ["a" =: emptyDictionary] "" test "{.a:1}" ["a" =: ["AB" =: "12"]] ".AB,12" test "{.a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] ".AB,12,CD,34" test "{.a,a}" [] "" test "{.a,a}" ["a" =: emptyList] "" test "{.a,a}" ["a" =: emptyDictionary] "" test "{.a,a}" ["a" =: ""] ".." test "{.a,b}" ["a" =: ""] "." test "{.a,b}" ["b" =: ""] "." test "{.%aa}" ["%aa" =: "A"] ".A" test "{.%aa}" ["%aa" =: ["A", "B"]] ".A,B" test "{.%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] ".A,1,B,2" test "{.%aa*}" ["%aa" =: "A"] ".A" test "{.%aa*}" ["%aa" =: ["A", "B"]] ".A.B" test "{.%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] ".A=1.B=2" Test.it "handles segment expansion" $ do test "{/a}" [] "" test "{/a}" ["a" =: emptyList] "" test "{/a}" ["a" =: emptyDictionary] "" test "{/a}" ["a" =: ""] "/" test "{/a}" ["a" =: "A"] "/A" test "{/a}" ["a" =: "~"] "/~" test "{/a}" ["a" =: "%"] "/%25" test "{/a}" ["a" =: "?"] "/%3F" test "{/a}" ["a" =: "&"] "/%26" test "{/a}" ["a" =: "\xa0"] "/%C2%A0" test "{/a}" ["a" =: "\xd7ff"] "/%ED%9F%BF" test "{/a}" ["a" =: "\x10000"] "/%F0%90%80%80" test "{/a}" ["a" =: ["A"]] "/A" test "{/a}" ["a" =: ["A", "B"]] "/A,B" test "{/a}" ["a" =: ["%"]] "/%25" test "{/a}" ["a" =: ["\xa0"]] "/%C2%A0" test "{/a}" ["a" =: ["A" =: "1"]] "/A,1" test "{/a}" ["a" =: ["A" =: "1", "B" =: "2"]] "/A,1,B,2" test "{/a}" ["a" =: ["A" =: "%"]] "/A,%25" test "{/a}" ["a" =: ["A" =: "\xa0"]] "/A,%C2%A0" test "{/a}" ["a" =: ["%" =: "1"]] "/%25,1" test "{/a*}" [] "" test "{/a*}" ["a" =: ""] "/" test "{/a*}" ["a" =: "A"] "/A" test "{/a*}" ["a" =: emptyList] "" test "{/a*}" ["a" =: ["A"]] "/A" test "{/a*}" ["a" =: ["A", "B"]] "/A/B" test "{/a*}" ["a" =: emptyDictionary] "" test "{/a*}" ["a" =: ["A" =: "1"]] "/A=1" test "{/a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "/A=1/B=2" test "{/a:1}" [] "" test "{/a:1}" ["a" =: ""] "/" test "{/a:1}" ["a" =: "A"] "/A" test "{/a:1}" ["a" =: "AB"] "/A" test "{/a:1}" ["a" =: "%B"] "/%25" test "{/a:1}" ["a" =: "\xa0\&B"] "/%C2%A0" test "{/a:1}" ["a" =: "\xd7ff\&B"] "/%ED%9F%BF" test "{/a:1}" ["a" =: "\x10000\&B"] "/%F0%90%80%80" test "{/a:1}" ["a" =: emptyList] "" test "{/a:1}" ["a" =: ["AB"]] "/AB" test "{/a:1}" ["a" =: ["AB", "CD"]] "/AB,CD" test "{/a:1}" ["a" =: emptyDictionary] "" test "{/a:1}" ["a" =: ["AB" =: "12"]] "/AB,12" test "{/a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "/AB,12,CD,34" test "{/a,a}" [] "" test "{/a,a}" ["a" =: emptyList] "" test "{/a,a}" ["a" =: emptyDictionary] "" test "{/a,a}" ["a" =: ""] "//" test "{/a,b}" ["a" =: ""] "/" test "{/a,b}" ["b" =: ""] "/" test "{/%aa}" ["%aa" =: "A"] "/A" test "{/%aa}" ["%aa" =: ["A", "B"]] "/A,B" test "{/%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "/A,1,B,2" test "{/%aa*}" ["%aa" =: "A"] "/A" test "{/%aa*}" ["%aa" =: ["A", "B"]] "/A/B" test "{/%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "/A=1/B=2" Test.it "handles parameter expansion" $ do test "{;a}" [] "" test "{;a}" ["a" =: emptyList] "" test "{;a}" ["a" =: emptyDictionary] "" test "{;a}" ["a" =: ""] ";a" test "{;a}" ["a" =: "A"] ";a=A" test "{;a}" ["a" =: "~"] ";a=~" test "{;a}" ["a" =: "%"] ";a=%25" test "{;a}" ["a" =: "?"] ";a=%3F" test "{;a}" ["a" =: "&"] ";a=%26" test "{;a}" ["a" =: "\xa0"] ";a=%C2%A0" test "{;a}" ["a" =: "\xd7ff"] ";a=%ED%9F%BF" test "{;a}" ["a" =: "\x10000"] ";a=%F0%90%80%80" test "{;a}" ["a" =: ["A"]] ";a=A" test "{;a}" ["a" =: ["A", "B"]] ";a=A,B" test "{;a}" ["a" =: ["%"]] ";a=%25" test "{;a}" ["a" =: ["\xa0"]] ";a=%C2%A0" test "{;a}" ["a" =: ["A" =: "1"]] ";a=A,1" test "{;a}" ["a" =: ["A" =: "1", "B" =: "2"]] ";a=A,1,B,2" test "{;a}" ["a" =: ["A" =: "%"]] ";a=A,%25" test "{;a}" ["a" =: ["A" =: "\xa0"]] ";a=A,%C2%A0" test "{;a}" ["a" =: ["%" =: "1"]] ";a=%25,1" test "{;a*}" [] "" test "{;a*}" ["a" =: ""] ";a" test "{;a*}" ["a" =: "A"] ";a=A" test "{;a*}" ["a" =: emptyList] "" test "{;a*}" ["a" =: ["A"]] ";a=A" test "{;a*}" ["a" =: ["A", "B"]] ";a=A;a=B" test "{;a*}" ["a" =: emptyDictionary] "" test "{;a*}" ["a" =: ["A" =: "1"]] ";A=1" test "{;a*}" ["a" =: ["A" =: "1", "B" =: "2"]] ";A=1;B=2" test "{;a:1}" [] "" test "{;a:1}" ["a" =: ""] ";a" test "{;a:1}" ["a" =: "A"] ";a=A" test "{;a:1}" ["a" =: "AB"] ";a=A" test "{;a:1}" ["a" =: "%B"] ";a=%25" test "{;a:1}" ["a" =: "\xa0\&B"] ";a=%C2%A0" test "{;a:1}" ["a" =: "\xd7ff\&B"] ";a=%ED%9F%BF" test "{;a:1}" ["a" =: "\x10000\&B"] ";a=%F0%90%80%80" test "{;a:1}" ["a" =: emptyList] "" test "{;a:1}" ["a" =: ["AB"]] ";a=AB" test "{;a:1}" ["a" =: ["AB", "CD"]] ";a=AB,CD" test "{;a:1}" ["a" =: emptyDictionary] "" test "{;a:1}" ["a" =: ["AB" =: "12"]] ";a=AB,12" test "{;a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] ";a=AB,12,CD,34" test "{;a,a}" [] "" test "{;a,a}" ["a" =: emptyList] "" test "{;a,a}" ["a" =: emptyDictionary] "" test "{;a,a}" ["a" =: ""] ";a;a" test "{;a,b}" ["a" =: ""] ";a" test "{;a,b}" ["b" =: ""] ";b" test "{;%aa}" ["%aa" =: "A"] ";%aa=A" test "{;%aa}" ["%aa" =: ["A", "B"]] ";%aa=A,B" test "{;%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] ";%aa=A,1,B,2" test "{;%aa*}" ["%aa" =: "A"] ";%aa=A" test "{;%aa*}" ["%aa" =: ["A", "B"]] ";%aa=A;%aa=B" test "{;%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] ";A=1;B=2" Test.it "handles query expansion" $ do test "{?a}" [] "" test "{?a}" ["a" =: emptyList] "" test "{?a}" ["a" =: emptyDictionary] "" test "{?a}" ["a" =: ""] "?a=" test "{?a}" ["a" =: "A"] "?a=A" test "{?a}" ["a" =: "~"] "?a=~" test "{?a}" ["a" =: "%"] "?a=%25" test "{?a}" ["a" =: "?"] "?a=%3F" test "{?a}" ["a" =: "&"] "?a=%26" test "{?a}" ["a" =: "\xa0"] "?a=%C2%A0" test "{?a}" ["a" =: "\xd7ff"] "?a=%ED%9F%BF" test "{?a}" ["a" =: "\x10000"] "?a=%F0%90%80%80" test "{?a}" ["a" =: ["A"]] "?a=A" test "{?a}" ["a" =: ["A", "B"]] "?a=A,B" test "{?a}" ["a" =: ["%"]] "?a=%25" test "{?a}" ["a" =: ["\xa0"]] "?a=%C2%A0" test "{?a}" ["a" =: ["A" =: "1"]] "?a=A,1" test "{?a}" ["a" =: ["A" =: "1", "B" =: "2"]] "?a=A,1,B,2" test "{?a}" ["a" =: ["A" =: "%"]] "?a=A,%25" test "{?a}" ["a" =: ["A" =: "\xa0"]] "?a=A,%C2%A0" test "{?a}" ["a" =: ["%" =: "1"]] "?a=%25,1" test "{?a*}" [] "" test "{?a*}" ["a" =: ""] "?a=" test "{?a*}" ["a" =: "A"] "?a=A" test "{?a*}" ["a" =: emptyList] "" test "{?a*}" ["a" =: ["A"]] "?a=A" test "{?a*}" ["a" =: ["A", "B"]] "?a=A&a=B" test "{?a*}" ["a" =: emptyDictionary] "" test "{?a*}" ["a" =: ["A" =: "1"]] "?A=1" test "{?a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "?A=1&B=2" test "{?a:1}" [] "" test "{?a:1}" ["a" =: ""] "?a=" test "{?a:1}" ["a" =: "A"] "?a=A" test "{?a:1}" ["a" =: "AB"] "?a=A" test "{?a:1}" ["a" =: "%B"] "?a=%25" test "{?a:1}" ["a" =: "\xa0\&B"] "?a=%C2%A0" test "{?a:1}" ["a" =: "\xd7ff\&B"] "?a=%ED%9F%BF" test "{?a:1}" ["a" =: "\x10000\&B"] "?a=%F0%90%80%80" test "{?a:1}" ["a" =: emptyList] "" test "{?a:1}" ["a" =: ["AB"]] "?a=AB" test "{?a:1}" ["a" =: ["AB", "CD"]] "?a=AB,CD" test "{?a:1}" ["a" =: emptyDictionary] "" test "{?a:1}" ["a" =: ["AB" =: "12"]] "?a=AB,12" test "{?a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "?a=AB,12,CD,34" test "{?a,a}" [] "" test "{?a,a}" ["a" =: emptyList] "" test "{?a,a}" ["a" =: emptyDictionary] "" test "{?a,a}" ["a" =: ""] "?a=&a=" test "{?a,b}" ["a" =: ""] "?a=" test "{?a,b}" ["b" =: ""] "?b=" test "{?%aa}" ["%aa" =: "A"] "?%aa=A" test "{?%aa}" ["%aa" =: ["A", "B"]] "?%aa=A,B" test "{?%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "?%aa=A,1,B,2" test "{?%aa*}" ["%aa" =: "A"] "?%aa=A" test "{?%aa*}" ["%aa" =: ["A", "B"]] "?%aa=A&%aa=B" test "{?%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "?A=1&B=2" Test.it "handles continuation expansion" $ do test "{&a}" [] "" test "{&a}" ["a" =: emptyList] "" test "{&a}" ["a" =: emptyDictionary] "" test "{&a}" ["a" =: ""] "&a=" test "{&a}" ["a" =: "A"] "&a=A" test "{&a}" ["a" =: "~"] "&a=~" test "{&a}" ["a" =: "%"] "&a=%25" test "{&a}" ["a" =: "?"] "&a=%3F" test "{&a}" ["a" =: "&"] "&a=%26" test "{&a}" ["a" =: "\xa0"] "&a=%C2%A0" test "{&a}" ["a" =: "\xd7ff"] "&a=%ED%9F%BF" test "{&a}" ["a" =: "\x10000"] "&a=%F0%90%80%80" test "{&a}" ["a" =: ["A"]] "&a=A" test "{&a}" ["a" =: ["A", "B"]] "&a=A,B" test "{&a}" ["a" =: ["%"]] "&a=%25" test "{&a}" ["a" =: ["\xa0"]] "&a=%C2%A0" test "{&a}" ["a" =: ["A" =: "1"]] "&a=A,1" test "{&a}" ["a" =: ["A" =: "1", "B" =: "2"]] "&a=A,1,B,2" test "{&a}" ["a" =: ["A" =: "%"]] "&a=A,%25" test "{&a}" ["a" =: ["A" =: "\xa0"]] "&a=A,%C2%A0" test "{&a}" ["a" =: ["%" =: "1"]] "&a=%25,1" test "{&a*}" [] "" test "{&a*}" ["a" =: ""] "&a=" test "{&a*}" ["a" =: "A"] "&a=A" test "{&a*}" ["a" =: emptyList] "" test "{&a*}" ["a" =: ["A"]] "&a=A" test "{&a*}" ["a" =: ["A", "B"]] "&a=A&a=B" test "{&a*}" ["a" =: emptyDictionary] "" test "{&a*}" ["a" =: ["A" =: "1"]] "&A=1" test "{&a*}" ["a" =: ["A" =: "1", "B" =: "2"]] "&A=1&B=2" test "{&a:1}" [] "" test "{&a:1}" ["a" =: ""] "&a=" test "{&a:1}" ["a" =: "A"] "&a=A" test "{&a:1}" ["a" =: "AB"] "&a=A" test "{&a:1}" ["a" =: "%B"] "&a=%25" test "{&a:1}" ["a" =: "\xa0\&B"] "&a=%C2%A0" test "{&a:1}" ["a" =: "\xd7ff\&B"] "&a=%ED%9F%BF" test "{&a:1}" ["a" =: "\x10000\&B"] "&a=%F0%90%80%80" test "{&a:1}" ["a" =: emptyList] "" test "{&a:1}" ["a" =: ["AB"]] "&a=AB" test "{&a:1}" ["a" =: ["AB", "CD"]] "&a=AB,CD" test "{&a:1}" ["a" =: emptyDictionary] "" test "{&a:1}" ["a" =: ["AB" =: "12"]] "&a=AB,12" test "{&a:1}" ["a" =: ["AB" =: "12", "CD" =: "34"]] "&a=AB,12,CD,34" test "{&a,a}" [] "" test "{&a,a}" ["a" =: emptyList] "" test "{&a,a}" ["a" =: emptyDictionary] "" test "{&a,a}" ["a" =: ""] "&a=&a=" test "{&a,b}" ["a" =: ""] "&a=" test "{&a,b}" ["b" =: ""] "&b=" test "{&%aa}" ["%aa" =: "A"] "&%aa=A" test "{&%aa}" ["%aa" =: ["A", "B"]] "&%aa=A,B" test "{&%aa}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "&%aa=A,1,B,2" test "{&%aa*}" ["%aa" =: "A"] "&%aa=A" test "{&%aa*}" ["%aa" =: ["A", "B"]] "&%aa=A&%aa=B" test "{&%aa*}" ["%aa" =: ["A" =: "1", "B" =: "2"]] "&A=1&B=2" Test.describe "supports quasi quotes" $ do Test.it "as expressions" $ do let qq x y = Just x `Test.shouldBe` Burrito.parse y qq [Burrito.uriTemplate||] "" qq [Burrito.uriTemplate|a|] "a" qq [Burrito.uriTemplate|%00|] "%00" qq [Burrito.uriTemplate|{a}|] "{a}" qq [Burrito.uriTemplate|{a,b}|] "{a,b}" qq [Burrito.uriTemplate|{a}{b}|] "{a}{b}" qq [Burrito.uriTemplate|{a*}|] "{a*}" qq [Burrito.uriTemplate|{a:1}|] "{a:1}" qq [Burrito.uriTemplate|{+a}|] "{+a}" qq [Burrito.uriTemplate|{#a}|] "{#a}" qq [Burrito.uriTemplate|{.a}|] "{.a}" qq [Burrito.uriTemplate|{/a}|] "{/a}" qq [Burrito.uriTemplate|{;a}|] "{;a}" qq [Burrito.uriTemplate|{?a}|] "{?a}" qq [Burrito.uriTemplate|{&a}|] "{&a}" Test.it "round trips" . QC.property $ \template -> let rendered = Burrito.render $ unwrapTemplate template in fmap Burrito.render (Burrito.parse rendered) == Just rendered test :: Stack.HasCallStack => String -> Values -> Expected -> IO () test input values output = fmap (Burrito.expand (fmap (fmap unwrapValue) values)) (Burrito.parse input) `Test.shouldBe` expectedToMaybe output data Expected = Failure | Success String deriving (Eq, Show) instance String.IsString Expected where fromString = Success expectedToMaybe :: Expected -> Maybe String expectedToMaybe expected = case expected of Failure -> Nothing Success string -> Just string type Values = [(String, Value)] newtype Value = Value { unwrapValue :: Burrito.Value } deriving (Eq, Show) instance Exts.IsList Value where type Item Value = Item fromList items = Value $ case Either.partitionEithers $ fmap unwrapItem items of (strings, []) -> Burrito.listValue strings ([], tuples) -> Burrito.dictionaryValue tuples (_, _) -> error $ "fromList " <> show items <> " :: Value" toList value = error $ "toList " <> show value instance String.IsString Value where fromString = Value . Burrito.stringValue emptyList :: Value emptyList = Value $ Burrito.listValue [] emptyDictionary :: Value emptyDictionary = Value $ Burrito.dictionaryValue [] newtype Item = Item { unwrapItem :: Either String (String, String) } deriving (Eq, Show) instance String.IsString Item where fromString = Item . Left class Pair a where type K a type V a (=:) :: K a -> V a -> a instance Pair (a, b) where type K (a, b) = a type V (a, b) = b (=:) = (,) instance Pair Item where type K Item = String type V Item = String key =: value = Item $ Right (key, value) newtype Template = Template { unwrapTemplate :: Burrito.Template } deriving (Eq) instance Show Template where show (Template t) = show (Burrito.render t, t) instance QC.Arbitrary Template where arbitrary = Template <$> QC.arbitrary shrink = fmap Template . QC.shrink . unwrapTemplate instance QC.Arbitrary Template.Template where arbitrary = Template.Template <$> QC.arbitrary shrink = fmap Template.Template . QC.shrink . Template.tokens instance QC.Arbitrary Token.Token where arbitrary = QC.oneof [Token.Expression <$> QC.arbitrary, Token.Literal <$> QC.arbitrary] shrink token = case token of Token.Expression expression -> Token.Expression <$> QC.shrink expression Token.Literal literal -> Token.Literal <$> QC.shrink literal instance QC.Arbitrary Expression.Expression where arbitrary = Expression.Expression <$> QC.arbitrary <*> QC.arbitrary shrink expression = uncurry Expression.Expression <$> QC.shrink (Expression.operator expression, Expression.variables expression) instance QC.Arbitrary Operator.Operator where arbitrary = QC.elements [ Operator.Ampersand , Operator.FullStop , Operator.None , Operator.NumberSign , Operator.PlusSign , Operator.QuestionMark , Operator.Semicolon , Operator.Solidus ] shrink operator = case operator of Operator.None -> [] _ -> [Operator.None] instance (QC.Arbitrary a) => QC.Arbitrary (NonEmpty.NonEmpty a) where arbitrary = NonEmpty.NonEmpty <$> QC.arbitrary <*> QC.arbitrary shrink nonEmpty = uncurry NonEmpty.NonEmpty <$> QC.shrink (NonEmpty.first nonEmpty, NonEmpty.rest nonEmpty) instance QC.Arbitrary Variable.Variable where arbitrary = Variable.Variable <$> QC.arbitrary <*> QC.arbitrary shrink variable = uncurry Variable.Variable <$> QC.shrink (Variable.modifier variable, Variable.name variable) instance QC.Arbitrary Modifier.Modifier where arbitrary = QC.oneof [ pure Modifier.Asterisk , do maxLength <- QC.choose (1, 9999) maybe QC.discard pure $ Modifier.makeColon maxLength , pure Modifier.None ] shrink modifier = case modifier of Modifier.Asterisk -> [Modifier.None] Modifier.Colon int -> Maybe.mapMaybe Modifier.makeColon $ QC.shrink int Modifier.None -> [] instance QC.Arbitrary Name.Name where arbitrary = Name.Name <$> QC.arbitrary <*> QC.arbitrary shrink name = uncurry Name.Name <$> QC.shrink (Name.first name, Name.rest name) instance QC.Arbitrary VarChar.VarChar where arbitrary = QC.oneof [ do hi <- QC.suchThat QC.arbitrary Char.isHexDigit lo <- QC.suchThat QC.arbitrary Char.isHexDigit maybe QC.discard pure $ VarChar.makeEncoded hi lo , do char <- QC.suchThat QC.arbitrary VarChar.isVarchar maybe QC.discard pure $ VarChar.makeUnencoded char ] shrink varChar = case varChar of VarChar.Encoded hi lo -> uncurry VarChar.Encoded <$> QC.shrink (hi, lo) VarChar.Unencoded char -> Maybe.mapMaybe VarChar.makeUnencoded $ QC.shrink char instance QC.Arbitrary Literal.Literal where arbitrary = Literal.Literal <$> QC.arbitrary shrink = fmap Literal.Literal . QC.shrink . Literal.characters instance QC.Arbitrary LitChar.LitChar where arbitrary = QC.oneof [ LitChar.Encoded <$> QC.arbitrary , do char <- QC.suchThat QC.arbitrary LitChar.isLiteral maybe QC.discard pure $ LitChar.makeUnencoded char ] shrink character = case character of LitChar.Encoded word8 -> LitChar.Encoded <$> QC.shrink word8 LitChar.Unencoded char -> Maybe.mapMaybe LitChar.makeUnencoded $ QC.shrink char