packages feed

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 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 [![Build Status](https://travis-ci.org/dwd31415/Haskell-OrchestrateDB.svg?branch=master)](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