packages feed

api-builder 0.11.0.0 → 0.17.0.0

raw patch · 10 files changed

Files

api-builder.cabal view
@@ -1,90 +1,95 @@-name: api-builder-version: 0.11.0.0-synopsis: Library for easily building REST API wrappers in Haskell-license: BSD3-license-file: LICENSE-author: Fraser Murray-maintainer: fraser.m.murray@gmail.com-homepage: https://github.com/intolerable/api-builder-copyright: (c) Fraser Murray 2014-category: Network-build-type: Simple-cabal-version: >= 1.10+name:           api-builder+version:        0.17.0.0+synopsis:       Library for easily building REST API wrappers in Haskell+category:       Network+homepage:       https://github.com/intolerable/api-builder+author:         Fraser Murray+maintainer:     fraser.m.murray@gmail.com+copyright:      (c) Fraser Murray 2014+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10  source-repository head   type: git   location: git://github.com/intolerable/api-builder.git -source-repository this-  type: git-  location: git://github.com/intolerable/api-builder.git-  tag: v0.11.0.0- library-  exposed-modules:-    Network.API.Builder-    Network.API.Builder.API-    Network.API.Builder.Builder-    Network.API.Builder.Error-    Network.API.Builder.Examples.StackOverflow-    Network.API.Builder.Query-    Network.API.Builder.Receive-    Network.API.Builder.Routes-    Network.API.Builder.Send-    Network.API.Builder.Send.Multipart-  build-depends:-    base >= 4.6 && < 4.9,-    aeson >= 0.9 && < 0.10,-    bifunctors >= 4.0 && < 6.0,-    bytestring == 0.10.*,-    HTTP == 4000.*,-    http-client >= 0.4.11 && < 0.4.21,-    http-client-tls >= 0.2 && < 0.2.3,-    http-types == 0.8.*,-    text == 1.*,-    transformers >= 0.4 && < 0.5   hs-source-dirs: src/-  default-language: Haskell2010   default-extensions:-    FlexibleInstances     OverloadedStrings+    FlexibleInstances   ghc-options: -Wall+  build-depends:+    HTTP >=4000,+    aeson >=0.9,+    base >=4.6,+    bifunctors >=4.0,+    bytestring >=0.10,+    http-client >=0.4.30,+    http-client-tls >=0.2,+    http-types >=0.8,+    text >=1.0,+    tls >=1.3,+    transformers >=0.4+  exposed-modules:+      Network.API.Builder+      Network.API.Builder.API+      Network.API.Builder.Builder+      Network.API.Builder.Error+      Network.API.Builder.Examples.StackOverflow+      Network.API.Builder.Query+      Network.API.Builder.Receive+      Network.API.Builder.Routes+      Network.API.Builder.Send+      Network.API.Builder.Send.Multipart+  other-modules:+      Paths_api_builder+  default-language: Haskell2010  test-suite test-  hs-source-dirs: test-  main-is: Spec.hs-  default-extensions:-    OverloadedStrings-    ScopedTypeVariables-  default-language: Haskell2010   type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs: test/+  default-extensions: OverloadedStrings FlexibleInstances ScopedTypeVariables+  ghc-options: -Wall   build-depends:-    base == 4.*,-    Cabal >= 1.16.0,-    api-builder,+    Cabal >=1.16.0,     aeson,+    api-builder,+    base ==4.*,     bytestring,     hspec,     http-client,+    QuickCheck,     text,     transformers-  GHC-options: -Wall+  other-modules:+    Network.API.Builder.ErrorSpec+    Network.API.Builder.QuerySpec+    Network.API.Builder.RoutesSpec+    Network.API.Builder.SendSpec+    Paths_api_builder+  default-language: Haskell2010  test-suite test-io-  hs-source-dirs: test-io-  main-is: Spec.hs-  default-extensions:-    LambdaCase-  default-language: Haskell2010   type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs: test-io/+  default-extensions: OverloadedStrings FlexibleInstances LambdaCase+  ghc-options: -Wall   build-depends:-    base == 4.*,-    Cabal >= 1.16.0,-    api-builder,-    aeson,-    bytestring,-    containers,-    hspec,-    text,-    transformers-  GHC-options: -Wall+      Cabal >=1.16.0+    , aeson+    , api-builder+    , base ==4.*+    , bytestring+    , containers+    , hspec+    , text+    , transformers+  other-modules:+    Network.API.Builder.Examples.StackOverflowSpec+    Paths_api_builder+  default-language: Haskell2010
src/Network/API/Builder/API.hs view
@@ -81,7 +81,6 @@ execAPI b s api = do   m <- liftIO $ newManager tlsManagerSettings   (res, _, _) <- runAPI b m s api-  liftIO $ closeManager m   return res  -- | Runs an @API@ by executing its transformer stack and dumping it all into @IO@.
src/Network/API/Builder/Error.hs view
@@ -1,7 +1,7 @@ module Network.API.Builder.Error   ( APIError(..) ) where -import Data.Monoid+import Data.Semigroup import Network.HTTP.Client (HttpException) import Prelude @@ -23,9 +23,13 @@   (APIError a) == (APIError b) = a == b   InvalidURLError == InvalidURLError = True   (ParseError a) == (ParseError b) = a == b+  EmptyError == EmptyError = True   _ == _ = False +instance Semigroup (APIError a) where+  EmptyError <> x = x+  x <> _ = x+ instance Monoid (APIError a) where   mempty = EmptyError-  EmptyError `mappend` x = x-  x `mappend` _ = x+  mappend = (<>)
src/Network/API/Builder/Examples/StackOverflow.hs view
@@ -3,7 +3,7 @@ module Network.API.Builder.Examples.StackOverflow where  import Network.API.Builder-import Control.Applicative+import Control.Applicative ((<$>), (<*>)) import Data.Aeson import Data.Monoid (mempty) import Data.Text (Text)@@ -35,6 +35,9 @@ stackOverflow :: Builder stackOverflow = basicBuilder "StackOverflow API" "http://api.stackexchange.com" +stackOverflowSSL :: Builder+stackOverflowSSL = basicBuilder "StackOverflow API" "https://api.stackexchange.com"+ answersRoute :: Route answersRoute = Route { urlPieces = [ "2.2", "questions" ]                      , urlParams = [ "order" =. ("desc" :: Text)@@ -44,3 +47,6 @@  getAnswers :: IO (Either (APIError ()) Questions) getAnswers = execAPI stackOverflow () $ runRoute answersRoute++getAnswersSSL :: IO (Either (APIError ()) Questions)+getAnswersSSL = execAPI stackOverflowSSL () $ runRoute answersRoute
src/Network/API/Builder/Send.hs view
@@ -18,7 +18,7 @@   send builder r () =     case httpMethod r of       "POST" -> do-        req <- parseUrl $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)+        req <- parseUrlThrow $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)         return $ _customizeRequest builder $           req { requestHeaders = ("Content-Type", "application/x-www-form-urlencoded") : requestHeaders req               , requestBody = RequestBodyBS (dropQuestion $ queryString req)@@ -29,19 +29,22 @@  basicSend :: Builder -> Route -> Maybe Request basicSend builder r = do-  req <- parseUrl $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)+  req <- parseUrlThrow $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)   return $ _customizeRequest builder $ req { method = httpMethod r }  instance Sendable Value where   send builder r value =     case httpMethod r of       "POST" -> do-        req <- parseUrl $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)+        req <- parseUrlThrow $ Text.unpack $ routeURL (_baseURL builder) (_customizeRoute builder r)         return $ _customizeRequest builder $           req { requestBody = RequestBodyLBS (encode value)               , requestHeaders = ("Content-Type", "application/json") : requestHeaders req               , method = httpMethod r }       _ -> Nothing++useToJSON :: ToJSON a => Builder -> Route -> a -> Maybe Request+useToJSON b r v = send b r (toJSON v)  instance Sendable ByteString where   send builder r bs =
+ test-io/Network/API/Builder/Examples/StackOverflowSpec.hs view
@@ -0,0 +1,40 @@+module Network.API.Builder.Examples.StackOverflowSpec where++import Network.API.Builder.Examples.StackOverflow++import Control.Monad+import Data.List (nub)+import Test.Hspec+import qualified Data.Set as Set+import qualified Data.Text as Text++main :: IO ()+main = hspec spec++spec :: Spec+spec =+  describe "getAnswers" $ do++    it "can get answers" $+      getAnswers >>= \case+        Left _ -> expectationFailure "getAnswers failed"+        Right (Questions qs) -> do+          nub qs `shouldMatchList` qs+          forM_ qs $ \q -> do+            tags q `shouldSatisfy` isNubbed+            title q `shouldSatisfy` (not . Text.null)++    it "can get answers via https" $+      getAnswersSSL >>= \case+        Left _ -> expectationFailure "getAnswers failed"+        Right (Questions qs) -> do+          nub qs `shouldMatchList` qs+          forM_ qs $ \q -> do+            tags q `shouldSatisfy` isNubbed+            title q `shouldSatisfy` (not . Text.null)++isNubbed :: Ord a => [a] -> Bool+isNubbed = f Set.empty+  where+    f _ [] = True+    f s (x:xs) = not (Set.member x s) && f (Set.insert x s) xs
+ test/Network/API/Builder/ErrorSpec.hs view
@@ -0,0 +1,40 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Network.API.Builder.ErrorSpec where++import Data.Semigroup ((<>))+import Network.API.Builder.Error++import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck++main :: IO ()+main = hspec spec++instance Arbitrary a => Arbitrary (APIError a) where+  arbitrary = oneof+    [ APIError <$> arbitrary+    , pure InvalidURLError+    , ParseError <$> arbitrary+    , pure EmptyError+    ]++spec :: Spec+spec =+  describe "APIError" $ do+    describe "Eq" $ do+      it "should check for equality properly" $ do+        HTTPError undefined == (HTTPError undefined :: APIError ())+          `shouldBe` False+        APIError () `shouldBe` APIError ()+        InvalidURLError `shouldBe` (InvalidURLError :: APIError ())++      prop "ParseError s == ParseError s" $ \s ->+        ParseError s == (ParseError s :: APIError ())++      prop "APIError x == APIError x" $ \(x :: String) ->+        APIError x == APIError x++    describe "Semigroup" $ do+      prop "(x <> y) <> z == x <> (y <> z)" $ \(x :: APIError ()) y z ->+        (x <> y) <> z == x <> (y <> z)
+ test/Network/API/Builder/QuerySpec.hs view
@@ -0,0 +1,33 @@+module Network.API.Builder.QuerySpec where++import Network.API.Builder.Query++import Test.Hspec+import Test.Hspec.QuickCheck++main :: IO ()+main = hspec spec++spec :: Spec+spec =+  describe "ToQuery" $ do++    it "can convert an Integer" $ do+      toQuery "integer" (5 :: Integer) `shouldBe`+        [("integer", "5")]++    it "can convert a Boolean" $ do+      toQuery "bool" False `shouldBe` [("bool", "false")]++    it "can convert a list of Ints" $ do+      toQuery "list" ([1,2,3,4,5] :: [Int]) `shouldBe` [("list", "1,2,3,4,5")]++    it "can convert Maybe values" $ do+      toQuery "maybe" (Just False) `shouldBe` [("maybe", "false")]+      toQuery "maybe" (Nothing :: Maybe Bool) `shouldBe` []++    prop "toQuery x y == toQuery x (Just y)" $ \(y :: Int) ->+      toQuery "x" y == toQuery "x" (Just y)++skip :: String -> Expectation -> Expectation+skip _ _ = return ()
+ test/Network/API/Builder/RoutesSpec.hs view
@@ -0,0 +1,24 @@+module Network.API.Builder.RoutesSpec where++import Network.API.Builder.Routes++import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  describe "routeURL" $ do+    it "should be able to create a basic url" $ do+      routeURL "" (Route [ "api", "index" ] [ ] "GET")+        `shouldBe` "/api/index"+      routeURL "" (Route [ "api", "index.json" ] [ ] "GET")+        `shouldBe` "/api/index.json"+      routeURL "" (Route [ ] [ "test" =. False ] "GET")+        `shouldBe` "?test=false"+      routeURL "" (Route [ "about.json" ] [ "test" =. True ] "GET")+        `shouldBe` "/about.json?test=true"+      routeURL "" (Route [ "about.json" ] [ "test" =. True ] "GET")+        `shouldBe` "/about.json?test=true"+
+ test/Network/API/Builder/SendSpec.hs view
@@ -0,0 +1,100 @@+module Network.API.Builder.SendSpec where++import Network.API.Builder.Builder+import Network.API.Builder.Routes+import Network.API.Builder.Send++import Data.Aeson+import Data.ByteString.Lazy (ByteString)+import qualified Network.HTTP.Client as HTTP+import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  describe "Sendable" $ do++    describe "()" $ do+      it "should be able to construct a basic request" $ do+        case send exampleBuilder (Route ["api.json"] [] "GET") () of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "GET"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` ""+            HTTP.path req `shouldBe` "/api.json"+          Nothing -> expectationFailure "req construction failed"+      it "should be able to construct request with query params" $ do+        case send exampleBuilder (Route ["api.json"] ["a_query" =. True] "GET") () of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "GET"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` "?a_query=true"+            HTTP.path req `shouldBe` "/api.json"+          Nothing -> expectationFailure "req construction failed"+      it "should be able to construct a POST request" $ do+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") () of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "POST"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` ""+            HTTP.path req `shouldBe` "/api.json"+            case HTTP.requestBody req of+              HTTP.RequestBodyBS "query=false" -> return ()+              _ -> expectationFailure "incorrect POST body"+          Nothing -> expectationFailure "req construction failed"++    describe "PostQuery" $ do+      it "should be able to construct a POST request with no body" $ do+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") PostQuery of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "POST"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` "?query=false"+            HTTP.path req `shouldBe` "/api.json"+            case HTTP.requestBody req of+              HTTP.RequestBodyLBS "" -> return ()+              _ -> expectationFailure "incorrect POST body"+          Nothing -> expectationFailure "req construction failed"++    describe "Value" $ do+      it "should be able to construct a POST request with the correct JSON body" $ do+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") $ object ["hello" .= Null] of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "POST"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` "?query=false"+            HTTP.path req `shouldBe` "/api.json"+            case HTTP.requestBody req of+              HTTP.RequestBodyLBS "{\"hello\":null}" -> return ()+              _ -> expectationFailure "incorrect POST body"+          Nothing -> expectationFailure "req construction failed"++    describe "ByteString" $ do+      it "should be able to construct a POST request with the correct ByteString body" $ do+        case send exampleBuilder (Route ["api.json"] ["query" =. False] "POST") ("hello world" :: ByteString) of+          Just req -> do+            HTTP.secure req `shouldBe` True+            HTTP.host req `shouldBe` "example.com"+            HTTP.method req `shouldBe` "POST"+            HTTP.port req `shouldBe` 443+            HTTP.queryString req `shouldBe` "?query=false"+            HTTP.path req `shouldBe` "/api.json"+            case HTTP.requestBody req of+              HTTP.RequestBodyLBS "hello world" -> return ()+              _ -> expectationFailure "incorrect POST body"+          Nothing -> expectationFailure "req construction failed"++exampleBuilder :: Builder+exampleBuilder = basicBuilder "example" "https://example.com"