packages feed

etcd 1.0.3 → 1.0.4

raw patch · 3 files changed

+126/−45 lines, 3 filesdep +asyncPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: async

API changes (from Hackage documentation)

- Network.Etcd: instance FromJSON Error
+ Network.Etcd: wait :: Client -> Key -> IO (Maybe Node)
+ Network.Etcd: waitIndex :: Client -> Key -> Index -> IO (Maybe Node)
+ Network.Etcd: waitIndexRecursive :: Client -> Key -> Index -> IO (Maybe Node)
+ Network.Etcd: waitRecursive :: Client -> Key -> IO (Maybe Node)

Files

etcd.cabal view
@@ -1,5 +1,5 @@ name:                etcd-version:             1.0.3+version:             1.0.4 license:             OtherLicense license-file:        UNLICENSE synopsis:            Client for etcd, a highly-available key value store@@ -11,23 +11,27 @@ cabal-version:       >=1.10  +source-repository head+  type:     git+  location: git://github.com/wereHamster/etcd-hs.git++ library   default-language:  Haskell2010   hs-source-dirs:    src    exposed-modules:   Network.Etcd -  build-depends:     aeson         >= 0-  build-depends:     base          >= 4.0     && < 5-  build-depends:     bytestring    >= 0-  build-depends:     http-conduit  >= 0-  build-depends:     time          >= 0+  build-depends:     aeson+  build-depends:     base >= 4.0 && < 5+  build-depends:     bytestring+  build-depends:     http-conduit+  build-depends:     time   build-depends:     text    ghc-options:       -Wall  - test-suite test   type:              exitcode-stdio-1.0 @@ -36,6 +40,7 @@    main-is:           Test.hs +  build-depends:     async   build-depends:     MonadRandom   build-depends:     base   build-depends:     etcd
src/Network/Etcd.hs view
@@ -1,4 +1,5 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE ScopedTypeVariables #-}  {-| @@ -21,6 +22,10 @@     , get     , set     , create+    , wait+    , waitIndex+    , waitRecursive+    , waitIndexRecursive        -- * Directory operations     , createDirectory@@ -38,6 +43,7 @@ import qualified Data.Text as T import           Data.Text.Encoding import           Data.Monoid+import           Data.ByteString.Lazy (ByteString)  import           Control.Applicative import           Control.Exception@@ -45,7 +51,9 @@  import           Network.HTTP.Conduit hiding (Response, path) +import           Prelude + -- | The 'Client' holds all data required to make requests to the etcd -- cluster. You should use 'createClient' to initialize a new client. data Client = Client@@ -105,13 +113,10 @@  ------------------------------------------------------------------------------ -- | The server sometimes responds to errors with this error object.-data Error = Error+data Error = Error !Text     deriving (Show, Eq, Ord) -instance FromJSON Error where-    parseJSON _ = return Error - -- | The etcd index is a unique, monotonically-incrementing integer created for -- each change to etcd. See etcd documentation for more details. type Index = Int@@ -153,7 +158,7 @@       -- which the given key was created.      , _nodeModifiedIndex :: !Index-      -- ^ Like '_nodeCreatedIndex', but reflects when the node was laste+      -- ^ Like '_nodeCreatedIndex', but reflects when the node was last       -- changed.      , _nodeDir           :: !Bool@@ -179,9 +184,9 @@  instance FromJSON Node where     parseJSON (Object o) = Node-        <$> o .:  "key"-        <*> o .:  "createdIndex"-        <*> o .:  "modifiedIndex"+        <$> o .:? "key" .!= "/"+        <*> o .:? "createdIndex" .!= 0+        <*> o .:? "modifiedIndex" .!= 0         <*> o .:? "dir" .!= False         <*> o .:? "value"         <*> o .:? "nodes"@@ -206,12 +211,19 @@ -- A type synonym for a http response. type HR = Either Error Response +decodeResponseBody :: ByteString -> IO HR+decodeResponseBody body = do+    return $ case eitherDecode body of+        Left e  -> Left $ Error (T.pack e)+        Right n -> Right n -httpGET :: Text -> IO HR-httpGET url = do-    req  <- acceptJSON <$> parseUrl (T.unpack url)++httpGET :: Text -> [(Text, Text)] -> IO HR+httpGET url params = do+    req'  <- acceptJSON <$> parseUrl (T.unpack url)+    let req = setQueryString (map (\(k,v) -> (encodeUtf8 k, Just $ encodeUtf8 v)) params) $ req'     body <- responseBody <$> (withManager $ httpLbs req)-    return $ maybe (Left Error) Right $ decode body+    decodeResponseBody body    where     acceptHeader   = ("Accept","application/json")@@ -224,15 +236,16 @@     let req = urlEncodedBody (map (\(k,v) -> (encodeUtf8 k, encodeUtf8 v)) params) $ req'      body <- responseBody <$> (withManager $ httpLbs $ req { method = "PUT" })-    return $ maybe (Left Error) Right $ decode body+    decodeResponseBody body + httpPOST :: Text -> [(Text, Text)] -> IO HR httpPOST url params = do     req' <- parseUrl (T.unpack url)     let req = urlEncodedBody (map (\(k,v) -> (encodeUtf8 k, encodeUtf8 v)) params) $ req'      body <- responseBody <$> (withManager $ httpLbs $ req { method = "POST" })-    return $ maybe (Left Error) Right $ decode body+    decodeResponseBody body   -- | Issue a DELETE request to the given url. Since DELETE requests don't have@@ -241,7 +254,7 @@ httpDELETE url params = do     req  <- parseUrl $ T.unpack $ url <> (asQueryParams params)     body <- responseBody <$> (withManager $ httpLbs $ req { method = "DELETE" })-    return $ maybe (Left Error) Right $ decode body+    decodeResponseBody body    where     asQueryParams [] = ""@@ -252,10 +265,10 @@ -- | Run a low-level HTTP request. Catch any exceptions and convert them into -- a 'Left Error'. runRequest :: IO HR -> IO HR-runRequest a = catch a (ignoreExceptionWith (return $ Left Error))+runRequest a = catch a (\(e :: SomeException) -> return $ Left $ Error $ T.pack $ show e) -ignoreExceptionWith :: a -> SomeException -> a-ignoreExceptionWith a _ = a+runRequest' :: IO HR -> IO (Maybe Node)+runRequest' m = either (const Nothing) (Just . _resNode) <$> runRequest m   -- | Encode an optional TTL into a param pair.@@ -285,41 +298,68 @@  -} +waitParam :: (Text, Text)+waitParam = ("wait","true") +waitRecursiveParam :: (Text, Text)+waitRecursiveParam = ("recursive","true")++waitIndexParam :: Index -> (Text, Text)+waitIndexParam i = ("waitIndex", (T.pack $ show i))++ -- | Get the node at the given key. get :: Client -> Key -> IO (Maybe Node)-get client key = do-    hr <- runRequest $ httpGET $ keyUrl client key-    case hr of-        Left _ -> return Nothing-        Right res -> return $ Just $ _resNode res+get client key =+    runRequest' $ httpGET (keyUrl client key) []   -- | Set the value at the given key. set :: Client -> Key -> Value -> Maybe TTL -> IO (Maybe Node)-set client key value mbTTL = do-    hr <- runRequest $ httpPUT (keyUrl client key) params-    case hr of-        Left _ -> return Nothing-        Right res -> return $ Just $ _resNode res--  where-    params = [("value",value)] ++ ttlParam mbTTL+set client key value mbTTL =+    runRequest' $ httpPUT (keyUrl client key) $+        [("value",value)] ++ ttlParam mbTTL   -- | Create a value in the given key. The key must be a directory. create :: Client -> Key -> Value -> Maybe TTL -> IO Node create client key value mbTTL = do-    hr <- runRequest $ httpPOST (keyUrl client key) params+    hr <- runRequest $ httpPOST (keyUrl client key) $+        [("value",value)] ++ ttlParam mbTTL+     case hr of         Left _ -> error "Unexpected error"         Right res -> return $ _resNode res -  where-    params = [("value",value)] ++ ttlParam mbTTL +-- | Wait for changes on the node at the given key.+wait :: Client -> Key -> IO (Maybe Node)+wait client key =+    runRequest' $ httpGET (keyUrl client key) [waitParam]  +-- | Same as 'wait' but at a given index.+waitIndex :: Client -> Key -> Index -> IO (Maybe Node)+waitIndex client key index =+    runRequest' $ httpGET (keyUrl client key) $+        [waitParam, waitIndexParam index]+++-- | Same as 'wait' but includes changes on children.+waitRecursive :: Client -> Key -> IO (Maybe Node)+waitRecursive client key =+    runRequest' $ httpGET (keyUrl client key) $+        [waitParam, waitRecursiveParam]+++-- | Same as 'waitIndex' but includes changes on children.+waitIndexRecursive :: Client -> Key -> Index -> IO (Maybe Node)+waitIndexRecursive client key index =+    runRequest' $ httpGET (keyUrl client key) $+        [waitParam, waitIndexParam index, waitRecursiveParam]+++ {-----------------------------------------------------------------------------  Directories are non-leaf nodes which contain zero or more child nodes. When@@ -343,7 +383,7 @@ -- | List all nodes within the given directory. listDirectoryContents :: Client -> Key -> IO [Node] listDirectoryContents client key = do-    hr <- runRequest $ httpGET $ keyUrl client key+    hr <- runRequest $ httpGET (keyUrl client key) []     case hr of         Left _ -> return []         Right res -> do
test/Test.hs view
@@ -12,6 +12,7 @@  import           Control.Applicative import           Control.Concurrent+import qualified Control.Concurrent.Async as Async import           Control.Monad import           Control.Monad.Random (getRandomR, evalRandIO) import           Control.Monad.Trans@@ -58,6 +59,11 @@ spec = parallel $ do      describe "Key Space Operations" $ do+        it "Get the root directory node" $ do+            (client, key) <- setup+            node <- expectNode =<< get client "/"+            shouldBeDirectory node+         it "Setting the value of a key" $ do             (client, key) <- setup             node <- expectNode =<< set client key "value" Nothing@@ -65,11 +71,11 @@          it "Using key TTL" $ do             (client, key) <- setup-            set client key "value" (Just 1)+            set client key "value" (Just 5)             node <- expectNode =<< get client key             node `shouldBeLeaf` "value" -            threadDelay $ 2 * 1000 * 1000+            threadDelay $ 6 * 1000 * 1000             node <- get client key             node `shouldSatisfy` isNothing @@ -82,6 +88,36 @@         it "Get the value of a key" $ do             (client, _) <- setup             void $ fromJust <$> get client "/_etcd/machines"++        it "Wait for any change on a key" $ do+            (client, key) <- setup+            a <- Async.async $ wait client key+            threadDelay $ 2 * 1000 * 1000+            Async.async $ set client key "value" Nothing+            node <- expectNode =<< Async.wait a+            node `shouldBeLeaf` "value"++        it "Wait for a change on a key at a given index" $ do+            (client, key) <- setup+            Just n <- set client key "value" Nothing+            let i = _nodeModifiedIndex n+            node <- expectNode =<< waitIndex client key i+            node `shouldBeLeaf` "value"++        it "Wait for any change on a key recursively" $ do+            (client, key) <- setup+            a <- Async.async $ waitRecursive client key+            threadDelay $ 2 * 1000 * 1000+            Async.async $ set client (key <> "/child") "value" Nothing+            node <- expectNode =<< Async.wait a+            node `shouldBeLeaf` "value"++        it "Wait for a change on a key at a given index recursively" $ do+            (client, key) <- setup+            Just n <- set client (key <> "/child") "value" Nothing+            let i = _nodeModifiedIndex n+            node <- expectNode =<< waitIndexRecursive client key i+            node `shouldBeLeaf` "value"          it "Creating directories" $ do             (client, key) <- setup