wireform-yaml-0.1.0.0: test/Test/YAML/Decode.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE OverloadedStrings #-}
module Test.YAML.Decode (tests) where
import Data.Aeson qualified as Aeson
import Data.ByteString qualified as BS
import Data.Either (isLeft)
import Data.Text qualified as T
import Data.Text.Array qualified as TA
import Data.Text.Encoding qualified as TE
import Data.Text.Internal qualified as TI
import Data.Vector qualified as V
import GHC.Exts (Int (..), sizeofByteArray#)
import Test.Syd
import Test.YAML.Helpers (assertRenderRoundtrip)
import YAML.Decode qualified as D
import YAML.Encode qualified as E
import YAML.JSON qualified as J
import YAML.Pretty qualified as P
import YAML.Value
tests :: Spec
tests =
describe "decode" $
sequence_
[ scalarTests
, collectionTests
, flowTests
, stringTests
, blockScalarTests
, anchorTests
, streamTests
, validationTests
]
scalarTests :: Spec
scalarTests =
describe "scalars" $
sequence_
[ it "null" $ D.decode "null" `shouldBe` Right YNull
, it "tilde" $ D.decode "~" `shouldBe` Right YNull
, it "true" $ D.decode "true" `shouldBe` Right (YBool True)
, it "false" $ D.decode "false" `shouldBe` Right (YBool False)
, it "int" $ D.decode "42" `shouldBe` Right (YInt 42)
, it "neg" $ D.decode "-17" `shouldBe` Right (YInt (-17))
, it "hex" $ D.decode "0x2A" `shouldBe` Right (YInt 42)
, it "oct" $ D.decode "0o52" `shouldBe` Right (YInt 42)
, it "float" $ case D.decode "3.14" of
Right (YFloat d) -> (abs (d - 3.14) < 1e-9) `shouldBe` True
r -> expectationFailure (show r)
, it ".inf" $ case D.decode ".inf" of
Right (YFloat d) -> (isInfinite d && d > 0) `shouldBe` True
r -> expectationFailure (show r)
, it "-.inf" $ case D.decode "-.inf" of
Right (YFloat d) -> (isInfinite d && d < 0) `shouldBe` True
r -> expectationFailure (show r)
, it ".nan" $ case D.decode ".nan" of
Right (YFloat d) -> (isNaN d) `shouldBe` True
r -> expectationFailure (show r)
, it "unquoted string" $ D.decode "hello" `shouldBe` Right (YString "hello")
]
collectionTests :: Spec
collectionTests =
describe "block style" $
sequence_
[ it "block mapping" $ do
let src =
T.unlines
[ "name: alice"
, "age: 30"
]
case D.decode src of
Right v -> do
lookupKey "name" v `shouldBe` Just (YString "alice")
lookupKey "age" v `shouldBe` Just (YInt 30)
Left e -> expectationFailure e
, it "block sequence" $ do
let src =
T.unlines
[ "- 1"
, "- 2"
, "- 3"
]
case D.decode src of
Right (YSeq xs) -> V.toList xs `shouldBe` [YInt 1, YInt 2, YInt 3]
r -> expectationFailure (show r)
, it "nested mapping" $ do
let src =
T.unlines
[ "server:"
, " host: localhost"
, " port: 8080"
]
case D.decode src of
Right v ->
case lookupKey "server" v of
Just inner -> do
lookupKey "host" inner `shouldBe` Just (YString "localhost")
lookupKey "port" inner `shouldBe` Just (YInt 8080)
_ -> expectationFailure "no server"
Left e -> expectationFailure e
, it "sequence of mappings" $ do
let src =
T.unlines
[ "- name: a"
, " v: 1"
, "- name: b"
, " v: 2"
]
case D.decode src of
Right (YSeq xs) -> do
V.length xs `shouldBe` 2
let m0 = xs V.! 0
lookupKey "name" m0 `shouldBe` Just (YString "a")
lookupKey "v" m0 `shouldBe` Just (YInt 1)
r -> expectationFailure (show r)
, it "comment + blank lines" $ do
let src =
T.unlines
[ "# header"
, ""
, "foo: bar # trailing"
]
case D.decode src of
Right v -> lookupKey "foo" v `shouldBe` Just (YString "bar")
Left e -> expectationFailure e
]
flowTests :: Spec
flowTests =
describe "flow style" $
sequence_
[ it "flow seq" $
D.decode "[1, 2, 3]"
`shouldBe` Right (YSeq (V.fromList [YInt 1, YInt 2, YInt 3]))
, it "flow map" $ case D.decode "{a: 1, b: 2}" of
Right (YMap kvs) -> do
V.length kvs `shouldBe` 2
r -> expectationFailure (show r)
, it "nested flow" $
case D.decode "[[1, 2], [3, 4]]" of
Right (YSeq xs) -> V.length xs `shouldBe` 2
r -> expectationFailure (show r)
]
stringTests :: Spec
stringTests =
describe "strings" $
sequence_
[ it "double quoted" $
D.decode "\"hello world\"" `shouldBe` Right (YString "hello world")
, it "single quoted" $
D.decode "'it''s ok'" `shouldBe` Right (YString "it's ok")
, it "escape \\n" $
D.decode "\"a\\nb\"" `shouldBe` Right (YString "a\nb")
, it "escape \\u" $
D.decode "\"\\u00E9\"" `shouldBe` Right (YString "\233")
, it "quoted in mapping" $ do
case D.decode "msg: \"hello\\tworld\"" of
Right v -> lookupKey "msg" v `shouldBe` Just (YString "hello\tworld")
Left e -> expectationFailure e
]
blockScalarTests :: Spec
blockScalarTests =
describe "block scalars" $
sequence_
[ it "literal" $ do
let src =
T.unlines
[ "txt: |"
, " one"
, " two"
]
case D.decode src of
Right v -> lookupKey "txt" v `shouldBe` Just (YString "one\ntwo\n")
Left e -> expectationFailure e
, it "literal strip" $ do
let src =
T.unlines
[ "txt: |-"
, " one"
, " two"
]
case D.decode src of
Right v -> lookupKey "txt" v `shouldBe` Just (YString "one\ntwo")
Left e -> expectationFailure e
, it "folded" $ do
let src =
T.unlines
[ "txt: >"
, " one"
, " two"
]
case D.decode src of
Right v -> lookupKey "txt" v `shouldBe` Just (YString "one two\n")
Left e -> expectationFailure e
]
anchorTests :: Spec
anchorTests =
describe "anchors" $
sequence_
[ it "anchor + alias scalar" $ do
let src =
T.unlines
[ "a: &x 1"
, "b: *x"
]
case D.decode src of
Right v -> do
-- Aliases come back wrapped in 'YAnchored' so the
-- size-of walk can identify shared subtrees by name;
-- 'unwrap' strips the wrapper for value comparison.
fmap unwrap (lookupKey "a" v) `shouldBe` Just (YInt 1)
fmap unwrap (lookupKey "b" v) `shouldBe` Just (YInt 1)
Left e -> expectationFailure e
]
streamTests :: Spec
streamTests =
describe "stream" $
sequence_
[ it "single doc, no markers" $
case D.decodeStream "k: 1" of
Right (Stream xs) -> V.length xs `shouldBe` 1
Left e -> expectationFailure e
, it "two docs" $ do
let src =
T.unlines
[ "---"
, "a: 1"
, "..."
, "---"
, "b: 2"
]
case D.decodeStream src of
Right (Stream xs) -> V.length xs `shouldBe` 2
Left e -> expectationFailure e
]
validationTests :: Spec
validationTests = describe "validation" $ do
it "decodes escapes after Unicode characters and Unicode flow siblings" $
mapM_
(\(src, expected) -> D.decode src `shouldBe` Right expected)
[ ("\"é\\n\"", YString "é\n")
, ("[é, \"😀\\t\\u0061\"]", seq_ [YString "é", YString "😀\ta"])
, ("\"é\\x41\\U0001F600\"", YString "éA😀")
]
it "rejects invalid Unicode scalar escapes through Either" $
mapM_
(\src -> D.decode src `shouldSatisfy` isLeft)
["\"\\U00110000\"", "\"\\UFFFFFFFF\"", "\"\\uD800\"", "[\"\\uDFFF\"]"]
it "does not mistake quoted strings for aliases" $ do
D.decode "[\"\\0alias\\0missing\"]" `shouldBe` Right (seq_ [YString "\0alias\0missing"])
case D.decode "[&a secret, \"\\0alias\\0a\"]" of
Right (YSeq xs) -> xs V.! 1 `shouldBe` YString "\0alias\0a"
other -> expectationFailure (show other)
it "ignores stream end markers that do not terminate a document" $ do
D.decodeStream "# comment\n...\n" `shouldBe` Right (Stream V.empty)
fmap (V.map docBody . unStream) (D.decodeStream "a\n...\n...\nb\n")
`shouldBe` Right (V.fromList [YString "a", YString "b"])
it "keeps document-start comments out of scalar content" $
D.decode "--- # comment\nscalar\n" `shouldBe` Right (YString "scalar")
it "resolves overridden secondary tag handles in block and flow nodes" $ do
fmap J.yamlToJSON (D.decode "%TAG !! tag:example.com,2000:app/\n---\n!!int 1 - 3\n")
`shouldBe` Right (Aeson.String "1 - 3")
fmap J.yamlToJSON (D.decode "%TAG !! tag:example.com,2000:app/\n---\n[!!int 1 - 3]\n")
`shouldBe` Right (Aeson.Array (V.singleton (Aeson.String "1 - 3")))
it "aliases inherit a tag applied before their anchor" $
mapM_
(\src -> fmap J.yamlToJSON (D.decode src) `shouldBe` Right (Aeson.Array (V.fromList [Aeson.Bool False, Aeson.Bool False])))
["[!!bool &a false, *a]", "- !!bool &a false\n- *a\n"]
it "implements core-schema numeric spellings" $
mapM_
(\(src, expected) -> D.decode src `shouldBe` Right expected)
[("01", YInt 1), (".5", YFloat 0.5), ("1.", YFloat 1), ("-.5", YFloat (-0.5)), ("1.e2", YFloat 100), ("0XFF", YString "0XFF"), ("+0xFF", YString "+0xFF")]
it "rejects malformed UTF-8 in both byte entry points" $ do
D.decodeBS (BS.pack [0xff]) `shouldSatisfy` isLeft
D.decodeStreamBS (BS.pack [0xc0, 0x80]) `shouldSatisfy` isLeft
it "accepts CR and CRLF line endings" $
mapM_
(\src -> D.decode src `shouldBe` Right (mapping [(YString "a", YInt 1), (YString "b", YInt 2)]))
["a: 1\rb: 2\r", "a: 1\r\nb: 2\r\n"]
it "keeps earlier alias bindings when a source name is reused" $
fmap J.yamlToJSON (D.decode "[&a [1], &b [*a, &a 2], *a, *b]")
`shouldBe` Right (Aeson.Array (V.fromList [Aeson.Array (V.singleton (Aeson.Number 1)), Aeson.Array (V.fromList [Aeson.Array (V.singleton (Aeson.Number 1)), Aeson.Number 2]), Aeson.Number 2, Aeson.Array (V.fromList [Aeson.Array (V.singleton (Aeson.Number 1)), Aeson.Number 2])]))
it "decodes all YAML escapes in quoted mapping keys" $
D.decode "\"\\u0061\\x62\\U0001F600\": 1\n\"é\\n\": 2\n"
`shouldBe` Right (mapping [(YString "ab😀", YInt 1), (YString "é\n", YInt 2)])
it "rejects malformed escapes in mapping keys" $
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["\"\\q\": 1", "\"\\uD800\": 1", "x: 0\n\"\\U00110000\": 1", "\"unterminated: 1"]
it "handles URI punctuation inside verbatim flow tags" $ do
let value = seq_ [YTagged (Tag "urn:comma,a") (YString "x")]
D.decode "[!<urn:comma,a> x]" `shouldBe` Right value
D.decode (P.render P.compactOptions value) `shouldBe` Right value
it "expands each tag handle exactly once" $
D.decode "%TAG ! !prefix-\n---\n!foo key: value\n"
`shouldBe` Right (mapping [(YTagged (Tag "!prefix-foo") (YString "key"), YString "value")])
it "rejects duplicate keys after scalar and tag resolution" $
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["a: 1\na: 2", "{a: 1, a: 2}", "01: a\n1: b", "!!str a: 1\na: 2", "? [1,2]\n: a\n? [1,2]\n: b", ".nan: a\n.NaN: b", "-0.0: a\n0.0: b"]
it "rejects stray flow commas and empty anchor names" $
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["{,a: 1}", "{a: 1,, b: 2}", "{,,}", "[& x]", "& value", "[* ]"]
it "supports merge mappings with explicit overrides and earlier-map precedence" $
fmap J.yamlToJSON (D.decode "a: &a {x: 1, y: 2}\nb: &b {x: 3, z: 4}\nvalue:\n <<: [*a, *b]\n y: 9\n")
`shouldBe` Right (Aeson.object ["a" Aeson..= Aeson.object ["x" Aeson..= (1 :: Int), "y" Aeson..= (2 :: Int)], "b" Aeson..= Aeson.object ["x" Aeson..= (3 :: Int), "z" Aeson..= (4 :: Int)], "value" Aeson..= Aeson.object ["x" Aeson..= (1 :: Int), "y" Aeson..= (9 :: Int), "z" Aeson..= (4 :: Int)]])
it "keeps quoted merge-looking keys literal and rejects invalid merge sources" $ do
let literal = mapping [(YString "<<", YInt 1)]
D.decode "\"<<\": 1" `shouldBe` Right literal
D.decode (E.encode literal) `shouldBe` Right literal
D.decode (P.render P.defaultOptions literal) `shouldBe` Right literal
D.decode (E.encode (YString "<<")) `shouldBe` Right (YString "<<")
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["<<: 1", "<<: [{x: 1}, 2]"]
it "does not retain discarded literal blank lines in the decoded text buffer" $
mapM_
( \header -> case D.decode (header <> "\n text\n" <> T.replicate 10000 "\n") of
Right (YString text@(TI.Text (TA.ByteArray bytes#) _ _)) -> do
T.stripEnd text `shouldBe` "text"
I# (sizeofByteArray# bytes#) `shouldSatisfy` (<= 128)
result -> expectationFailure (show result)
)
["|-", "|"]
it "validates controls at every byte alignment through the ASCII fast path" $
mapM_
( \offset -> do
let prefix = "key: " <> T.replicate offset "x"
mapM_ (\char -> D.decode (prefix <> T.singleton char) `shouldSatisfy` isLeft) ['\0', '\v', '\x7f', '\x80', '\xFFFF']
D.decode (prefix <> "\r\nnext: valid\r\n") `shouldBe` D.decode (prefix <> "\nnext: valid\n")
)
[0 .. 15]
it "keeps non-ASCII quoted content on the lexical validation path" $
mapM_
( \offset -> do
let content = T.replicate offset "x" <> "\x7f\x80\xFFFF"
D.decode ("\"" <> content <> "\"") `shouldBe` Right (YString content)
)
[0 .. 15]
it "copies literal UTF-8, relative indentation, and chomping without changing content" $ do
D.decode "|-\n é\n 🧰\n\n\n" `shouldBe` Right (YString "é\n 🧰")
D.decode "|\n é\n 🧰\n\n\n" `shouldBe` Right (YString "é\n 🧰\n")
D.decode "|+\n é\n 🧰\n\n\n" `shouldBe` Right (YString "é\n 🧰\n\n\n")
D.decode "|2-\n a\n b\n\n" `shouldBe` Right (YString " a\n b")
it "rounds decimal floats correctly and bounds extreme exponents" $ do
mapM_
(\number -> D.decode (E.encode (YFloat number)) `shouldBe` Right (YFloat number))
[1.1930203655234305e70, -2.3686045112702234e69, 5e-324, 2.2250738585072014e-308, 1.7976931348623157e308]
D.decode "1e999999999999999999999999" `shouldBe` Right (YFloat (1 / 0))
D.decode "1e-999999999999999999999999" `shouldBe` Right (YFloat 0)
D.decode "0e999999999999999999999999" `shouldBe` Right (YFloat 0)
it "rejects raw C0 controls even inside quoted strings and comments" $
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["x\0y", "\"x\0y\"", "'x\1y'", "# comment\2\n"]
it "rejects non-printable characters in plain and block scalars" $
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["x\x7fy", "x\x9fy", "x\xfffey", "|\n x\xffffy\n", "x\xfeffy"]
it "accepts byte order marks at document prefixes" $ do
D.decode "\xfeffkey: value" `shouldBe` Right (mapping [(YString "key", YString "value")])
fmap (fmap docBody . V.toList . unStream) (D.decodeStream "\xfeff---\n1\n...\n\xfeff---\n2\n") `shouldBe` Right [YInt 1, YInt 2]
D.decode "- one\n\xfeff\n- two\n" `shouldSatisfy` isLeft
it "detects UTF-16 and UTF-32 with and without a BOM" $ do
let source = "key: \"hello 😀\"\n"
expected = D.decode source
encoders = [TE.encodeUtf16LE, TE.encodeUtf16BE, TE.encodeUtf32LE, TE.encodeUtf32BE]
mapM_ (\encode -> mapM_ (\text -> D.decodeBS (encode text) `shouldBe` expected) [source, "\xfeff" <> source]) encoders
it "rejects malformed wide encodings instead of throwing or replacing characters" $
mapM_
(\bytes -> D.decodeBS (BS.pack bytes) `shouldSatisfy` isLeft)
[[255, 254, 0], [254, 255, 216, 0], [255, 254, 0, 220], [0, 0, 254, 255, 0, 17, 0, 0], [255, 254, 0, 0, 0, 216, 0, 0]]
it "validates directive versions, handles, and repeated handle declarations" $
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["%YAML 2.0\n---\nx", "%TAG bad tag:example.com,2020:\n---\nx", "%TAG !bad_! !prefix\n---\nx", "%TAG !e! !a\n%TAG !e! !b\n---\nx"]
it "rejects repeated properties on one node in both block and flow contexts" $
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["!foo !bar x", "&a &b x", "[!foo !bar x]", "[&a &b x]", "!foo &a !bar x", "&a !foo &b x"]
it "validates tag URI spelling without expanding percent escapes" $ do
D.decode "!<tag:example.com,%41> x" `shouldBe` Right (YTagged (Tag "tag:example.com,%41") (YString "x"))
mapM_
(\source -> D.decode source `shouldSatisfy` isLeft)
["!<urn:bad%> x", "[!<urn:bad%GG> x]", "!<> x", "!<!> x", "!<$:?> x", "%TAG !e! urn:x\n---\n!e! x", "!<urn:x>value"]
it "resolves a non-specific tag on an empty node as an empty string" $
fmap unwrap (D.decode "!") `shouldBe` Right (YString "")
it "allows a root anchor before a same-column mapping" $
D.decode "&a\nx: 1\ny: 2\n" `shouldBe` Right (YAnchored (Anchor "a") (mapping [(YString "x", YInt 1), (YString "y", YInt 2)]))
it "registers properties on later mapping keys before parsing their values" $
mapM_
(\source -> D.decode source `shouldSatisfy` either (const False) (const True))
["first: 1\n&a second: *a\n", "? first\n: 1\n&a second: *a\n", "[1]: value\n&a second: *a\n"]
it "decodes quoted keys followed by quoted values as mappings" $
mapM_
(\source -> D.decode source `shouldBe` Right (mapping [(YString "", YString "")]))
["\"\": \"\"", "'': ''"]
it "uses null for empty implicit keys and honours explicit flow-key markers" $ do
D.decode ":" `shouldBe` Right (mapping [(YNull, YNull)])
D.decode "{? foo :, : bar}" `shouldBe` Right (mapping [(YString "foo", YNull), (YNull, YString "bar")])
D.decode "{?}" `shouldBe` Right (mapping [(YNull, YNull)])
it "keeps compact mapping keys inside explicit keys" $
D.decode "? []: x\n" `shouldBe` Right (mapping [(mapping [(seq_ [], YString "x")], YNull)])
it "does not absorb sibling sequence entries into empty anchored values or keys" $ do
D.decode "- first: &a\n- second\n" `shouldBe` Right (seq_ [mapping [(YString "first", YAnchored (Anchor "a") YNull)], YString "second"])
D.decode "- ? &a\n- second\n" `shouldBe` Right (seq_ [mapping [(YAnchored (Anchor "a") YNull, YNull)], YString "second"])
it "preserves comment-looking block scalar content at its baseline" $
D.decode "text: |\n first\n # content\n last\n" `shouldBe` Right (mapping [(YString "text", YString "first\n# content\nlast\n")])
it "rejects reserved indicators in plain scalars at roots and in flow values" $
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["@foo", "`foo", "[%foo]", "[|foo]", "{a: :}", "key: @foo", "key: `foo"]
it "rejects multiple documents through single-document entry points" $ do
D.decode "1\n---\n2" `shouldSatisfy` isLeft
D.decodeBS "1\n---\n2" `shouldSatisfy` isLeft
it "treats empty custom-tag scalars as empty strings" $
D.decode "!custom" `shouldBe` Right (YTagged (Tag "!custom") (YString ""))
it "distinguishes raw non-C0 quoted content from non-printable comments" $ do
mapM_ (\source -> fmap unwrap (D.decode source) `shouldBe` Right (YString "\x9f")) ["\"\x9f\"", "'\x9f'", "--- \"\x9f\"", "\xfeff\"\x9f\""]
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["# \"\x9f\"\n", "key: value # \xfffe", "'key': \"value\" # \x7f"]
it "preserves Unicode spaces and non-ASCII line separators as scalar content" $
mapM_
( \char -> do
let space = T.singleton char
strings = [space, "left" <> space, space <> "right", "left" <> space <> "right"]
mapM_
( \text -> do
D.decode text `shouldBe` Right (YString text)
D.decode (E.encode (YString text)) `shouldBe` Right (YString text)
mapM_ (\options -> assertRenderRoundtrip options (mapping [(YString ("key" <> space), YString text)])) [P.defaultOptions, P.compactOptions]
)
strings
)
['\xA0', '\x85', '\x2009', '\x2028', '\x2029']
it "keeps non-breaking spaces next to comment indicators and continuation prefixes" $ do
let nbsp = T.singleton '\xA0'
D.decode ("key: " <> nbsp <> "#content") `shouldBe` Right (mapping [(YString "key", YString (nbsp <> "#content"))])
D.decode ("first\n " <> nbsp <> "second") `shouldBe` Right (YString ("first " <> nbsp <> "second"))
D.decode ("\"first\n " <> nbsp <> "second\"") `shouldBe` Right (YString ("first " <> nbsp <> "second"))
it "requires ASCII directive separation and float tag spellings" $ do
D.decode ("%YAML " <> T.singleton '\xA0' <> "1.2\n---\nx") `shouldSatisfy` isLeft
D.decode "!!float 0x10" `shouldSatisfy` isLeft
case D.decode "!!float -0" of
Right value -> case unwrap value of YFloat number -> isNegativeZero number `shouldBe` True; _ -> expectationFailure "expected float"
Left err -> expectationFailure err
it "accepts numeric YAML versions with leading zeroes" $
D.decode "%YAML 01.02\n---\nx" `shouldBe` Right (YString "x")
it "accepts indented comments throughout multiline flow collections" $ do
D.decode "{\n # key comment\n key: 1\n # before close\n}\n" `shouldBe` D.decode "{key: 1}"
D.decode "[\n # item comment\n 1, # inline\n # next comment\n 2\n]\n" `shouldBe` D.decode "[1, 2]"
it "decodes expanding Unicode escapes into their UTF-8 scalars" $ do
let source = "\"é" <> T.replicate 500 "\\L\\P\\u00a0\\U0010ffff" <> "\\nlast\""
expected = YString ("é" <> T.replicate 500 "\x2028\x2029\xA0\x10FFFF" <> "\nlast")
D.decode source `shouldBe` Right expected
it "distinguishes escaped quotes after odd and even backslash runs" $
mapM_
(\text -> assertRenderRoundtrip (P.defaultOptions {P.roStringStyleHint = P.HintAlwaysDouble}) (YString text))
["\\", "\\\"", "\\\\", "\\\\\"", "a\"b\\c", "é\\\\\"last"]
it "rejects incomplete and out-of-range escapes at the end of a quoted buffer" $
mapM_ (\source -> D.decode source `shouldSatisfy` isLeft) ["\"trailing\\", "[\"\\u\"]", "\"\\Uffffffff\"", "\"\\U00110000\"", "\"\\uD800\""]