apioiaf-client (empty) → 0.1.0.0
raw patch · 5 files changed
+303/−0 lines, 5 filesdep +aesondep +apioiaf-clientdep +basesetup-changed
Dependencies added: aeson, apioiaf-client, base, bytestring, lens, wreq
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- apioiaf-client.cabal +42/−0
- src/IceAndFire.hs +227/−0
- test/Spec.hs +2/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Author name here (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ apioiaf-client.cabal view
@@ -0,0 +1,42 @@+name: apioiaf-client+version: 0.1.0.0+synopsis: Consumer library for anapioficeandfire.com+description: + .+ A wrapper library for consuming the data provided by <anapioficeandfire.com>+ .+ See README for API details <https://github.com/kberger/anapioficeandfire-haskell#readme>+homepage: https://github.com/kberger/anapioficeandfire-haskell#readme+license: BSD3+license-file: LICENSE+author: Karl Berger+maintainer: krab.berger@gmail.com+copyright: 2016 Karl Berger+category: Web+build-type: Simple+-- extra-source-files:+cabal-version: >=1.10++library+ ghc-options: -Wall+ hs-source-dirs: src+ exposed-modules: IceAndFire+ build-depends: base >= 4.7 && < 5+ , wreq+ , aeson+ , lens+ , bytestring+ default-language: Haskell2010++test-suite apioiaf-client-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends: base+ , apioiaf-client+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ default-language: Haskell2010++source-repository head+ type: git+ location: https://github.com/kberger/anapioficeandfire-haskell
+ src/IceAndFire.hs view
@@ -0,0 +1,227 @@+{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}++module IceAndFire+ ( Book(..)+ , Character(..)+ , House(..)+ , getBookById+ , getCharacterById+ , getCharactersByName+ , getCharactersByCulture+ , getCharactersByGender+ , getHouseById+ , getHouseByName+ ) where++import Network.Wreq+import Data.Aeson+import Data.Aeson.Types+import Data.ByteString.Char8 (unpack)+import Control.Lens++data Book = Book+ { bookUrl :: String+ , bookName :: String+ , isbn :: String+ , authors :: [String]+ , numberOfPages :: Integer+ , publisher :: String+ , country :: String+ , mediaType :: String+ , released :: String+ , characters :: [String]+ , povCharacters :: [String] + } deriving Show++instance FromJSON Book where+ parseJSON (Object v) = Book <$>+ v .: "url" <*>+ v .: "name" <*>+ v .: "isbn" <*>+ v .: "authors" <*>+ v .: "numberOfPages" <*>+ v .: "publisher" <*>+ v .: "country" <*>+ v .: "mediaType" <*>+ v .: "released" <*>+ v .: "characters" <*>+ v .: "povCharacters"+ parseJSON invalid = typeMismatch "Book" invalid++data Character = Character+ { charUrl :: String+ , charName :: String+ , gender :: String+ , culture :: String+ , born :: String+ , died :: String+ , charTitles :: [String]+ , aliases :: [String]+ , father :: String+ , mother :: String+ , spouse :: String+ , allegiances :: [String]+ , books :: [String]+ , povBooks :: [String]+ , tvSeries :: [String]+ , playedBy :: [String]+ } deriving Show++instance FromJSON Character where+ parseJSON (Object v) = Character <$>+ v .: "url" <*>+ v .: "name" <*>+ v .: "gender" <*>+ v .: "culture" <*>+ v .: "born" <*>+ v .: "died" <*>+ v .: "titles" <*>+ v .: "aliases" <*>+ v .: "father" <*>+ v .: "mother" <*>+ v .: "spouse" <*>+ v .: "allegiances" <*>+ v .: "books" <*>+ v .: "povBooks" <*>+ v .: "tvSeries" <*>+ v .: "playedBy"+ parseJSON invalid = typeMismatch "Character" invalid++data House = House+ { houseUrl :: String+ , houseName :: String+ , region :: String+ , coatOfArms :: String+ , words :: String+ , houseTitles :: [String]+ , seats :: [String]+ , currentLord :: String+ , heir :: String+ , overlord :: String+ , founded :: String+ , founder :: String+ , diedOut :: String+ , ancestralWeapons :: [String]+ , cadetBranches :: [String]+ , swornMembers :: [String]+ } deriving Show++instance FromJSON House where+ parseJSON (Object v) = House <$>+ v .: "url" <*>+ v .: "name" <*>+ v .: "region" <*>+ v .: "coatOfArms" <*>+ v .: "words" <*>+ v .: "titles" <*>+ v .: "seats" <*>+ v .: "currentLord" <*>+ v .: "heir" <*>+ v .: "overlord" <*>+ v .: "founded" <*>+ v .: "founder" <*>+ v .: "diedOut" <*>+ v .: "ancestralWeapons" <*>+ v .: "cadetBranches" <*>+ v .: "swornMembers"+ parseJSON invalid = typeMismatch "House" invalid++baseUrl :: String+baseUrl = "http://www.anapioficeandfire.com/api"++-- | Get Book by id number+getBookById :: Int -> IO (Maybe Book)+getBookById = loadSingleById "books"++-- | Get Book by name+getBookByName :: String -> IO [Book]+getBookByName bName = entityQuery "books" [("name", bName)]++-- | Get Character by id number+getCharacterById :: Int -> IO (Maybe Character)+getCharacterById = loadSingleById "characters"++-- | Get Characters by name+getCharactersByName :: String -> IO [Character]+getCharactersByName cName = entityQuery "characters" [("name", cName)]++-- | Get Character by culture+--+-- Example:+--+-- @+-- 'getCharactersByCulture' \"dothraki\"+-- @+--+-- >>> d <- getCharactersByCulture "dothraki"+-- >>> List.length d+-- 23+getCharactersByCulture :: String -> IO [Character]+getCharactersByCulture cCulture = entityQuery "characters" [("culture", cCulture)]++-- | Get Character by gender+--+-- Example:+--+-- @+-- 'getCharactersByGender' \"female\"+-- @+--+-- >>> f <- getCharactersByGender "female"+-- >>> List.length f+-- 461+getCharactersByGender :: String -> IO [Character]+getCharactersByGender cGender = entityQuery "characters" [("gender", cGender)]++-- | Get House by id number+getHouseById :: Int -> IO (Maybe House)+getHouseById = loadSingleById "houses"++-- | Get House by name+getHouseByName :: String -> IO [House]+getHouseByName hName = entityQuery "houses" [("name", hName)]++-- | Get House by region+--+-- Example:+--+-- @+-- 'getHousesByRegion' \"The Crownlands\"+-- @+--+-- >>> c <- getHousesByRegion "The Crownlands"+-- >>> List.length c+-- 49+getHousesByRegion :: String -> IO [House]+getHousesByRegion hRegion = entityQuery "houses" [("region", hRegion)]++-- | Get a House by its words+getHouseByWords :: String -> IO [House]+getHouseByWords hWords = entityQuery "houses" [("hasWords", "true"), ("words", hWords)]++loadSingleById :: (FromJSON a) => String -> Int -> IO (Maybe a)+loadSingleById entityType entityId = do+ response <- get (baseUrl ++ "/" ++ entityType ++ "/" ++ (show entityId) :: String)+ let entityJson = decode (view responseBody response)+ return entityJson++--Mainly used to control common query paramaters like pageSize+--Probably where caching should go+entityQuery :: (FromJSON a) => String -> [(String,String)] -> IO [a]+entityQuery entityType qParams = + loadFromQueryUrl (baseUrl ++ "/" ++ entityType ++ "/?pageSize=50" ++ paramsToStr qParams)+ where paramsToStr = foldl (\ acc (k,v) -> acc ++ "&" ++ k ++ "=" ++ v) ""++loadFromQueryUrl :: (FromJSON a) => String -> IO [a]+loadFromQueryUrl url = do+ response <- get url+ let entity = decode (view responseBody response)+ let unpacked = case entity of (Just []) -> []+ (Just list) -> list+ Nothing -> []+ -- Greedily consume and append 'next' links until none more+ let nextLink = (response ^? responseLink "rel" "next" . linkURL)+ let next = case nextLink of (Just linkUrl) -> loadFromQueryUrl (unpack linkUrl)+ Nothing -> return ([] :: [a])+ nextList <- next+ return (unpacked ++ nextList)
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"