OrchestrateDB (empty) → 1.0.0.0
raw patch · 9 files changed
+902/−0 lines, 9 filesdep +HTTPdep +aesondep +basesetup-changed
Dependencies added: HTTP, aeson, base, bytestring, http-conduit, http-types, lifted-base, random
Files
- LICENSE +30/−0
- OrchestrateDB.cabal +88/−0
- README.md +22/−0
- Setup.hs +2/−0
- examples/Test.hs +74/−0
- examples/Test2.hs +41/−0
- src/Orchestrate.hs +151/−0
- src/Orchestrate/REST.hs +386/−0
- src/Orchestrate/Types.hs +108/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Adrian Dawid++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 Adrian Dawid 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.
+ OrchestrateDB.cabal view
@@ -0,0 +1,88 @@+-- Initial OrchestrateDB.cabal generated by cabal init. For further+-- documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name: OrchestrateDB++-- The package version. See the Haskell package versioning policy (PVP)+-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 1.0.0.0++-- A short (one-line) description of the package.+synopsis: Unofficial Haskell Client Library for the Orchestrate.io API++-- A longer description of the package.+description: An implementation of most of the functions of the Orchestrate.io REST API in Haskell.+ To get started, see the documentation for the Orchestrate and Orchestrate.REST module below.+ Because this module does use 'Data.Aeson' for parsing and encoding JSON you might want to take a look at it's docs too.+ For some working examples check out the "examples/Test.hs" file and the documentation.++-- The license under which the package is released.+license: BSD3++-- The file containing the license text.+license-file: LICENSE++-- The package author(s).+author: Adrian Dawid++-- An email address to which users can send suggestions, bug reports, and+-- patches.+maintainer: adriandwd@gmail.com++-- A copyright notice.+copyright: Copyright (c) 2015, Adrian Dawid++category: Database+homepage: https://github.com/dwd31415/Haskell-OrchestrateDB+bug-reports: https://github.com/dwd31415/Haskell-OrchestrateDB/issues+build-type: Simple++-- Extra files to be distributed with the package, such as examples or a+-- README.+extra-source-files: README.md++-- Constraint on the version of Cabal needed to build this package.+cabal-version: >=1.10+++library+ -- Modules exported by the library.+ exposed-modules: Orchestrate++ -- Modules included in this library but not exported.+ other-modules: Orchestrate.REST, Orchestrate.Types++ -- LANGUAGE extensions used by modules in this package.+ -- other-extensions:++ -- Other library packages from which modules are imported.+ build-depends: base >=4.7 && <4.8, HTTP >= 4000, http-conduit >= 2.1, http-types >= 0.8, bytestring >= 0.10+ ,lifted-base >= 0.2, aeson++ -- Directories containing source files.+ hs-source-dirs: src++ -- Base language which the package is written in.+ default-language: Haskell2010++Test-Suite tests-example1+ type: exitcode-stdio-1.0+ main-is: Test.hs+ hs-source-dirs: src,examples+ default-language: Haskell2010+ build-depends: base >=4.4 && <4.8, HTTP >= 4000, http-conduit >= 2.1, http-types >= 0.8, bytestring >= 0.10+ ,lifted-base >= 0.2, aeson, random+++Test-Suite test-sherlock+ type: exitcode-stdio-1.0+ main-is: Test2.hs+ hs-source-dirs: src,examples+ default-language: Haskell2010+ build-depends: base >=4.4 && <4.8, HTTP >= 4000, http-conduit >= 2.1, http-types >= 0.8, bytestring >= 0.10+ ,lifted-base >= 0.2, aeson, random
+ README.md view
@@ -0,0 +1,22 @@+# Haskell-OrchestrateDB [](https://travis-ci.org/dwd31415/Haskell-OrchestrateDB)+Unofficial Haskell Client Library for the Orchestrate.io API++# How to install this package?++The most convenient way of installing this package is via Hackage, just click on the Hackage badge to get to the project's hackage site.+Alternatively, you could also just clone this repository and run the+```+cabal install+```+command in it.++# Where to find the documentation?++Haddock docs can be found on the hackage project site, but if you want to, you can also build them on your local machine by running+```+ cabal haddock --html+```++# Tests?++Some tests are run automatically after each commit over the Travis-CI service. If the Travis-CI badge says "build passing" everything should be fine.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ examples/Test.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}++module Main where++import Control.Monad+import Data.Aeson+import Data.Aeson.Types+import Data.Maybe+import GHC.Generics+import qualified Orchestrate as DB+import System.Exit (exitFailure)+import System.Random++data TestRecord = TestRecord+ { string :: String+ , number :: Int+ } deriving (Show,Read,Generic,Eq)++instance FromJSON TestRecord+instance ToJSON TestRecord++reachTest :: Integer -> IO ()+reachTest number = do+ putStrLn $ "Completed test number " ++ show number++main :: IO ()+main = do+ configTxt <- readFile "examples/config.txt"+ randomNumber <- randomIO :: IO Integer+ let config = read configTxt :: (String,String)+ let dbApplication = DB.createStdApplication "TestApp" (fst config)+ let dbCollection = DB.createStdCollection (snd config)+ let testRecord1 = TestRecord {string = "You may delay, but time will not.(Benjamin Franklin)",number = 903}+ let testRecord2 = TestRecord {string = "Two things are infinite: the universe and human stupidity; and I am not sure about the universe.(Albert Einstein)",number = 913}+ res1 <- DB.validateApplication dbApplication+ unless res1 exitFailure+ reachTest 1+ let testKey = "hello-world" ++ show randomNumber+ res2 <- DB.orchestrateCollectionPut dbApplication dbCollection testKey testRecord1+ unless res2 exitFailure+ reachTest 2+ res3 <- DB.orchestrateCollectionPut dbApplication dbCollection "irrelevant" testRecord1+ unless res3 exitFailure+ reachTest 3+ res4 <- DB.orchestrateCollectionPutWithoutKey dbApplication dbCollection testRecord2+ unless res4 exitFailure+ reachTest 4+ shouldBeTestRecord1Maybe <- DB.orchestrateCollectionGet dbApplication dbCollection testKey :: IO (Maybe TestRecord)+ let shouldBeTestRecord1 = fromJust shouldBeTestRecord1Maybe+ unless (shouldBeTestRecord1 == testRecord1) exitFailure+ reachTest 5+ res5 <- DB.orchestrateCollectionDeleteKey dbApplication dbCollection "irrelevant"+ unless res5 exitFailure+ reachTest 6+ res6Maybe <- DB.orchestrateCollectionSearch dbApplication dbCollection "You may delay,"+ let (res6,hasNextPage) = fromJust res6Maybe+ unless (checkIfResSixIsCorrect res6) exitFailure+ reachTest 7+ res7Maybe <- DB.orchestrateCollectionList dbApplication dbCollection 100+ let res7 = fromJust res7Maybe+ unless (res7 /= []) exitFailure+ reachTest 8++checkIfResSixIsCorrect :: [Object] -> Bool+checkIfResSixIsCorrect (first:rest) = do+ let num = flip parseMaybe first $ \obj -> obj .: "number" :: Parser Int+ isJust num &&+ (if fromJust num == 903 then checkIfResSixIsCorrect rest else+ False)++checkIfResSixIsCorrect [] = True
+ examples/Test2.hs view
@@ -0,0 +1,41 @@+ {-# LANGUAGE DeriveGeneric #-}+module Main where++import GHC.Generics+import Data.Aeson+import qualified Orchestrate as DB++data SherlockHolmesCase = SherlockHolmesCase {+ title :: String,+ typeOfCrime :: String,+ shortStory :: Bool,+ index :: Integer,+ solved :: Bool+} deriving (Show,Read,Generic,Eq)++ {-This automatically creates both a JSON parser and a JSON encoder based on the data type SherlockHolmesCase, if you however+want the property names in JSON and haskell to differ, you will have to write your own parser and you own encoder.+ Please check out the 'Data.Aeson' documentation for information on how to do that. -}+instance FromJSON SherlockHolmesCase+instance ToJSON SherlockHolmesCase++main :: IO ()+main = do+ configTxt <- readFile "examples/config.txt"+ let config = read configTxt :: (String,String)+ -- Create an application record. Oh, BTW, in case you planned to store your api-key in the source code, it is a very very bad idea.+ let dbApplcation = DB.createStdApplication "your.orchestrate.application.name " (fst config)+ -- Create an collection record. This collection does not have to exist, if it does not, it will be created as soon as you try to store something in it.+ let dbCollection = DB.createStdCollection "TheCasebookOfSherlockHolmes "+ let aStudyInScarlet = SherlockHolmesCase {+ title = "A Study in Scarlet ",+ typeOfCrime = "Murder ",+ shortStory = False,+ index = 0,+ solved = True+ }+ -- Store a haskell value in the collection we created earlier. The "withOutKey " lets the server generate the key, so no key must be specified.+ didItWork <- DB.orchestrateCollectionPutWithoutKey dbApplcation dbCollection aStudyInScarlet+ if didItWork+ then putStrLn "Invictus maneo!"+ else putStrLn "Something is rotten in the state of Denmark!"
+ src/Orchestrate.hs view
@@ -0,0 +1,151 @@+{-|+Module : Orchestrate+Copyright : (c) Adrian Dawid 2015+License : BSD3+Maintainer : adriandwd@gmail.com+Stability : stable++This library implements most parts of the Orchestrate.io API in Haskell. It provides a convenient way of accessing these parts of the REST API:++ * Validate API Keys++ * List Key/Values++ * Create Key/Value++ * Create Key/Value with server-generated key++ * Update Key/Value++ * Retrieve Value for Key++ * Delete Collection(s)++ * Query Collection++NOTE: You can also use 'Network.Curl', if you need one of the currently unsupported API functions, but you would have to parse all response bodies yourself if you do that.++=How to use this library?+Using this library is pretty straightforward, just import the Orchestrate module like this:++@+import qualified Orchestrate as DB+@++Once you have it, you can use all the supported API functions(Check out the documentation on 'Orchestrate.REST' for detailed information).+One important thing you need to remeber when working with this library is, that it uses 'Data.Aeson' under the hood, so if you want to+store your Haskell types in an Orchestrate database, 'Data.Aseon' must know how convert them into JSON. The best way to achive that, is+to use the 'DeriveGeneric' Langauge extension.++Here is an example of how to store a haskell value in an Orchestrate Collection:++@+\{\-\# LANGUAGE DeriveGeneric \#\-\}+module Main where++import GHC.Generics+import Data.Aeson+import qualified Orchestrate as DB++data SherlockHolmesCase = SherlockHolmesCase {+ title :: String,+ typeOfCrime :: String,+ shortStory :: Bool,+ index :: Integer,+ solved :: Bool+} deriving (Show,Read,Generic,Eq)++\{\-This automatically creates both a JSON parser and a JSON encoder based on the data type SherlockHolmesCase, if you however+want the property names in JSON and haskell to differ, you will have to write your own parser and you own encoder.+ Please check out the 'Data.Aeson' documentation for information on how to do that.\-\}+instance FromJSON SherlockHolmesCase+instance ToJSON SherlockHolmesCase++main :: IO ()+main = do+ -- Create an application record. Oh, BTW, in case you planned to store your api-key in the source code, it is a very very bad idea.+ let dbApplcation = DB.createStdApplication \"your.orchestrate.application.name\" \"one-of-your-api-keys\"+ -- Create an collection record. This collection does not have to exist, if it does not, it will be created as soon as you try to store something in it.+ let dbCollection = DB.createStdCollection \"TheCasebookOfSherlockHolmes\"+ let aStudyInScarlet = SherlockHolmesCase {+ title = \"A Study in Scarlet\",+ typeOfCrime = \"Murder\",+ shortStory = False,+ index = 0,+ solved = True+ }+ -- Store a haskell value in the collection we created earlier. The \"withOutKey\" lets the server generate the key, so no key must be specified.+ didItWork <- DB.orchestrateCollectionPutWithoutKey dbApplcation dbCollection aStudyInScarlet+ if didItWork+ then putStrLn "Invictus maneo!"+ else putStrLn "Something is rotten in the state of Denmark!"+@++If this application does say \"Invictus maneo!\", you should get this result if you LIST the \"TheCasebookOfSherlockHolmes\" collection:++@+"count" : 1,+ "results" : [ {+ "path" : {+ "collection" : "TheCasebookOfSherlockHolmes ",+ "kind" : "item",+ "key" : "0b8b506cf0204ce9",+ "ref" : "--------------",+ "reftime" : --------------+ },+ "value" : {+ "solved" : true,+ "shortStory" : false,+ "title" : "A Study in Scarlet ",+ "index" : 0,+ "typeOfCrime" : "Murder "+ },+ "reftime" : --------------+ } ]+@++If you don't get that result, you either have run the example more than once, it did not return "Invictus maneo", or something really strange is going on.+If you are fairly certain, that something strange is going on, and it is most likely my fault and you might want to open an issue at github.++=How to use the "cabal test" command+Testing applications which depend on some REST api is not easy, and therfore the cabal tests will fail by default. They just will not be able to authenticate, if you+want to run the tests on your local machine, please create a file called "examples/config.txt" and fill it according to this scheme:++@+("a valid api key","something funny(it will be used as a name for the test collection)")+@++If you are asking yourself now, if there really is no way of checking that this library works, before installing it, there is nothing to worry about.+You should consult the travis-ci server(<https://travis-ci.org/dwd31415/Haskell-OrchestrateDB>) on the state of the project, it does not only check wether or not ther library can be built, but also runs the tests with an valid api key.+-}+module Orchestrate(Orchestrate.Types.OrchestrateCollection(..),+ Orchestrate.Types.OrchestrateApplication(..),+ Orchestrate.REST.validateApplication,+ Orchestrate.REST.orchestrateCollectionGet,+ Orchestrate.REST.orchestrateCollectionPut,+ Orchestrate.REST.orchestrateCollectionPutWithoutKey,+ Orchestrate.REST.orchestrateCollectionDelete,+ Orchestrate.REST.orchestrateCollectionDeleteKey,+ Orchestrate.REST.orchestrateCollectionSearch,+ Orchestrate.REST.orchestrateCollectionSearchWithOffset,+ Orchestrate.REST.orchestrateCollectionList,+ createStdCollection,+ createStdApplication) where++import qualified Orchestrate.REST+import qualified Orchestrate.Types++createStdApplication :: String -> String -> Orchestrate.Types.OrchestrateApplication+-- ^Creates an application record with the std.(\"https://api.orchestrate.io/v0\") enpoint.+createStdApplication name api_key =+ Orchestrate.Types.OrchestrateApplication {+ Orchestrate.Types.applicationName = name,+ Orchestrate.Types.apiKey = api_key,+ Orchestrate.Types.httpsEndpoint = "https://api.orchestrate.io/v0"+ }++createStdCollection :: String -> Orchestrate.Types.OrchestrateCollection+-- ^Creates an collection record.+createStdCollection name = Orchestrate.Types.OrchestrateCollection {+ Orchestrate.Types.collectionName = name+}
+ src/Orchestrate/REST.hs view
@@ -0,0 +1,386 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++{-|+Module : Orchestrate.REST+Description : All functions which interact with the Orchestrate.io REST API.+Copyright : (c) Adrian Dawid 2015+License : BSD3+Maintainer : adriandwd@gmail.com+Stability : stable++This module conatins all the functions which interact with the Orchestrate.io REST API.+Right now these actions are supported:++ * Validate API Keys++ * List Key/Values++ * Create Key/Value++ * Create Key/Value with server-generated key++ * Update Key/Value++ * Retrieve Value for Key++ * Delete Collection(s)++ * Query Collection++-}+module Orchestrate.REST+ (+ validateApplication,+ orchestrateCollectionPutWithoutKey,+ orchestrateCollectionDelete,+ orchestrateCollectionGet,+ orchestrateCollectionPut,+ orchestrateCollectionDeleteKey,+ orchestrateCollectionSearch,+ orchestrateCollectionSearchWithOffset,+ orchestrateCollectionList+ ) where++import Network.HTTP.Conduit+import Network.HTTP.Types.Status++import qualified Control.Exception.Lifted as X+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy as BSLazy++import Data.Aeson+import Data.Aeson.Types+import Data.Maybe+import Orchestrate.Types++validateApplication :: OrchestrateApplication -> IO Bool+-- ^The 'validateApplication' function validates your API key,+-- by making an authenticated HEAD request to the endpoint specified in the 'OrchestrateApplication' record.+-- The function returns False when the key is invalid or no connection with the endpoint could be established.+validateApplication application = do+ let api_key = apiKey application+ if api_key == ""+ then return False+ else do+ let url = httpsEndpoint application+ case parseUrl url of+ Nothing -> return False+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = applyBasicAuth (B.pack $ apiKey application) "" unsecuredRequest+ let reqHead = request {+ method = "HEAD",+ secure = True }+ resOrException <- X.try (http reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == ok200+ then return True+ else return False+ Left (_::X.SomeException) -> return False++orchestrateCollectionList :: OrchestrateApplication -> OrchestrateCollection -> Integer -> IO (Maybe [Object])+-- ^ The 'orchestrateCollectionList' function lists the contents of the specified collection, by making GET request to the \/$collection?limit=$limit endpoint.+-- For more information check out the Orchestrate.io API docs: <https://orchestrate.io/docs/apiref#keyvalue-list>.+-- If connecting to the api fails, or the api key stored in the application record is invlaid, 'Nothing' is returned.+-- Otherwise an array of the type 'Object'(see the documentation of 'Data.Aeson' for more information) is returned, it+-- contains the values from the HTTP response(see <https://orchestrate.io/docs/apiref#keyvalue-list> for an example of how the response looks like in JSON).+--+-- = Example:+-- @+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- dbContents <- DB.orchestrateCollectionList dbApplication dbCollection 10+-- @+orchestrateCollectionList application collection limit = do+ let api_key = apiKey application+ if api_key == ""+ then return Nothing+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "?limit=" ++ show limit+ case parseUrl url of+ Nothing -> return Nothing+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "GET",+ secure = True }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (httpLbs reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == ok200+ then do+ let resBody = responseBody res+ let results = fromMaybe [] (parseListResponseBody resBody)+ return $ Just results++ else return Nothing+ Left (_::X.SomeException) -> return Nothing++-- KEY/VALUE++orchestrateCollectionPutWithoutKey :: ToJSON obj => OrchestrateApplication -> OrchestrateCollection -> obj -> IO Bool+-- ^ The 'orchestrateCollectionPutWithoutKey' function stores a Haskell value(with a 'ToJSON' instance) in an Orchestrate.io database.+-- It does so by making a POST request to the \/$collection endpoint(Offical API docs:<https://orchestrate.io/docs/apiref#keyvalue-post>).+-- This function does not need a user specified key, because it uses a server-generated key, if you want to know the key+-- use 'orchestrateCollectionPut' instead of this function.+--+-- = Example:+-- @+-- data TestRecord = TestRecord+-- { string :: String+-- , number :: Int+-- } deriving (Show,Read,Generic,Eq)+--+-- instance FromJSON TestRecord+-- instance ToJSON TestRecord+--+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- let testRecord = TestRecord {string = "You may delay, but time will not!",number = 903}+-- _ <- DB.orchestrateCollectionPutWithoutKey dbApplication dbCollection testRecord+-- @+orchestrateCollectionPutWithoutKey application collection object = do+ let objAsJson = encode object+ let api_key = apiKey application+ if api_key == ""+ then return False+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection+ case parseUrl url of+ Nothing -> return False+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "POST",+ secure = True,+ requestHeaders = [("Content-Type", "application/json")],+ requestBody = RequestBodyBS $ BSLazy.toStrict objAsJson }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (http reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == status201+ then return True+ else return False+ Left (_::X.SomeException) -> return False++orchestrateCollectionPut :: ToJSON obj => OrchestrateApplication -> OrchestrateCollection -> String -> obj -> IO Bool+-- ^ The 'orchestrateCollectionPut' function stores a Haskell value(with a 'ToJSON' instance) in an Orchestrate.io database.+-- It does so by making a PUT request to the \/$collection\/$key endpoint(Offical API docs:<https://orchestrate.io/docs/apiref#keyvalue-put>).+-- In order to upload a Haskell Value to the database, it must have an instance of 'ToJSON' because this+-- client library uses 'Data.Aeson' to convert Haskel Values to JSON, which is required by Orchestrate.io.+--+-- = Example:+-- @+-- data TestRecord = TestRecord+-- { string :: String+-- , number :: Int+-- } deriving (Show,Read,Generic,Eq)+--+-- instance FromJSON TestRecord+-- instance ToJSON TestRecord+--+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- let testRecord = TestRecord {string = "You may delay, but time will not!",number = 903}+-- _ <- DB.orchestrateCollectionPutWithoutKey dbApplication dbCollection "KEY" testRecord+-- @+orchestrateCollectionPut application collection key object = do+ let objAsJson = encode object+ let api_key = apiKey application+ if api_key == ""+ then return False+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "/" ++ key+ case parseUrl url of+ Nothing -> return False+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "PUT",+ secure = True,+ requestHeaders = [("Content-Type", "application/json")],+ requestBody = RequestBodyBS $ BSLazy.toStrict objAsJson }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (http reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == status201+ then return True+ else return False+ Left (_::X.SomeException) -> return False++orchestrateCollectionGet :: FromJSON res => OrchestrateApplication -> OrchestrateCollection -> String -> IO (Maybe res)+-- ^ The 'orchestrateCollectionGet' function request a value from an Orchestrate.io database, and tries to convert it to the specified Haskell type,+-- if either gettings the value from the database or converting it to the Haskell type fails 'Nothing' is returned.+-- The value is requested by making a GET request to the \/$collection\/$key endpoint(Offical documentation:<https://orchestrate.io/docs/apiref#keyvalue-get>)+--+-- = Example:+-- @+-- data TestRecord = TestRecord+-- { string :: String+-- , number :: Int+-- } deriving (Show,Read,Generic,Eq)+--+-- instance FromJSON TestRecord+-- instance ToJSON TestRecord+--+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- let testRecord = TestRecord {string = "You may delay, but time will not!",number = 903}+-- dbValue <- DB.orchestrateCollectionGet dbApplication dbCollection "KEY" :: IO (Maybe TestRecord)+-- @+orchestrateCollectionGet application collection key = do+ let api_key = apiKey application+ if api_key == ""+ then return Nothing+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "/" ++ key+ case parseUrl url of+ Nothing -> return Nothing+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "GET",+ secure = True }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (httpLbs reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == ok200+ then do+ let resBody = responseBody res+ let jsonObject = decode resBody+ return jsonObject+ else return Nothing+ Left (_::X.SomeException) -> return Nothing++orchestrateCollectionDeleteKey :: OrchestrateApplication -> OrchestrateCollection -> String -> IO Bool+-- ^ The 'orchestrateCollectionDeleteKey' function deletes a value from an Orchestrate.io database.+-- This is done by making a DELETE request to the \/$collection\/$key endpoint(Offical documentation:<https://orchestrate.io/docs/apiref#keyvalue-delete>)+--+-- = Example:+-- @+-- data TestRecord = TestRecord+-- { string :: String+-- , number :: Int+-- } deriving (Show,Read,Generic,Eq)+--+-- instance FromJSON TestRecord+-- instance ToJSON TestRecord+--+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- _ <- DB.orchestrateCollectionKey dbApplication dbCollection "KEY"+-- @+orchestrateCollectionDeleteKey application collection key = do+ let api_key = apiKey application+ if api_key == ""+ then return False+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "/" ++ key ++ "?force=true"+ case parseUrl url of+ Nothing -> return False+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "DELETE",+ secure = True }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (http reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == status204+ then return True+ else return False+ Left (_::X.SomeException) -> return False++orchestrateCollectionDelete :: OrchestrateApplication -> OrchestrateCollection -> IO Bool+-- ^ The 'orchestrateCollectionDelete' function deletes a collection from an Orchestrate.io application.+-- This is done by making a DELETE request to the \/$collection endpoint(Offical documentation:<https://orchestrate.io/docs/apiref#collections-delete>)+--+-- = Example:+-- @+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- _ <- DB.orchestrateCollectionDelete dbApplication dbCollection+-- @+orchestrateCollectionDelete application collection = do+ let api_key = apiKey application+ if api_key == ""+ then return False+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "?force=true"+ case parseUrl url of+ Nothing -> return False+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "DELETE",+ secure = True }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (http reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == status204+ then return True+ else return False+ Left (_::X.SomeException) -> return False++-- SEARCH++orchestrateCollectionSearch :: OrchestrateApplication -> OrchestrateCollection -> String -> IO (Maybe([Object],Bool))+-- ^ Please see 'orchestrateCollectionSearchWithOffset' for more information. This function just calls it without an offset and with a limit of 10.+orchestrateCollectionSearch application collection query = orchestrateCollectionSearchWithOffset application collection query 0 10+++orchestrateCollectionSearchWithOffset :: OrchestrateApplication -> OrchestrateCollection -> String -> Integer -> Integer -> IO (Maybe([Object],Bool))+-- ^ The 'orchestrateCollectionSearchWithOffset' function searches for the query in the database and returns an array+-- of the type \"'Maybe' ['Object']\". Nothing is returned when establishing a connection or authenticating failed. The function uses the+-- SEARCH method of the Orchestrate.io API (Offical documentation:<https://orchestrate.io/docs/apiref#search-collection>)+-- , but automatically parsers the response. It returns a tupel of the type ('Maybe'(['Object'],'Bool')), the boolean indicates wether or not+-- more results are availble on the server. If that is true, the function should be called again with an increased offset, until (Just _,False) is returned.+--+-- = Example:+-- @+-- dbSearchResults query num =+-- let results = DB.orchestrateCollectionSearchWithOffset query num (num+10)+-- let currentResults = fromJust $ fst results+-- if snd results+-- then currentResults:(dbSearchResults query (num+10))+-- else currentResults+--+-- let dbApplication = DB.createStdApplication \"APPLICATION_NAME\" \"API_KEY\"+-- let dbCollection = DB.createStdCollection \"COLLECTION_NAME\"+-- let completeDBSearchResults = dbSearchResults "QUERY" 0+-- @+orchestrateCollectionSearchWithOffset application collection query offset limit = do+ let api_key = apiKey application+ if api_key == ""+ then return Nothing+ else do+ let url = httpsEndpoint application ++ "/" ++ collectionName collection ++ "?query=" ++ query ++ "&limit=" ++ show limit ++ "&offset=" ++ show offset+ case parseUrl url of+ Nothing -> return Nothing+ Just unsecuredRequest -> withManager $ \manager -> do+ let request = unsecuredRequest {+ method = "GET",+ secure = True }+ let reqHead = applyBasicAuth (B.pack $ apiKey application) "" request+ resOrException <- X.try (httpLbs reqHead manager)+ case resOrException of+ Right res -> if responseStatus res == ok200+ then do+ let resBody = responseBody res+ let (results,count) = fromMaybe ([],0) (parseQueryResponseBody resBody)+ return $ Just (results,count > (fromIntegral (length results) + offset))++ else return Nothing+ Left (_::X.SomeException) -> return Nothing+++-- HELPERS++parseListResponseBody :: BSLazy.ByteString -> Maybe [Object]+parseListResponseBody resBodyRaw = do+ result <- decode resBodyRaw+ results <- flip parseMaybe result $ \obj -> obj .: "results" :: Parser [OrchestrateListResult]+ return $ resultValuesAsList results++parseQueryResponseBody :: BSLazy.ByteString -> Maybe ([Object],Integer)+parseQueryResponseBody resBodyRaw = do+ result <- decode resBodyRaw+ (count,results) <- flip parseMaybe result $ \obj -> do+ count <- obj .: "total_count" :: Parser Integer+ results <- obj .: "results" :: Parser [OrchestrateQueryResult]+ return (count,results)+ return (resultValuesAsList results,count)
+ src/Orchestrate/Types.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}++{-|+Module : Orchestrate.Types+Description : Haskell types for various JSON patterns.+Copyright : (c) Adrian Dawid 2015+License : BSD3+Maintainer : adriandwd@gmail.com+Stability : stable++This module conatins Haskell types which represent all necessary JSON patterns used by the Orchestrate.io API.+-}+module Orchestrate.Types+ ( OrchestrateApplication(..),+ OrchestrateCollection(..),+ OrchestrateQueryResult(..),+ OrchestratePath (..),+ OrchestrateListResult (..),+ resultValuesAsList) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad+import Data.Aeson+import GHC.Generics++{-|+A data type, that represents an Orchestrate application. It stores an api key (generated online) and a https-endpoint.+-}+data OrchestrateApplication = OrchestrateApplication {+ applicationName :: String,+ apiKey :: String,+ httpsEndpoint :: String+}++{-|+Represents a collection inside an OrchestrateApplication, it stores all data necessary to access it.+-}+data OrchestrateCollection = OrchestrateCollection {+ collectionName :: String+}++-- |TypeClass for OrchestrateQueryResult and OrchestrateListResult, it makes it possible to have one function('resultValuesAsList'), that extractes+-- the values from both of those types.+class OrchestrateIntermediateResult a where+ resultValuesAsList :: [a] -> [Object]++-- |Represents a path, as it is returned by the LIST and SEARCH function.+-- Right now it is not actually used, but it might be useful in the future.+data OrchestratePath = OrchestratePath {+ orchestratePathCollection :: String,+ orchestratePathKind :: String,+ orchestratePathKey :: String,+ orchestratePathRef :: String,+ orchestratePathReftime :: Integer+} deriving (Show,Read,Generic)++instance FromJSON OrchestratePath where+ parseJSON (Object v) = OrchestratePath <$>+ v .: "collection" <*>+ v .: "kind" <*>+ v .: "key" <*>+ v .: "ref" <*>+ v .: "reftime"+ -- A non-Object value is of the wrong type, so fail.+ parseJSON _ = mzero+instance ToJSON OrchestratePath++-- | Haskell type for the JSON scheme, that is returned by the SEARCH function.+data OrchestrateQueryResult = OrchestrateQueryResult {+ orchestrateQueryResultPath :: OrchestratePath,+ orchestrateQueryResultValue :: Object,+ orchestrateQueryResultScore :: Double,+ orchestrateQueryResultReftimeQueryResult :: Integer+} deriving (Show,Generic)++instance FromJSON OrchestrateQueryResult where+ parseJSON (Object v) = OrchestrateQueryResult <$>+ v .: "path" <*>+ v .: "value" <*>+ v .: "score" <*>+ v .: "reftime"+ -- A non-Object value is of the wrong type, so fail.+ parseJSON _ = mzero+instance ToJSON OrchestrateQueryResult+instance OrchestrateIntermediateResult OrchestrateQueryResult where+ resultValuesAsList = map orchestrateQueryResultValue+++-- | Haskell type for the JSON scheme, that is returned by the LIST function.+data OrchestrateListResult = OrchestrateListResult {+ orchestrateListResultPath :: OrchestratePath,+ orchestrateListResultValue :: Object,+ orchestrateListResultReftimeQueryResult :: Integer+} deriving (Show,Generic)++instance FromJSON OrchestrateListResult where+ parseJSON (Object v) = OrchestrateListResult <$>+ v .: "path" <*>+ v .: "value" <*>+ v .: "reftime"+ -- A non-Object value is of the wrong type, so fail.+ parseJSON _ = mzero+instance ToJSON OrchestrateListResult+instance OrchestrateIntermediateResult OrchestrateListResult where+ resultValuesAsList = map orchestrateListResultValue