1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
-- Examples of handling for JSON responses
--
-- This library provides several ways to handle JSON responses
{-# LANGUAGE DeriveGeneric, OverloadedStrings, ScopedTypeVariables #-}
{-# OPTIONS_GHC -fno-warn-unused-binds #-}
import Control.Lens ((&), (^.), (^?), (.~))
import Data.Aeson (FromJSON)
import Data.Aeson.Lens (key)
import Data.Map (Map)
import Data.Text (Text)
import GHC.Generics (Generic)
import qualified Control.Exception as E
import Network.Wreq
-- This Haskell type corresponds to the structure of a response body
-- from httpbin.org.
data GetBody = GetBody {
headers :: Map Text Text
, args :: Map Text Text
, origin :: Text
, url :: Text
} deriving (Show, Generic)
-- Get GHC to derive a FromJSON instance for us.
instance FromJSON GetBody
-- We expect this to succeed.
basic_asJSON :: IO ()
basic_asJSON = do
let opts = defaults & param "foo" .~ ["bar"]
r <- asJSON =<< getWith opts "http://httpbin.org/get"
-- The fact that we want a GetBody here will be inferred by our use
-- of the "headers" accessor function.
putStrLn $ "args: " ++ show (args (r ^. responseBody))
-- The response we expect here is valid JSON, but cannot be converted
-- to an [Int], so this will throw a JSONError.
failing_asJSON :: IO ()
failing_asJSON = do
(r :: Response [Int]) <- asJSON =<< get "http://httpbin.org/get"
putStrLn $ "response: " ++ show (r ^. responseBody)
-- This demonstrates how to catch a JSONError.
failing_asJSON_catch :: IO ()
failing_asJSON_catch =
failing_asJSON `E.catch` \(e :: JSONError) -> print e
-- Because asJSON is parameterized over MonadThrow, we can use it with
-- other instances.
--
-- Here, instead of throwing an exception in the IO monad, we instead
-- evaluate the result as an Either:
--
-- * if the conversion fails, the Left constructor will contain
-- whatever exception describes the error
--
-- * if the conversion succeeds, the Right constructor will contain
-- the converted response
either_asJSON :: IO ()
either_asJSON = do
r <- get "http://httpbin.org/get"
-- This first conversion attempt will fail, but because we're using
-- Either, it will not throw an exception that kills execution.
let failing = asJSON r :: Either E.SomeException (Response [Int])
print failing
-- Our second conversion attempt will succeed.
let succeeding = asJSON r :: Either E.SomeException (Response GetBody)
print succeeding
-- The lens package defines some handy combinators for use with the
-- aeson package, with which we can easily traverse parts of a JSON
-- response.
lens_aeson :: IO ()
lens_aeson = do
r <- get "http://httpbin.org/get"
print $ r ^? responseBody . key "headers" . key "User-Agent"
-- If we maintain the ResponseBody as a ByteString, the lens
-- combinators will have to convert the body to a Value every time
-- we start a new traversal.
-- When we need to poke at several parts of a response, it's more
-- efficient to use asValue to perform the conversion to a Value
-- once.
let opts = defaults & param "baz" .~ ["quux"]
v <- asValue =<< getWith opts "http://httpbin.org/get"
print $ v ^? responseBody . key "args" . key "baz"
main :: IO ()
main = do
basic_asJSON
failing_asJSON_catch
either_asJSON
lens_aeson
|