on master help sign in
Merge pull request #54 from zalora/handle_async_exceptions
96d77a7Michael Snoyman
Data/Yaml.hs+9−204
@@ -1,8 +1,4 @@
1−{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE FlexibleInstances #-}
3{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
4−{-# LANGUAGE DeriveDataTypeable #-}
5−{-# LANGUAGE MultiParamTypeClasses #-}
6{-# LANGUAGE PatternGuards #-}
2{-# LANGUAGE PatternGuards #-}
7{-# LANGUAGE ScopedTypeVariables #-}
3{-# LANGUAGE ScopedTypeVariables #-}
8{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
@@ -66,37 +62,27 @@ import Data.Aeson
66import Data.Aeson.Types (Pair, parseMaybe, parseEither, Parser)
62import Data.Aeson.Types (Pair, parseMaybe, parseEither, Parser)
67import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
63import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
68import Data.ByteString (ByteString)
64import Data.ByteString (ByteString)
69−import qualified Data.Map as Map
70import System.IO.Unsafe (unsafePerformIO)
65import System.IO.Unsafe (unsafePerformIO)
71−import Control.Exception (try, throwIO, fromException, Exception, SomeException, AsyncException)
66+import Control.Exception
72−import Control.Monad.Trans.State
73import qualified Data.Conduit as C
67import qualified Data.Conduit as C
74import qualified Data.Conduit.List as CL
68import qualified Data.Conduit.List as CL
75−import Control.Monad.Trans.Class (MonadTrans, lift)
76−import Control.Monad.IO.Class (MonadIO, liftIO)
77−import Control.Monad (liftM, ap)
78−import Control.Applicative (Applicative(..))
79−import Data.Char (toUpper)
80import qualified Data.Vector as V
69import qualified Data.Vector as V
81−import Data.Text (Text, pack)
70+import Data.Text (Text)
82import qualified Data.Text as T
71import qualified Data.Text as T
83−import Data.Text.Encoding (encodeUtf8, decodeUtf8With)
72+import Data.Text.Encoding (encodeUtf8)
84−import Data.Text.Encoding.Error (lenientDecode)
85import qualified Data.HashMap.Strict as M
73import qualified Data.HashMap.Strict as M
86−import Data.Typeable
87import qualified Data.HashSet as HashSet
74import qualified Data.HashSet as HashSet
88−import Data.Text.Read
89#if MIN_VERSION_aeson(0, 7, 0)
75#if MIN_VERSION_aeson(0, 7, 0)
90−import Data.Scientific (fromFloatDigits)
91import qualified Data.Text.Encoding as TE
76import qualified Data.Text.Encoding as TE
92import qualified Data.Text.Lazy as TL
77import qualified Data.Text.Lazy as TL
93import Data.Text.Lazy.Builder (toLazyText)
78import Data.Text.Lazy.Builder (toLazyText)
94import Data.Aeson.Encode (encodeToTextBuilder)
79import Data.Aeson.Encode (encodeToTextBuilder)
95#else
80#else
96−import Data.Attoparsec.Number
97import qualified Data.ByteString.Char8 as S8
81import qualified Data.ByteString.Char8 as S8
98#endif
82#endif
99−import Control.Monad.Trans.Resource (ResourceT, runResourceT)
83+import Control.Monad.Trans.Resource (runResourceT)
84+
85+import Data.Yaml.Internal
100
86
101encode :: ToJSON a => a -> ByteString
87encode :: ToJSON a => a -> ByteString
102encode obj = unsafePerformIO $
88encode obj = unsafePerformIO $
@@ -172,164 +158,12 @@ isNumeric =
172 || c == '.'
158 || c == '.'
173 || c == '-'
159 || c == '-'
174
160
175−-- Parsing
176−
177−data ParseException = NonScalarKey
178− | UnknownAlias { _anchorName :: Y.AnchorName }
179− | UnexpectedEvent { _received :: Maybe Event
180− , _expected :: Maybe Event
181− }
182− | InvalidYaml (Maybe YamlException)
183− | AesonException String
184− | OtherParseException SomeException
185− | NonStringKeyAlias Y.AnchorName Value
186− | CyclicIncludes
187− deriving (Show, Typeable)
188−instance Exception ParseException
189−
190−newtype PErrorT m a = PErrorT { runPErrorT :: m (Either ParseException a) }
191−instance Monad m => Functor (PErrorT m) where
192− fmap = liftM
193−instance Monad m => Applicative (PErrorT m) where
194− pure = return
195− (<*>) = ap
196−instance Monad m => Monad (PErrorT m) where
197− return = PErrorT . return . Right
198− (PErrorT m) >>= f = PErrorT $ do
199− e <- m
200− case e of
201− Left e' -> return $ Left e'
202− Right a -> runPErrorT $ f a
203−instance MonadTrans PErrorT where
204− lift = PErrorT . liftM Right
205−instance MonadIO m => MonadIO (PErrorT m) where
206− liftIO = lift . liftIO
207−
208−type Parse = StateT (Map.Map String Value) (ResourceT IO)
209−
210−requireEvent :: Event -> C.Sink Event Parse ()
211−requireEvent e = do
212− f <- CL.head
213− if f == Just e
214− then return ()
215− else liftIO $ throwIO $ UnexpectedEvent f $ Just e
216−
217−parse :: C.Sink Event Parse Value
218−parse = do
219− requireEvent EventStreamStart
220− requireEvent EventDocumentStart
221− res <- parseO
222− requireEvent EventDocumentEnd
223− requireEvent EventStreamEnd
224− return res
225−
226−parseScalar :: ByteString -> Anchor -> Style -> Tag
227− -> C.Sink Event Parse Text
228−parseScalar v a style tag = do
229− let res = decodeUtf8With lenientDecode v
230− case a of
231− Nothing -> return res
232− Just an -> do
233− lift $ modify (Map.insert an $ textToValue style tag res)
234− return res
235−
236−textToValue :: Style -> Tag -> Text -> Value
237−textToValue SingleQuoted _ t = String t
238−textToValue DoubleQuoted _ t = String t
239−textToValue _ StrTag t = String t
240−textToValue Folded _ t = String t
241−textToValue _ _ t
242− | t `elem` ["null", "Null", "NULL", "~", ""] = Null
243− | any (t `isLike`) ["y", "yes", "on", "true"] = Bool True
244− | any (t `isLike`) ["n", "no", "off", "false"] = Bool False
245−#if MIN_VERSION_aeson(0, 7, 0)
246− | Right (x, "") <- signed decimal t = Number $ fromIntegral (x :: Integer)
247− | Right (x, "") <- double t = Number $ fromFloatDigits x
248−#else
249− | Right (x, "") <- signed decimal t = Number $ I x
250− | Right (x, "") <- double t = Number $ D x
251−#endif
252− | otherwise = String t
253− where x `isLike` ref = x `elem` [ref, T.toUpper ref, titleCased]
254− where titleCased = toUpper (T.head ref) `T.cons` T.tail ref
255−
256−
257−parseO :: C.Sink Event Parse Value
258−parseO = do
259− me <- CL.head
260− case me of
261− Just (EventScalar v tag style a) -> fmap (textToValue style tag) $ parseScalar v a style tag
262− Just (EventSequenceStart a) -> parseS a id
263− Just (EventMappingStart a) -> parseM a M.empty
264− Just (EventAlias an) -> do
265− m <- lift get
266− case Map.lookup an m of
267− Nothing -> liftIO $ throwIO $ UnknownAlias an
268− Just v -> return v
269− _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
270−
271−parseS :: Y.Anchor
272− -> ([Value] -> [Value])
273− -> C.Sink Event Parse Value
274−parseS a front = do
275− me <- CL.peek
276− case me of
277− Just EventSequenceEnd -> do
278− CL.drop 1
279− let res = Array $ V.fromList $ front []
280− case a of
281− Nothing -> return res
282− Just an -> do
283− lift $ modify $ Map.insert an res
284− return res
285− _ -> do
286− o <- parseO
287− parseS a $ front . (:) o
288−
289−parseM :: Y.Anchor
290− -> M.HashMap Text Value
291− -> C.Sink Event Parse Value
292−parseM a front = do
293− me <- CL.peek
294− case me of
295− Just EventMappingEnd -> do
296− CL.drop 1
297− let res = Object front
298− case a of
299− Nothing -> return res
300− Just an -> do
301− lift $ modify $ Map.insert an res
302− return res
303− _ -> do
304− CL.drop 1
305− s <- case me of
306− Just (EventScalar v tag style a') -> parseScalar v a' style tag
307− Just (EventAlias an) -> do
308− m <- lift get
309− case Map.lookup an m of
310− Nothing -> liftIO $ throwIO $ UnknownAlias an
311− Just (String t) -> return t
312− Just v -> liftIO $ throwIO $ NonStringKeyAlias an v
313− _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
314− o <- parseO
315−
316− let al = M.insert s o front
317− al' = if s == pack "<<"
318− then case o of
319− Object l -> M.union front l
320− Array l -> M.union front $ foldl merge' M.empty $ V.toList l
321− _ -> al
322− else al
323− parseM a al'
324− where merge' al (Object om) = M.union al om
325− merge' al _ = al
326−
327decode :: FromJSON a
161decode :: FromJSON a
328 => ByteString
162 => ByteString
329 -> Maybe a
163 -> Maybe a
330decode bs = unsafePerformIO
164decode bs = unsafePerformIO
331− $ fmap (either (const Nothing) (either (const Nothing) Just))
165+ $ fmap (either (const Nothing) id)
332− $ decodeHelper (Y.decode bs)
166+ $ decodeHelper_ (Y.decode bs)
333
167
334decodeFile :: FromJSON a
168decodeFile :: FromJSON a
335 => FilePath
169 => FilePath
@@ -343,24 +177,7 @@ decodeFileEither
343 :: FromJSON a
177 :: FromJSON a
344 => FilePath
178 => FilePath
345 -> IO (Either ParseException a)
179 -> IO (Either ParseException a)
346−decodeFileEither fp = do
180+decodeFileEither = decodeHelper_ . Y.decodeFile
347− x <- try $ runResourceT $ flip evalStateT Map.empty $ Y.decodeFile fp C.$$ parse
348− case x of
349− Left e
350− | Just pe <- fromException e -> return $ Left pe
351− | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)
352− | shouldBeCaught e -> return $ Left $ OtherParseException e
353− | otherwise -> throwIO e
354− Right y ->
355− return $ case parseEither parseJSON y of
356− Left s -> Left $ AesonException s
357− Right v -> Right v
358− where
359− -- FIXME this function needs more thought
360− shouldBeCaught e
361− | Just (_ :: AsyncException) <- fromException e = False
362− -- Would be nice... | Just (_ :: Timeout) <- fromException e = False
363− | otherwise = True
364
181
365decodeEither :: FromJSON a => ByteString -> Either String a
182decodeEither :: FromJSON a => ByteString -> Either String a
366decodeEither bs = unsafePerformIO
183decodeEither bs = unsafePerformIO
@@ -376,18 +193,6 @@ decodeEither' = either Left (either (Left . AesonException) Right)
376 . decodeHelper
193 . decodeHelper
377 . Y.decode
194 . Y.decode
378
195
379−decodeHelper :: FromJSON a
380− => C.Source Parse Y.Event
381− -> IO (Either ParseException (Either String a))
382−decodeHelper src = do
383− x <- try $ runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
384− case x of
385− Left e
386− | Just pe <- fromException e -> return $ Left pe
387− | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)
388− | otherwise -> throwIO e
389− Right y -> return $ Right $ parseEither parseJSON y
390−
391array :: [Value] -> Value
196array :: [Value] -> Value
392array = Array . V.fromList
197array = Array . V.fromList
393
198
Data/Yaml/Include.hs+2−3
@@ -1,7 +1,6 @@
1{-# LANGUAGE RankNTypes #-}
1{-# LANGUAGE RankNTypes #-}
2module Data.Yaml.Include (decodeFile, decodeFileEither) where
2module Data.Yaml.Include (decodeFile, decodeFileEither) where
3
3
4−import Control.Applicative
5import Control.Monad (when)
4import Control.Monad (when)
6import Control.Exception (throwIO)
5import Control.Exception (throwIO)
7import Control.Monad.IO.Class (liftIO)
6import Control.Monad.IO.Class (liftIO)
@@ -13,7 +12,7 @@ import Data.Text.Encoding (decodeUtf8)
13import Data.Aeson (FromJSON)
12import Data.Aeson (FromJSON)
14import Data.Conduit
13import Data.Conduit
15import qualified Data.Conduit.List as CL
14import qualified Data.Conduit.List as CL
16−import Data.Yaml (ParseException(..), decodeHelper)
15+import Data.Yaml.Internal (ParseException(..), decodeHelper_, decodeHelper)
17import Text.Libyaml hiding (decodeFile)
16import Text.Libyaml hiding (decodeFile)
18import qualified Text.Libyaml as Y
17import qualified Text.Libyaml as Y
19
18
@@ -59,4 +58,4 @@ decodeFileEither
59 :: FromJSON a
58 :: FromJSON a
60 => FilePath
59 => FilePath
61 -> IO (Either ParseException a)
60 -> IO (Either ParseException a)
62−decodeFileEither fp = either Left (either (Left . AesonException) Right) <$> decodeHelper (eventsFromFile fp)
61+decodeFileEither = decodeHelper_ . eventsFromFile
Data/Yaml/Internal.hsnew file+219
@@ -0,0 +1,219 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE DeriveDataTypeable #-}
3+{-# LANGUAGE PatternGuards #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+module Data.Yaml.Internal
6+ (
7+ ParseException(..)
8+ , parse
9+ , decodeHelper
10+ , decodeHelper_
11+ ) where
12+
13+import qualified Text.Libyaml as Y
14+import Data.Aeson
15+import Data.Aeson.Types hiding (parse)
16+import Text.Libyaml hiding (encode, decode, encodeFile, decodeFile)
17+import Data.ByteString (ByteString)
18+import qualified Data.Map as Map
19+import Control.Exception
20+import Control.Exception.Enclosed
21+import Control.Monad.Trans.State
22+import qualified Data.Conduit as C
23+import qualified Data.Conduit.List as CL
24+import Control.Monad.Trans.Class (MonadTrans, lift)
25+import Control.Monad.IO.Class (MonadIO, liftIO)
26+import Control.Monad (liftM, ap)
27+import Control.Applicative (Applicative(..))
28+import Data.Char (toUpper)
29+import qualified Data.Vector as V
30+import Data.Text (Text, pack)
31+import qualified Data.Text as T
32+import Data.Text.Encoding (decodeUtf8With)
33+import Data.Text.Encoding.Error (lenientDecode)
34+import qualified Data.HashMap.Strict as M
35+import Data.Typeable
36+import Data.Text.Read
37+#if MIN_VERSION_aeson(0, 7, 0)
38+import Data.Scientific (fromFloatDigits)
39+#else
40+import Data.Attoparsec.Number
41+#endif
42+import Control.Monad.Trans.Resource (ResourceT, runResourceT)
43+
44+data ParseException = NonScalarKey
45+ | UnknownAlias { _anchorName :: Y.AnchorName }
46+ | UnexpectedEvent { _received :: Maybe Event
47+ , _expected :: Maybe Event
48+ }
49+ | InvalidYaml (Maybe YamlException)
50+ | AesonException String
51+ | OtherParseException SomeException
52+ | NonStringKeyAlias Y.AnchorName Value
53+ | CyclicIncludes
54+ deriving (Show, Typeable)
55+instance Exception ParseException
56+
57+newtype PErrorT m a = PErrorT { runPErrorT :: m (Either ParseException a) }
58+instance Monad m => Functor (PErrorT m) where
59+ fmap = liftM
60+instance Monad m => Applicative (PErrorT m) where
61+ pure = return
62+ (<*>) = ap
63+instance Monad m => Monad (PErrorT m) where
64+ return = PErrorT . return . Right
65+ (PErrorT m) >>= f = PErrorT $ do
66+ e <- m
67+ case e of
68+ Left e' -> return $ Left e'
69+ Right a -> runPErrorT $ f a
70+instance MonadTrans PErrorT where
71+ lift = PErrorT . liftM Right
72+instance MonadIO m => MonadIO (PErrorT m) where
73+ liftIO = lift . liftIO
74+
75+type Parse = StateT (Map.Map String Value) (ResourceT IO)
76+
77+requireEvent :: Event -> C.Sink Event Parse ()
78+requireEvent e = do
79+ f <- CL.head
80+ if f == Just e
81+ then return ()
82+ else liftIO $ throwIO $ UnexpectedEvent f $ Just e
83+
84+parse :: C.Sink Event Parse Value
85+parse = do
86+ requireEvent EventStreamStart
87+ requireEvent EventDocumentStart
88+ res <- parseO
89+ requireEvent EventDocumentEnd
90+ requireEvent EventStreamEnd
91+ return res
92+
93+parseScalar :: ByteString -> Anchor -> Style -> Tag
94+ -> C.Sink Event Parse Text
95+parseScalar v a style tag = do
96+ let res = decodeUtf8With lenientDecode v
97+ case a of
98+ Nothing -> return res
99+ Just an -> do
100+ lift $ modify (Map.insert an $ textToValue style tag res)
101+ return res
102+
103+textToValue :: Style -> Tag -> Text -> Value
104+textToValue SingleQuoted _ t = String t
105+textToValue DoubleQuoted _ t = String t
106+textToValue _ StrTag t = String t
107+textToValue Folded _ t = String t
108+textToValue _ _ t
109+ | t `elem` ["null", "Null", "NULL", "~", ""] = Null
110+ | any (t `isLike`) ["y", "yes", "on", "true"] = Bool True
111+ | any (t `isLike`) ["n", "no", "off", "false"] = Bool False
112+#if MIN_VERSION_aeson(0, 7, 0)
113+ | Right (x, "") <- signed decimal t = Number $ fromIntegral (x :: Integer)
114+ | Right (x, "") <- double t = Number $ fromFloatDigits x
115+#else
116+ | Right (x, "") <- signed decimal t = Number $ I x
117+ | Right (x, "") <- double t = Number $ D x
118+#endif
119+ | otherwise = String t
120+ where x `isLike` ref = x `elem` [ref, T.toUpper ref, titleCased]
121+ where titleCased = toUpper (T.head ref) `T.cons` T.tail ref
122+
123+
124+parseO :: C.Sink Event Parse Value
125+parseO = do
126+ me <- CL.head
127+ case me of
128+ Just (EventScalar v tag style a) -> fmap (textToValue style tag) $ parseScalar v a style tag
129+ Just (EventSequenceStart a) -> parseS a id
130+ Just (EventMappingStart a) -> parseM a M.empty
131+ Just (EventAlias an) -> do
132+ m <- lift get
133+ case Map.lookup an m of
134+ Nothing -> liftIO $ throwIO $ UnknownAlias an
135+ Just v -> return v
136+ _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
137+
138+parseS :: Y.Anchor
139+ -> ([Value] -> [Value])
140+ -> C.Sink Event Parse Value
141+parseS a front = do
142+ me <- CL.peek
143+ case me of
144+ Just EventSequenceEnd -> do
145+ CL.drop 1
146+ let res = Array $ V.fromList $ front []
147+ case a of
148+ Nothing -> return res
149+ Just an -> do
150+ lift $ modify $ Map.insert an res
151+ return res
152+ _ -> do
153+ o <- parseO
154+ parseS a $ front . (:) o
155+
156+parseM :: Y.Anchor
157+ -> M.HashMap Text Value
158+ -> C.Sink Event Parse Value
159+parseM a front = do
160+ me <- CL.peek
161+ case me of
162+ Just EventMappingEnd -> do
163+ CL.drop 1
164+ let res = Object front
165+ case a of
166+ Nothing -> return res
167+ Just an -> do
168+ lift $ modify $ Map.insert an res
169+ return res
170+ _ -> do
171+ CL.drop 1
172+ s <- case me of
173+ Just (EventScalar v tag style a') -> parseScalar v a' style tag
174+ Just (EventAlias an) -> do
175+ m <- lift get
176+ case Map.lookup an m of
177+ Nothing -> liftIO $ throwIO $ UnknownAlias an
178+ Just (String t) -> return t
179+ Just v -> liftIO $ throwIO $ NonStringKeyAlias an v
180+ _ -> liftIO $ throwIO $ UnexpectedEvent me Nothing
181+ o <- parseO
182+
183+ let al = M.insert s o front
184+ al' = if s == pack "<<"
185+ then case o of
186+ Object l -> M.union front l
187+ Array l -> M.union front $ foldl merge' M.empty $ V.toList l
188+ _ -> al
189+ else al
190+ parseM a al'
191+ where merge' al (Object om) = M.union al om
192+ merge' al _ = al
193+
194+decodeHelper :: FromJSON a
195+ => C.Source Parse Y.Event
196+ -> IO (Either ParseException (Either String a))
197+decodeHelper src = do
198+ x <- tryAny $ runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
199+ case x of
200+ Left e
201+ | Just pe <- fromException e -> return $ Left pe
202+ | Just ye <- fromException e -> return $ Left $ InvalidYaml $ Just (ye :: YamlException)
203+ | otherwise -> throwIO e
204+ Right y -> return $ Right $ parseEither parseJSON y
205+
206+decodeHelper_ :: FromJSON a
207+ => C.Source Parse Event
208+ -> IO (Either ParseException a)
209+decodeHelper_ src = do
210+ x <- tryAny $ runResourceT $ flip evalStateT Map.empty $ src C.$$ parse
211+ return $ case x of
212+ Left e
213+ | Just pe <- fromException e -> Left pe
214+ | Just ye <- fromException e -> Left $ InvalidYaml $ Just (ye :: YamlException)
215+ | otherwise -> Left $ OtherParseException e
216+ Right y -> either
217+ (Left . AesonException)
218+ Right
219+ (parseEither parseJSON y)
test/Data/Yaml/IncludeSpec.hs+7
@@ -2,8 +2,10 @@
2module Data.Yaml.IncludeSpec (main, spec) where
2module Data.Yaml.IncludeSpec (main, spec) where
3
3
4import Test.Hspec
4import Test.Hspec
5+import Test.Hspec.Expectations.Contrib
5import Data.Aeson
6import Data.Aeson
6import Data.Aeson.QQ
7import Data.Aeson.QQ
8+import Data.Yaml (ParseException)
7
9
8import Data.Yaml.Include
10import Data.Yaml.Include
9
11
@@ -35,3 +37,8 @@ spec = do
35
37
36 it "aborts on cyclic includes" $ do
38 it "aborts on cyclic includes" $ do
37 (decodeFile "test/resources/loop/foo.yaml" :: IO (Maybe Value)) `shouldThrow` anyException
39 (decodeFile "test/resources/loop/foo.yaml" :: IO (Maybe Value)) `shouldThrow` anyException
40+
41+ describe "decodeFileEither" $ do
42+ context "when file does not exist" $ do
43+ it "returns Left" $ do
44+ (decodeFileEither "./does_not_exist.yaml" :: IO (Either ParseException Value)) >>= (`shouldSatisfy` isLeft)
test/Data/YamlSpec.hs+7−1
@@ -19,6 +19,7 @@ import System.Directory
19import Control.Monad
19import Control.Monad
20import Control.Exception (try, SomeException)
20import Control.Exception (try, SomeException)
21import Test.Hspec
21import Test.Hspec
22+import Test.Hspec.Expectations.Contrib
22
23
23import qualified Data.Yaml as D
24import qualified Data.Yaml as D
24import Data.Yaml (object, array, (.=))
25import Data.Yaml (object, array, (.=))
@@ -114,7 +115,7 @@ spec = do
114 in D.decode (D.encode value') `shouldBe` Just value'
115 in D.decode (D.encode value') `shouldBe` Just value'
115 mapM_ tester specialStrings
116 mapM_ tester specialStrings
116
117
117− describe "yamlFileJSONparse" $
118+ describe "decodeFileEither" $ do
118 it "loads YAML through JSON into Haskell data" $ do
119 it "loads YAML through JSON into Haskell data" $ do
119 tj <- either (error . show) id `fmap` D.decodeFileEither "test/json.yaml"
120 tj <- either (error . show) id `fmap` D.decodeFileEither "test/json.yaml"
120 tj `shouldBe` TestJSON
121 tj `shouldBe` TestJSON
@@ -125,6 +126,11 @@ spec = do
125 , extrastring = "1234-foo"
126 , extrastring = "1234-foo"
126 }
127 }
127
128
129+ context "when file does not exist" $ do
130+ it "returns Left" $ do
131+ (D.decodeFileEither "./does_not_exist.yaml" :: IO (Either D.ParseException D.Value)) >>= (`shouldSatisfy` isLeft)
132+
133+
128 describe "round-tripping of special scalars" $ do
134 describe "round-tripping of special scalars" $ do
129 let special = words "y Y On ON false 12345 12345.0 12345a 12e3"
135 let special = words "y Y On ON false 12345 12345.0 12345a 12e3"
130 forM_ special $ \w -> it w $
136 forM_ special $ \w -> it w $
yaml.cabal+4
@@ -52,12 +52,15 @@ library
52 , scientific
52 , scientific
53 , filepath
53 , filepath
54 , directory
54 , directory
55+ , enclosed-exceptions
55 exposed-modules: Text.Libyaml
56 exposed-modules: Text.Libyaml
56 Data.Yaml
57 Data.Yaml
57 Data.Yaml.Aeson
58 Data.Yaml.Aeson
58 Data.Yaml.Builder
59 Data.Yaml.Builder
59 Data.Yaml.Parser
60 Data.Yaml.Parser
60 Data.Yaml.Include
61 Data.Yaml.Include
62+ other-modules:
63+ Data.Yaml.Internal
61 ghc-options: -Wall
64 ghc-options: -Wall
62 c-sources: c/helper.c
65 c-sources: c/helper.c
63 include-dirs: c
66 include-dirs: c
@@ -111,6 +114,7 @@ test-suite spec
111 Data.Yaml.IncludeSpec
114 Data.Yaml.IncludeSpec
112 cpp-options: -DTEST
115 cpp-options: -DTEST
113 build-depends: hspec >= 1.3
116 build-depends: hspec >= 1.3
117+ , hspec-expectations
114 , HUnit
118 , HUnit
115 , directory
119 , directory
116 , base >= 4 && < 5
120 , base >= 4 && < 5