packages feed

libjenkins 0.5.0 → 0.6.0

raw patch · 20 files changed

+1322/−1005 lines, 20 filesdep +attoparsecdep +containersdep +http-client-tlsdep −conduitdep −exceptionsdep −http-conduitdep ~asyncdep ~http-clientdep ~lensPVP ok

version bump matches the API change (PVP)

Dependencies added: attoparsec, containers, http-client-tls, lifted-async, lifted-base, mtl, profunctors

Dependencies removed: conduit, exceptions, http-conduit, resourcet

Dependency ranges changed: async, http-client, lens, transformers, xml-conduit

API changes (from Hackage documentation)

- Jenkins.Discover: server_id :: Discover -> Maybe Text
- Jenkins.Rest: ConnectInfo :: String -> Int -> Text -> Text -> ConnectInfo
- Jenkins.Rest: Error :: e -> Result e v
- Jenkins.Rest: Result :: v -> Result e v
- Jenkins.Rest: _Error :: Prism (Result e a) (Result e' a) e e'
- Jenkins.Rest: _Result :: Prism (Result e a) (Result e b) a b
- Jenkins.Rest: _jenkinsApiToken :: ConnectInfo -> Text
- Jenkins.Rest: _jenkinsPort :: ConnectInfo -> Int
- Jenkins.Rest: _jenkinsUrl :: ConnectInfo -> String
- Jenkins.Rest: _jenkinsUser :: ConnectInfo -> Text
- Jenkins.Rest: class HasConnectInfo t
- Jenkins.Rest: connectInfo :: HasConnectInfo t => Lens' t ConnectInfo
- Jenkins.Rest: data ConnectInfo
- Jenkins.Rest: data HttpException :: *
- Jenkins.Rest: data Jenkins a
- Jenkins.Rest: defaultConnectInfo :: ConnectInfo
- Jenkins.Rest: getS :: Method Complete f -> Jenkins (ResumableSource (ResourceT IO) ByteString)
- Jenkins.Rest: jenkinsApiToken :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest: jenkinsPassword :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest: jenkinsPort :: HasConnectInfo t => Lens' t Int
- Jenkins.Rest: jenkinsUrl :: HasConnectInfo t => Lens' t String
- Jenkins.Rest: jenkinsUser :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest: postXML :: (forall f. Method Complete f) -> Document -> Jenkins ()
- Jenkins.Rest: runJenkins :: HasConnectInfo t => t -> Jenkins a -> IO (Result JenkinsException a)
- Jenkins.Rest: traverseC :: (a -> Jenkins b) -> [a] -> Jenkins [b]
- Jenkins.Rest: traverseC_ :: (a -> Jenkins b) -> [a] -> Jenkins ()
- Jenkins.Rest: with :: (Request -> Request) -> Jenkins a -> Jenkins a
- Jenkins.Rest.Internal: Conc :: Jenkins a -> Jenkins b -> (a -> b -> c) -> JenkinsF c
- Jenkins.Rest.Internal: ConnectInfo :: String -> Int -> Text -> Text -> ConnectInfo
- Jenkins.Rest.Internal: Dcon :: JenkinsF a
- Jenkins.Rest.Internal: Disconnect :: Result e v
- Jenkins.Rest.Internal: Error :: e -> Result e v
- Jenkins.Rest.Internal: Get :: Method Complete f -> (ResumableSource (ResourceT IO) ByteString -> a) -> JenkinsF a
- Jenkins.Rest.Internal: IO :: IO a -> JenkinsF a
- Jenkins.Rest.Internal: Jenkins :: F JenkinsF a -> Jenkins a
- Jenkins.Rest.Internal: JenkinsHttpException :: HttpException -> JenkinsException
- Jenkins.Rest.Internal: Or :: Jenkins a -> Jenkins a -> JenkinsF a
- Jenkins.Rest.Internal: Post :: (forall f. Method Complete f) -> ByteString -> a -> JenkinsF a
- Jenkins.Rest.Internal: Result :: v -> Result e v
- Jenkins.Rest.Internal: With :: (Request -> Request) -> Jenkins b -> (b -> a) -> JenkinsF a
- Jenkins.Rest.Internal: _Disconnect :: Prism' (Result e a) ()
- Jenkins.Rest.Internal: _Error :: Prism (Result e a) (Result e' a) e e'
- Jenkins.Rest.Internal: _Result :: Prism (Result e a) (Result e b) a b
- Jenkins.Rest.Internal: _jenkinsApiToken :: ConnectInfo -> Text
- Jenkins.Rest.Internal: _jenkinsPort :: ConnectInfo -> Int
- Jenkins.Rest.Internal: _jenkinsUrl :: ConnectInfo -> String
- Jenkins.Rest.Internal: _jenkinsUser :: ConnectInfo -> Text
- Jenkins.Rest.Internal: class HasConnectInfo t
- Jenkins.Rest.Internal: connectInfo :: HasConnectInfo t => Lens' t ConnectInfo
- Jenkins.Rest.Internal: data ConnectInfo
- Jenkins.Rest.Internal: data JenkinsF a
- Jenkins.Rest.Internal: data Result e v
- Jenkins.Rest.Internal: defaultConnectInfo :: ConnectInfo
- Jenkins.Rest.Internal: instance (Data e, Data v) => Data (Result e v)
- Jenkins.Rest.Internal: instance (Eq e, Eq v) => Eq (Result e v)
- Jenkins.Rest.Internal: instance (Ord e, Ord v) => Ord (Result e v)
- Jenkins.Rest.Internal: instance (Show e, Show v) => Show (Result e v)
- Jenkins.Rest.Internal: instance Applicative Jenkins
- Jenkins.Rest.Internal: instance Constructor C1_0ConnectInfo
- Jenkins.Rest.Internal: instance Constructor C1_0Result
- Jenkins.Rest.Internal: instance Constructor C1_1Result
- Jenkins.Rest.Internal: instance Constructor C1_2Result
- Jenkins.Rest.Internal: instance Data ConnectInfo
- Jenkins.Rest.Internal: instance Datatype D1ConnectInfo
- Jenkins.Rest.Internal: instance Datatype D1Result
- Jenkins.Rest.Internal: instance Eq ConnectInfo
- Jenkins.Rest.Internal: instance Exception JenkinsException
- Jenkins.Rest.Internal: instance Functor Jenkins
- Jenkins.Rest.Internal: instance Functor JenkinsF
- Jenkins.Rest.Internal: instance Generic (Result e v)
- Jenkins.Rest.Internal: instance Generic ConnectInfo
- Jenkins.Rest.Internal: instance HasConnectInfo ConnectInfo
- Jenkins.Rest.Internal: instance Monad Jenkins
- Jenkins.Rest.Internal: instance MonadIO Jenkins
- Jenkins.Rest.Internal: instance Selector S1_0_0ConnectInfo
- Jenkins.Rest.Internal: instance Selector S1_0_1ConnectInfo
- Jenkins.Rest.Internal: instance Selector S1_0_2ConnectInfo
- Jenkins.Rest.Internal: instance Selector S1_0_3ConnectInfo
- Jenkins.Rest.Internal: instance Show ConnectInfo
- Jenkins.Rest.Internal: instance Show JenkinsException
- Jenkins.Rest.Internal: instance Typeable ConnectInfo
- Jenkins.Rest.Internal: instance Typeable JenkinsException
- Jenkins.Rest.Internal: instance Typeable Result
- Jenkins.Rest.Internal: interpreter :: Manager -> JenkinsF (MaybeT (ReaderT Request (ResourceT IO)) a) -> MaybeT (ReaderT Request (ResourceT IO)) a
- Jenkins.Rest.Internal: intoIO :: Monad m => Manager -> ((forall b. Jenkins b -> IO (StT ResourceT (StT (ReaderT Request) (StT MaybeT b)))) -> m a) -> MaybeT (ReaderT Request (ResourceT m)) a
- Jenkins.Rest.Internal: iterJenkins :: Monad m => (JenkinsF (m a) -> m a) -> Jenkins a -> m a
- Jenkins.Rest.Internal: iterJenkinsIO :: Manager -> Jenkins a -> MaybeT (ReaderT Request (ResourceT IO)) a
- Jenkins.Rest.Internal: jenkinsApiToken :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest.Internal: jenkinsPassword :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest.Internal: jenkinsPort :: HasConnectInfo t => Lens' t Int
- Jenkins.Rest.Internal: jenkinsUrl :: HasConnectInfo t => Lens' t String
- Jenkins.Rest.Internal: jenkinsUser :: HasConnectInfo t => Lens' t Text
- Jenkins.Rest.Internal: liftJ :: JenkinsF a -> Jenkins a
- Jenkins.Rest.Internal: newtype Jenkins a
- Jenkins.Rest.Internal: newtype JenkinsException
- Jenkins.Rest.Internal: outoIO :: IO (StT ResourceT (StT (ReaderT Request) (StT MaybeT b))) -> MaybeT (ReaderT Request (ResourceT IO)) b
- Jenkins.Rest.Internal: prepareGet :: Method Complete f -> Request -> Request
- Jenkins.Rest.Internal: preparePost :: Method Complete f -> ByteString -> Request -> Request
- Jenkins.Rest.Internal: runJenkins :: HasConnectInfo t => t -> Jenkins a -> IO (Result JenkinsException a)
- Jenkins.Rest.Internal: runJenkinsInternal :: HasConnectInfo t => t -> Jenkins a -> IO (Maybe a)
- Jenkins.Rest.Internal: unJenkins :: Jenkins a -> F JenkinsF a
- Jenkins.Rest.Internal: withException :: (MonadCatch m, Exception e, Exception e') => m a -> (e -> e') -> m a
- Jenkins.Rest.Method: as :: Method Complete f -> As f -> Method Complete f
- Jenkins.Rest.Method: class JSONy t
- Jenkins.Rest.Method: class Pythony t
- Jenkins.Rest.Method: class XMLy t
- Jenkins.Rest.Method: data As :: Format -> *
- Jenkins.Rest.Method: instance Constructor C1_0Format
- Jenkins.Rest.Method: instance Constructor C1_0Type
- Jenkins.Rest.Method: instance Constructor C1_1Format
- Jenkins.Rest.Method: instance Constructor C1_1Type
- Jenkins.Rest.Method: instance Constructor C1_2Format
- Jenkins.Rest.Method: instance Data Format
- Jenkins.Rest.Method: instance Data Type
- Jenkins.Rest.Method: instance Datatype D1Format
- Jenkins.Rest.Method: instance Datatype D1Type
- Jenkins.Rest.Method: instance Eq (As f)
- Jenkins.Rest.Method: instance Eq Format
- Jenkins.Rest.Method: instance Eq Type
- Jenkins.Rest.Method: instance Generic Format
- Jenkins.Rest.Method: instance Generic Type
- Jenkins.Rest.Method: instance IsString (Method 'Complete f)
- Jenkins.Rest.Method: instance IsString (Method 'Query f)
- Jenkins.Rest.Method: instance JSONy As
- Jenkins.Rest.Method: instance Pythony As
- Jenkins.Rest.Method: instance Show (As f)
- Jenkins.Rest.Method: instance Show (As f) => Show (Method t f)
- Jenkins.Rest.Method: instance Show Format
- Jenkins.Rest.Method: instance Show Type
- Jenkins.Rest.Method: instance Typeable Format
- Jenkins.Rest.Method: instance Typeable Type
- Jenkins.Rest.Method: instance XMLy As
- Jenkins.Rest.Method: instance t ~ 'Complete => JSONy (Method t)
- Jenkins.Rest.Method: instance t ~ 'Complete => Num (Method t f)
- Jenkins.Rest.Method: instance t ~ 'Complete => Pythony (Method t)
- Jenkins.Rest.Method: instance t ~ 'Complete => XMLy (Method t)
- Jenkins.Rest.Method: render :: Method t f -> ByteString
- Jenkins.Rest.Method: slash :: (IsString m, Monoid m) => m -> m -> m
- Network.HTTP.Conduit.Lens: _ExpectedBlankAfter100Continue :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _FailedConnectionException :: AsHttpException t => Prism' t (String, Int)
- Network.HTTP.Conduit.Lens: _HandshakeFailed :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _HttpException :: AsHttpException t => Prism' t HttpException
- Network.HTTP.Conduit.Lens: _HttpParserException :: AsHttpException t => Prism' t String
- Network.HTTP.Conduit.Lens: _IncompleteHeaders :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _InternalIOException :: AsHttpException t => Prism' t IOException
- Network.HTTP.Conduit.Lens: _InvalidChunkHeaders :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _InvalidHeader :: AsHttpException t => Prism' t ByteString
- Network.HTTP.Conduit.Lens: _InvalidStatusLine :: AsHttpException t => Prism' t ByteString
- Network.HTTP.Conduit.Lens: _InvalidUrlException :: AsHttpException t => Prism' t (String, String)
- Network.HTTP.Conduit.Lens: _NoResponseDataReceived :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _OverlongHeaders :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _ProxyConnectException :: AsHttpException t => Prism' t (ByteString, Int, Either ByteString HttpException)
- Network.HTTP.Conduit.Lens: _ResponseBodyTooShort :: AsHttpException t => Prism' t (Word64, Word64)
- Network.HTTP.Conduit.Lens: _ResponseTimeout :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _StatusCodeException :: AsHttpException t => Prism' t (Status, ResponseHeaders, CookieJar)
- Network.HTTP.Conduit.Lens: _TlsException :: AsHttpException t => Prism' t SomeException
- Network.HTTP.Conduit.Lens: _TlsNotSupported :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _TooManyRedirects :: AsHttpException t => Prism' t [Response ByteString]
- Network.HTTP.Conduit.Lens: _TooManyRetries :: AsHttpException t => Prism' t ()
- Network.HTTP.Conduit.Lens: _UnparseableRedirect :: AsHttpException t => Prism' t (Response ByteString)
- Network.HTTP.Conduit.Lens: checkStatus :: Lens' Request (Status -> ResponseHeaders -> CookieJar -> Maybe SomeException)
- Network.HTTP.Conduit.Lens: class AsHttpException t
- Network.HTTP.Conduit.Lens: cookieJar :: Lens' Request (Maybe CookieJar)
- Network.HTTP.Conduit.Lens: decompress :: Lens' Request (ByteString -> Bool)
- Network.HTTP.Conduit.Lens: getConnectionWrapper :: Lens' Request (Maybe Int -> HttpException -> IO (ConnRelease, Connection, ManagedConn) -> IO (Maybe Int, (ConnRelease, Connection, ManagedConn)))
- Network.HTTP.Conduit.Lens: host :: Lens' Request ByteString
- Network.HTTP.Conduit.Lens: hostAddress :: Lens' Request (Maybe HostAddress)
- Network.HTTP.Conduit.Lens: instance AsHttpException HttpException
- Network.HTTP.Conduit.Lens: instance AsHttpException SomeException
- Network.HTTP.Conduit.Lens: method :: Lens' Request Method
- Network.HTTP.Conduit.Lens: path :: Lens' Request ByteString
- Network.HTTP.Conduit.Lens: port :: Lens' Request Int
- Network.HTTP.Conduit.Lens: proxy :: Lens' Request (Maybe Proxy)
- Network.HTTP.Conduit.Lens: queryString :: Lens' Request ByteString
- Network.HTTP.Conduit.Lens: rawBody :: Lens' Request Bool
- Network.HTTP.Conduit.Lens: redirectCount :: Lens' Request Int
- Network.HTTP.Conduit.Lens: requestBody :: Lens' Request RequestBody
- Network.HTTP.Conduit.Lens: requestHeaders :: Lens' Request RequestHeaders
- Network.HTTP.Conduit.Lens: responseTimeout :: Lens' Request (Maybe Int)
- Network.HTTP.Conduit.Lens: secure :: Lens' Request Bool
+ Jenkins.Discover: port :: Discover -> Maybe Text
+ Jenkins.Discover: serverId :: Discover -> Maybe Text
+ Jenkins.Rest: Exception :: JenkinsException -> Result v
+ Jenkins.Rest: Ok :: v -> Result v
+ Jenkins.Rest: _Exception :: Prism (Result a) (Result a) JenkinsException JenkinsException
+ Jenkins.Rest: _Ok :: Prism (Result a) (Result b) a b
+ Jenkins.Rest: apiToken :: (HasMaster t, HasMaster t) => Lens' t Text
+ Jenkins.Rest: class HasMaster t where url = master . \ f x -> f (_url x) <&> \ p -> x {_url = p} user = master . \ f x -> f (_user x) <&> \ p -> x {_user = p} apiToken = master . \ f x -> f (_apiToken x) <&> \ p -> x {_apiToken = p}
+ Jenkins.Rest: data JenkinsT m a
+ Jenkins.Rest: data Master
+ Jenkins.Rest: defaultMaster :: Master
+ Jenkins.Rest: disconnect :: JenkinsT m a
+ Jenkins.Rest: instance Data Master
+ Jenkins.Rest: instance Eq Master
+ Jenkins.Rest: instance HasMaster Master
+ Jenkins.Rest: instance Show Master
+ Jenkins.Rest: instance Show v => Show (Result v)
+ Jenkins.Rest: instance Typeable Master
+ Jenkins.Rest: instance Typeable Result
+ Jenkins.Rest: locally :: (Request -> Request) -> JenkinsT m a -> JenkinsT m a
+ Jenkins.Rest: master :: HasMaster t => Lens' t Master
+ Jenkins.Rest: orElse_ :: JenkinsT m a -> JenkinsT m a -> JenkinsT m a
+ Jenkins.Rest: postXml :: (forall f. Method Complete f) -> ByteString -> JenkinsT m ()
+ Jenkins.Rest: run :: (MonadIO m, MonadBaseControl IO m, HasMaster t) => t -> JenkinsT m a -> m (Result a)
+ Jenkins.Rest: traverse :: (a -> JenkinsT m b) -> [a] -> JenkinsT m [b]
+ Jenkins.Rest: traverse_ :: Foldable f => (a -> JenkinsT m b) -> f a -> JenkinsT m ()
+ Jenkins.Rest: type Jenkins = JenkinsT IO
+ Jenkins.Rest: url :: (HasMaster t, HasMaster t) => Lens' t String
+ Jenkins.Rest: user :: (HasMaster t, HasMaster t) => Lens' t Text
+ Jenkins.Rest.Method: Json :: Format
+ Jenkins.Rest.Method: Python :: Format
+ Jenkins.Rest.Method: Xml :: Format
+ Jenkins.Rest.Method: data Formatter g
+ Jenkins.Rest.Method: plain :: Formatter f
+ Network.HTTP.Client.Lens: _ExpectedBlankAfter100Continue :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _FailedConnectionException :: AsHttpException t => Prism' t (String, Int)
+ Network.HTTP.Client.Lens: _HandshakeFailed :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _HttpException :: AsHttpException t => Prism' t HttpException
+ Network.HTTP.Client.Lens: _HttpParserException :: AsHttpException t => Prism' t String
+ Network.HTTP.Client.Lens: _IncompleteHeaders :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _InternalIOException :: AsHttpException t => Prism' t IOException
+ Network.HTTP.Client.Lens: _InvalidChunkHeaders :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _InvalidHeader :: AsHttpException t => Prism' t ByteString
+ Network.HTTP.Client.Lens: _InvalidStatusLine :: AsHttpException t => Prism' t ByteString
+ Network.HTTP.Client.Lens: _InvalidUrlException :: AsHttpException t => Prism' t (String, String)
+ Network.HTTP.Client.Lens: _NoResponseDataReceived :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _OverlongHeaders :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _ProxyConnectException :: AsHttpException t => Prism' t (ByteString, Int, Either ByteString HttpException)
+ Network.HTTP.Client.Lens: _ResponseBodyTooShort :: AsHttpException t => Prism' t (Word64, Word64)
+ Network.HTTP.Client.Lens: _ResponseTimeout :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _StatusCodeException :: AsHttpException t => Prism' t (Status, ResponseHeaders, CookieJar)
+ Network.HTTP.Client.Lens: _TlsException :: AsHttpException t => Prism' t SomeException
+ Network.HTTP.Client.Lens: _TlsNotSupported :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _TooManyRedirects :: AsHttpException t => Prism' t [Response ByteString]
+ Network.HTTP.Client.Lens: _TooManyRetries :: AsHttpException t => Prism' t ()
+ Network.HTTP.Client.Lens: _UnparseableRedirect :: AsHttpException t => Prism' t (Response ByteString)
+ Network.HTTP.Client.Lens: checkStatus :: Lens' Request (Status -> ResponseHeaders -> CookieJar -> Maybe SomeException)
+ Network.HTTP.Client.Lens: class AsHttpException t
+ Network.HTTP.Client.Lens: cookieJar :: Lens' Request (Maybe CookieJar)
+ Network.HTTP.Client.Lens: decompress :: Lens' Request (ByteString -> Bool)
+ Network.HTTP.Client.Lens: getConnectionWrapper :: Lens' Request (Maybe Int -> HttpException -> IO (ConnRelease, Connection, ManagedConn) -> IO (Maybe Int, (ConnRelease, Connection, ManagedConn)))
+ Network.HTTP.Client.Lens: host :: Lens' Request ByteString
+ Network.HTTP.Client.Lens: hostAddress :: Lens' Request (Maybe HostAddress)
+ Network.HTTP.Client.Lens: instance AsHttpException HttpException
+ Network.HTTP.Client.Lens: instance AsHttpException SomeException
+ Network.HTTP.Client.Lens: method :: Lens' Request Method
+ Network.HTTP.Client.Lens: path :: Lens' Request ByteString
+ Network.HTTP.Client.Lens: port :: Lens' Request Int
+ Network.HTTP.Client.Lens: proxy :: Lens' Request (Maybe Proxy)
+ Network.HTTP.Client.Lens: queryString :: Lens' Request ByteString
+ Network.HTTP.Client.Lens: rawBody :: Lens' Request Bool
+ Network.HTTP.Client.Lens: redirectCount :: Lens' Request Int
+ Network.HTTP.Client.Lens: requestBody :: Lens' Request RequestBody
+ Network.HTTP.Client.Lens: requestHeaders :: Lens' Request RequestHeaders
+ Network.HTTP.Client.Lens: responseTimeout :: Lens' Request (Maybe Int)
+ Network.HTTP.Client.Lens: secure :: Lens' Request Bool
- Jenkins.Discover: Discover :: Text -> Text -> Maybe Text -> Discover
+ Jenkins.Discover: Discover :: Text -> Text -> Maybe Text -> Maybe Text -> Discover
- Jenkins.Rest: Disconnect :: Result e v
+ Jenkins.Rest: Disconnect :: Result v
- Jenkins.Rest: _Disconnect :: Prism' (Result e a) ()
+ Jenkins.Rest: _Disconnect :: Prism' (Result a) ()
- Jenkins.Rest: concurrently :: Jenkins a -> Jenkins b -> Jenkins (a, b)
+ Jenkins.Rest: concurrently :: JenkinsT m a -> JenkinsT m b -> JenkinsT m (a, b)
- Jenkins.Rest: data Result e v
+ Jenkins.Rest: data Result v
- Jenkins.Rest: forceRestart :: Jenkins a
+ Jenkins.Rest: forceRestart :: JenkinsT m a
- Jenkins.Rest: get :: Method Complete f -> Jenkins ByteString
+ Jenkins.Rest: get :: Formatter f -> (forall g. Method Complete g) -> JenkinsT m ByteString
- Jenkins.Rest: orElse :: Jenkins a -> Jenkins a -> Jenkins a
+ Jenkins.Rest: orElse :: JenkinsT m a -> (JenkinsException -> JenkinsT m a) -> JenkinsT m a
- Jenkins.Rest: post :: (forall f. Method Complete f) -> ByteString -> Jenkins ()
+ Jenkins.Rest: post :: (forall f. Method Complete f) -> ByteString -> JenkinsT m ()
- Jenkins.Rest: post_ :: (forall f. Method Complete f) -> Jenkins ()
+ Jenkins.Rest: post_ :: (forall f. Method Complete f) -> JenkinsT m ()
- Jenkins.Rest: reload :: Jenkins a
+ Jenkins.Rest: reload :: JenkinsT m a
- Jenkins.Rest: restart :: Jenkins a
+ Jenkins.Rest: restart :: JenkinsT m a
- Jenkins.Rest.Method: build :: Integral a => Text -> a -> Method Complete f
+ Jenkins.Rest.Method: build :: Text -> Int -> Method Complete f
- Jenkins.Rest.Method: int :: Integer -> Method Complete f
+ Jenkins.Rest.Method: int :: Int -> Method Complete f
- Jenkins.Rest.Method: json :: JSONy t => t JSON
+ Jenkins.Rest.Method: json :: Formatter Json
- Jenkins.Rest.Method: python :: Pythony t => t Python
+ Jenkins.Rest.Method: python :: Formatter Python
- Jenkins.Rest.Method: xml :: XMLy t => t XML
+ Jenkins.Rest.Method: xml :: Formatter Xml

Files

CHANGELOG.md view
@@ -1,3 +1,22 @@+0.6.0+=====++  * Made `orElse` more powerful. The old version is available as `orElse_`++  * Renamed a bunch of things.  The `Jenkins.Rest` module is intended to be imported qualified.++  * Switched to the transformer version of the Church-encoded free monad++  * Removed `getS`. As a side-effect, `get` doesn't leak like crazy anymore++  * Generalized `traverseC_` (again)++  * Removed redundant `jenkinsPort` option: `jenkinsUrl` handles port numbers well enough++  * Reworked API method construction. The new version is safer (it's impossible to forget+    to specify the format of the response), less magical (format is a separate argument to+    the query function), and has fewer corner cases+ 0.5.0 ===== 
bench/Concurrency.hs view
@@ -18,7 +18,8 @@ import           Data.Aeson.Lens               -- lens-aeson import           Data.Text (Text)              -- text import qualified Data.Text as Text             -- text-import           Jenkins.Rest                  -- libjenkins+import           Jenkins.Rest (Jenkins, (-?-), (-=-))+import qualified Jenkins.Rest as Jenkins       -- libjenkins import           System.Environment (getArgs)  -- base import           System.Exit (exitFailure)     -- base import           System.IO (hPutStrLn, stderr) -- base@@ -28,32 +29,35 @@  main :: IO () main = do-  m:host:port:user:pass:_ <- getArgs+  m:url:user:token:_ <- getArgs   ds <- descriptions (aggregate m) $-    ConnectInfo host (read port) (Text.pack user) (Text.pack pass)+    Jenkins.defaultMaster+    & Jenkins.url .~ url+    & Jenkins.user .~ Text.pack user+    & Jenkins.apiToken .~ Text.pack token   case ds of-    Result ds' -> mapM_ print ds'-    Disconnect -> die "disconnect!"-    Error e    -> die (show e)+    Jenkins.Ok ds'      -> mapM_ print ds'+    Jenkins.Disconnect  -> die "disconnect!"+    Jenkins.Exception e -> die (show e)  where   die message = do     hPutStrLn stderr message     exitFailure    aggregate :: String -> Aggregate a b-  aggregate "concurrent" = traverseC+  aggregate "concurrent" = Jenkins.traverse   aggregate "sequential" = mapM   aggregate _ = error "Unknown mode"  descriptions   :: Aggregate Text (Maybe Text)-  -> ConnectInfo-  -> IO (Result JenkinsException [Maybe Text])-descriptions aggregate settings = runJenkins settings $ do-  res <- get (json -?- "tree" -=- "jobs[name]")+  -> Jenkins.Master+  -> IO (Jenkins.Result [Maybe Text])+descriptions aggregate settings = Jenkins.run settings $ do+  res <- Jenkins.get Jenkins.json ("" -?- "tree" -=- "jobs[name]")   aggregate describe (res ^.. key "jobs".values.key "name"._String)  describe :: Text -> Jenkins (Maybe Text) describe name = do-  desc <- get (job name `as` json -?- "tree" -=- "description")+  desc <- Jenkins.get Jenkins.json (Jenkins.job name -?- "tree" -=- "description")   return (desc ^? key "description"._String)
example/discover.hs view
@@ -4,8 +4,8 @@  import           Data.Monoid ((<>), mempty) -- base import           Data.Text (Text)           -- text-import qualified Data.Text as T             -- text-import qualified Data.Text.IO as T          -- text+import qualified Data.Text as Text          -- text+import qualified Data.Text.IO as Text       -- text import           Jenkins.Discover           -- libjenkins import           System.Exit (exitFailure)  -- base @@ -18,12 +18,12 @@     -- no Jenkins responded     [] -> exitFailure     -- pretty print responses-    _  -> mapM_ (T.putStrLn . pretty) discoveries+    _  -> mapM_ (Text.putStrLn . pretty) discoveries  -- | Pretty print Jenkins discovery responses pretty :: Discover -> Text-pretty x = T.unwords $-  ["Jenkins", version x] ++ maybe mempty (return . between "(" ")") (server_id x) ++ ["at", url x]+pretty x = Text.unwords $+  "Jenkins" : version x : maybe mempty (return . between "(" ")") (serverId x) ++ ["at", url x]  where   between l r t = l <> t <> r 
example/grep-jobs.hs view
@@ -11,7 +11,8 @@ import           Data.Text (Text)                          -- text import qualified Data.Text as Text                         -- text import qualified Data.Text.IO as Text                      -- text-import           Jenkins.Rest                              -- libjenkins+import           Jenkins.Rest ((-?-), (-=-))+import qualified Jenkins.Rest as Jenkins                   -- libjenkins import           System.Environment (getArgs)              -- base import           System.Exit.Lens                          -- lens import           System.Process (readProcessWithExitCode)  -- process@@ -20,18 +21,21 @@  main :: IO () main = do-  user:pswd:host:port:regex:_ <- getArgs-  jobs <- grep regex (ConnectInfo host (read port) (fromString user) (fromString pswd))+  url:user:token:regex:_ <- getArgs+  jobs <- grep regex $ Jenkins.defaultMaster+    & Jenkins.url .~ url+    & Jenkins.user .~ fromString user+    & Jenkins.apiToken .~ fromString token   case jobs of     [] -> throwingM _ExitFailure 1     _  -> mapM_ Text.putStrLn jobs  -- | Filter matching job names-grep :: String -> ConnectInfo -> IO [Text]+grep :: String -> Jenkins.Master -> IO [Text] grep regex conn = do-  jobs <- runJenkins conn $-    get (json -?- "tree" -=- "jobs[name]") <&> \res -> res ^.. key "jobs".values.key "name"._String-  filterM (match regex) (jobs ^.. _Result.folded)+  res <- Jenkins.run conn $+    Jenkins.get Jenkins.json ("/" -?- "tree" -=- "jobs[name]")+  filterM (match regex) (res ^.. Jenkins._Ok.key "jobs".values.key "name"._String)  -- | Match job name again Perl regex match :: String -> Text -> IO Bool
example/rename-jobs.hs view
@@ -12,7 +12,8 @@ import           Data.Text (Text)              -- text import qualified Data.Text as Text             -- text import qualified Data.Text.IO as Text          -- text-import           Jenkins.Rest                  -- libjenkins+import           Jenkins.Rest (Jenkins, (-?-), (-=-), (-/-), liftIO)+import qualified Jenkins.Rest as Jenkins       -- libjenkins import           System.Environment (getArgs)  -- base import           System.Exit (exitFailure)     -- base import           System.IO (hPutStrLn, stderr) -- base@@ -22,7 +23,7 @@  -- | Program options data Options = Options-  { settings :: ConnectInfo+  { settings :: Jenkins.Master   , old      :: Text   , new      :: Text   }@@ -31,25 +32,29 @@ main :: IO () main = do   -- more useful help on error-  host:port:user:pass:o:n:_ <- getArgs-  let opts = Options (ConnectInfo host (read port) (fromString user) (fromString pass)) (fromString o) (fromString n)+  url:user:token:o:n:_ <- getArgs+  let cinf = Jenkins.defaultMaster+        & Jenkins.url .~ url+        & Jenkins.user .~ fromString user+        & Jenkins.apiToken .~ fromString token+      opts = Options cinf (fromString o) (fromString n)   res <- rename opts   case res of-    Result _ -> Text.putStrLn "Done."+    Jenkins.Ok _ -> Text.putStrLn "Done."     -- disconnected for some reason-    Disconnect -> die "disconnect!"+    Jenkins.Disconnect -> die "disconnect!"     -- something bad happened, show it!-    Error e -> die (show e)+    Jenkins.Exception e -> die (show e)  where   die message = do     hPutStrLn stderr message     exitFailure  -- | Prompt to rename all jobs matching pattern-rename :: Options -> IO (Result JenkinsException ())-rename (Options { settings, old, new }) = runJenkins settings $ do+rename :: Options -> IO (Jenkins.Result ())+rename (Options { settings, old, new }) = Jenkins.run settings $ do   -- get jobs names from jenkins "root" API-  res <- get (json -?- "tree" -=- "jobs[name]")+  res <- Jenkins.get Jenkins.json ("/" -?- "tree" -=- "jobs[name]")   let jobs = res ^.. key "jobs".values.key "name"._String   for_ jobs rename_job  where@@ -60,7 +65,7 @@     yes <- prompt $ Text.unwords ["Rename", name, "to", name', "? [y/n]"]     when yes $       -- if user agrees then voodoo comes-      post_ (job name -/- "doRename" -?- "newName" -=- name')+      Jenkins.post_ (Jenkins.job name -/- "doRename" -?- "newName" -=- name')    -- asks user until she enters 'y' or 'n'   prompt message = liftIO . fix $ \loop -> do
example/running-jobs-count.hs view
@@ -7,24 +7,28 @@ -- it to "blue_anime", meaning "animated blue ball" if job is running module Main (main) where -import Control.Lens                      -- lens-import Data.Aeson.Lens                   -- lens-aeson-import Data.ByteString.Lazy (ByteString) -- bytestring-import Data.String (fromString)          -- bytestring-import Jenkins.Rest                      -- libjenkins-import System.Environment (getArgs)      -- base-import Text.Printf (printf)              -- base+import           Control.Lens                      -- lens+import           Data.Aeson.Lens                   -- lens-aeson+import           Data.ByteString.Lazy (ByteString) -- bytestring+import           Data.String (fromString)          -- bytestring+import           Jenkins.Rest (Jenkins, (-?-), (-=-))+import qualified Jenkins.Rest as Jenkins -- libjenkins+import           System.Environment (getArgs)      -- base+import           Text.Printf (printf)              -- base   main :: IO () main = do-  host:port:user:apiToken:_ <- getArgs-  let creds = ConnectInfo host (read port) (fromString user) (fromString apiToken)-  jobs <- runJenkins creds getJobs-  printf "Running jobs count: %d\n" (lengthOf (_Result.running) jobs)+  url:user:token:_ <- getArgs+  let creds = Jenkins.defaultMaster+        & Jenkins.url .~ url+        & Jenkins.user .~ fromString user+        & Jenkins.apiToken .~ fromString token+  jobs <- Jenkins.run creds getJobs+  printf "Running jobs count: %d\n" (lengthOf (Jenkins._Ok.running) jobs)  getJobs :: Jenkins ByteString-getJobs = get (json -?- "tree" -=- "jobs[color]")+getJobs = Jenkins.get Jenkins.json ("/" -?- "tree" -=- "jobs[color]")  running :: Fold ByteString () running = key "jobs".values.key "color"._String.only "blue_anime"
libjenkins.cabal view
@@ -1,5 +1,5 @@ name:                libjenkins-version:             0.5.0+version:             0.6.0 synopsis:            Jenkins API interface description:         Jenkins API interface. It supports REST and Discovery APIs license:             BSD3@@ -26,76 +26,92 @@ source-repository this   type:     git   location: https://github.com/supki/libjenkins-  tag:      0.5.0+  tag:      0.6.0  library-  default-language:  Haskell2010-  hs-source-dirs:    src+  default-language:+    Haskell2010+  build-depends:+      base            >= 4.6 && < 5+    , attoparsec      >= 0.12+    , bytestring      >= 0.9+    , containers+    , free            >= 4.1+    , http-client     >= 0.3.8+    , http-client-tls >= 0.2.2+    , http-types      >= 0.8+    , lifted-async    >= 0.2+    , lifted-base     >= 0.2.3+    , monad-control   >= 0.3+    , profunctors     >= 4.2+    , mtl             >= 2.1+    , network         >= 2.6+    , network-uri     >= 2.6+    , text            >= 0.11+    , transformers+  hs-source-dirs:+    src   exposed-modules:     Jenkins.Discover     Jenkins.Rest     Jenkins.Rest.Internal     Jenkins.Rest.Method-    Network.HTTP.Conduit.Lens-  build-depends:-      async         >= 2.0-    , base          >= 4.6 && < 5-    , bytestring    >= 0.9-    , conduit       >= 1.0-    , free          >= 4.1-    , exceptions    >= 0.6.1-    , http-client   >= 0.2.0.2-    , http-conduit  >= 2.0 && < 2.2-    , http-types    >= 0.8-    , lens          >= 4.0.1-    , monad-control >= 0.3-    , network       >= 2.6-    , network-uri   >= 2.6-    , resourcet     >= 1.1-    , text          >= 0.11-    , transformers  >= 0.3-    , xml-conduit   >= 1.1+    Jenkins.Rest.Method.Internal+    Network.HTTP.Client.Lens+    Network.HTTP.Client.Lens.Internal  test-suite doctest-  default-language:  Haskell2010-  type:              exitcode-stdio-1.0+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:       base          == 4.*     , directory     , doctest     , filepath-  hs-source-dirs:    test-  main-is:           Doctest.hs+  hs-source-dirs:+    test+  main-is:+    Doctest.hs  test-suite spec-  default-language:  Haskell2010-  type:              exitcode-stdio-1.0+  default-language:+    Haskell2010+  type:+    exitcode-stdio-1.0   build-depends:       async+    , attoparsec     , base          == 4.*     , bytestring-    , conduit+    , containers     , free-    , exceptions     , hspec     , hspec-expectations-lens     , http-client-    , http-conduit+    , http-client-tls     , http-types     , lens+    , lifted-async+    , lifted-base     , monad-control+    , mtl     , network     , network-uri-    , resourcet+    , profunctors     , text     , transformers     , xml-conduit   hs-source-dirs:     src     test-  main-is:           Spec.hs+  main-is:+    Spec.hs   other-modules:+    Jenkins.DiscoverSpec     Jenkins.RestSpec     Jenkins.Rest.InternalSpec+    Jenkins.Rest.Method.InternalSpec   cpp-options:     -DTEST
src/Jenkins/Discover.hs view
@@ -5,32 +5,35 @@   ( Discover(..)   , discover #ifdef TEST-  , parse+  , parseXml #endif   ) where -import           Control.Applicative (Applicative(..), (<$>))-import           Control.Lens hiding (element)+import           Control.Applicative+import           Control.Monad+import           Data.Attoparsec.Text import           Data.ByteString (ByteString)-import qualified Data.ByteString as B-import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as ByteString+import           Data.Map (Map)+import qualified Data.Map as Map import           Data.Maybe (mapMaybe)+import           Data.Monoid ((<>)) import           Data.Text (Text)+import qualified Data.Text.Encoding as Text import           Network.BSD import           Network.Socket-import           Network.Socket.ByteString as B+import           Network.Socket.ByteString as ByteString import           System.Timeout (timeout)-import           Text.XML-import           Text.XML.Cursor  {-# ANN module ("HLint: ignore Use camelCase" :: String) #-}   -- | Jenkins information data Discover = Discover-  { version   :: Text-  , url       :: Text-  , server_id :: Maybe Text+  { version  :: Text+  , url      :: Text+  , port     :: Maybe Text+  , serverId :: Maybe Text   } deriving (Show, Eq)  @@ -40,12 +43,12 @@   -> IO [Discover] discover t = do   (b, addr) <- broadcastSocket-  B.sendTo b (B.pack [0, 0, 0, 0]) addr -- does not matter what to send+  ByteString.sendTo b (ByteString.pack [0, 0, 0, 0]) addr -- does not matter what to send    msgs <- while (timeout t (readAnswer b))    close b-  return (mapMaybe parse msgs)+  return (mapMaybe parseXml msgs)  where   while :: IO (Maybe a) -> IO [a]   while io = go where@@ -59,31 +62,37 @@ broadcastSocket = do   s <- getProtocolNumber "udp" >>= socket AF_INET Datagram   setSocketOption s Broadcast 1-  return (s, SockAddrInet port (-1) {- 255.255.255.255 -})+  return (s, SockAddrInet p (-1) {- 255.255.255.255 -})  where-  port = 33848+  p = 33848  readAnswer :: Socket -> IO ByteString-readAnswer s = fst <$> B.recvFrom s 4096+readAnswer s = fst <$> ByteString.recvFrom s 4096   -- | Parse Jenkins discovery response XML------ The \"Scheme\" is as follows:------ @--- <hudson>---   <version>...</version>---   <url>...</url>---   <server-id>...</server-id>--- </hudson>--- @-parse :: ByteString -> Maybe Discover-parse bs = either (const Nothing) Just (parseLBS def (BL.fromStrict bs)) >>= \doc ->-  let-    cursor = fromDocument doc-    tag t  = preview _head (cursor $/ element t &// content)-  in Discover-    <$> tag "version"-    <*> tag "url"-    <*> pure (tag "server-id")+parseXml :: ByteString -> Maybe Discover+parseXml = fromMap <=< either (\_ -> Nothing) Just . parseOnly (parser <* endOfInput) . Text.decodeUtf8++fromMap :: Map Text Text -> Maybe Discover+fromMap m = do+  v <- Map.lookup "version" m+  u <- Map.lookup "url" m+  i <- return (Map.lookup "server-id" m)+  p <- return (Map.lookup "slave-port" m)+  return Discover { version = v, url = u, serverId = i, port = p }++parser :: Parser (Map Text Text)+parser = string "<hudson>" *> tags <* string "</hudson>"++tags :: Parser (Map Text Text)+tags = Map.fromList <$> many tag++tag :: Parser (Text, Text)+tag = do+  _ <- char '<'+  k <- takeWhile1 (/= '>')+  _ <- char '>'+  v <- takeWhile1 (/= '<')+  _ <- string ("</" <> k <> ">")+  return (k, v)
src/Jenkins/Rest.hs view
@@ -1,135 +1,252 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} -- | Jenkins REST API interface+--+-- This module is intended to be imported qualified. module Jenkins.Rest   ( -- * Query Jenkins-    Jenkins-  , HasConnectInfo(..)-  , ConnectInfo(..)-  , defaultConnectInfo+    run+  , JenkinsT+  , Jenkins   , Result(..)-  , runJenkins+  , HasMaster(..)+  , Master+  , defaultMaster     -- ** Combinators   , get-  , getS   , post   , post_-  , concurrently   , orElse-  , liftIO-  , with+  , orElse_+  , locally+  , disconnect     -- ** Method   , module Jenkins.Rest.Method+    -- ** Concurrency+  , concurrently+  , Jenkins.Rest.traverse+  , Jenkins.Rest.traverse_     -- ** Convenience-  , postXML-  , traverseC-  , traverseC_+  , postXml   , reload   , restart   , forceRestart     -- * Optics-  , jenkinsUrl-  , jenkinsPort-  , jenkinsUser-  , jenkinsApiToken-  , jenkinsPassword-  , _Error+  , _Exception   , _Disconnect-  , _Result+  , _Ok   , JenkinsException(..)     -- * Reexports+  , liftIO   , Request-  , HttpException   ) where  import           Control.Applicative ((<$))-import           Control.Lens-import           Control.Monad.Trans.Resource (ResourceT, runResourceT)-import           Control.Monad.IO.Class (liftIO)-import qualified Data.ByteString as Strict+import           Control.Exception.Lifted (try)+import           Control.Monad+import           Control.Monad.IO.Class (MonadIO(..))+import           Control.Monad.Trans.Control (MonadBaseControl(..)) import qualified Data.ByteString.Lazy as Lazy-import           Data.Conduit (ResumableSource, ($$+-))-import qualified Data.Conduit.List as CL+import           Data.Data (Data, Typeable)+import qualified Data.Foldable as F import           Data.Monoid (mempty)-import           Network.HTTP.Conduit (Request, HttpException)-import           Text.XML (Document, renderLBS, def)+import           Data.Text (Text)+import           Network.HTTP.Client (Request, requestHeaders)  import           Jenkins.Rest.Internal import           Jenkins.Rest.Method-import           Network.HTTP.Conduit.Lens+import           Jenkins.Rest.Method.Internal+import           Network.HTTP.Client.Lens.Internal  --- | @GET@ query+-- | Run a 'JenkinsT' action ----- While the return type is a lazy bytestring, the entire response--- sits in memory anyway: lazy I/O is not used-get :: Method Complete f -> Jenkins Lazy.ByteString-get m = fmap Lazy.fromChunks . liftIO . runResourceT =<< liftJ (Get m ($$+- CL.consume))+-- A successful 'Result' is either @'Disconnect'@ or @'Ok' v@+--+-- If a 'JenkinsException' is thrown by performing a request to Jenkins,+-- 'runJenkins' will catch and wrap it in @'Exception'@. Other exceptions+-- will propagate further+run+  :: (MonadIO m, MonadBaseControl IO m, HasMaster t)+  => t -> JenkinsT m a -> m (Result a)+run c jenk =+  either Exception (maybe Disconnect Ok)+ `liftM`+  try (runInternal (c^.url) (c^.user) (c^.apiToken) jenk) --- | @GET@ query+-- | A handy type synonym for the kind of 'JenkinsT' actions that's used the most+type Jenkins = JenkinsT IO++-- | The result of Jenkins REST API queries+data Result v =+    Exception JenkinsException+    -- ^ Exception was thrown while making requests to Jenkins+  | Disconnect+    -- ^ The client was explicitly disconnected by the user+  | Ok v+    -- ^ The result of uninterrupted execution of a 'JenkinsT' value+    deriving (Show, Typeable)++-- | A prism into Jenkins error+_Exception :: Prism (Result a) (Result a) JenkinsException JenkinsException+_Exception = prism' Exception $ \case+  Exception e -> Just e+  _           -> Nothing+{-# INLINE _Exception #-}++-- | A prism into disconnect+_Disconnect :: Prism' (Result a) ()+_Disconnect = prism' (\_ -> Disconnect) $ \case+  Disconnect -> Just ()+  _          -> Nothing+{-# INLINE _Disconnect #-}+{-# ANN _Disconnect ("HLint: ignore Use const" :: String) #-}++-- | A prism into result+_Ok :: Prism (Result a) (Result b) a b+_Ok = prism Ok $ \case+  Exception e -> Left (Exception e)+  Disconnect  -> Left Disconnect+  Ok a        -> Right a+{-# INLINE _Ok #-}++-- | Jenkins master node connection settings+class HasMaster t where+  master :: Lens' t Master++  -- | Jenkins master node URL+  url :: HasMaster t => Lens' t String+  url = master . \f x -> f (_url x) <&> \p -> x { _url = p }+  {-# INLINE url #-}++  -- | Jenkins user+  user :: HasMaster t => Lens' t Text+  user = master . \f x -> f (_user x) <&> \p -> x { _user = p }+  {-# INLINE user #-}++  -- | Jenkins user's password or API token+  apiToken :: HasMaster t => Lens' t Text+  apiToken = master . \f x -> f (_apiToken x) <&> \p -> x { _apiToken = p }+  {-# INLINE apiToken #-}++-- | Jenkins master node connection settings token+data Master = Master+  { _url      :: String -- ^ Jenkins URL+  , _user     :: Text   -- ^ Jenkins user+  , _apiToken :: Text   -- ^ Jenkins user API token or password+  } deriving (Show, Eq, Typeable, Data)++instance HasMaster Master where+  master = id+  {-# INLINE master #-}++-- | Default Jenkins master node connection settings token ----- If you don't close the source eventually (either explicitly with--- 'Data.Conduit.closeResumableSource' or implicitly by reading from it)--- it will leak a socket.-getS :: Method Complete f -> Jenkins (ResumableSource (ResourceT IO) Strict.ByteString)-getS m = liftJ (Get m id)+-- @+-- view 'url'      defaultConnectInfo = \"http:\/\/example.com\/jenkins\"+-- view 'user'     defaultConnectInfo = \"jenkins\"+-- view 'apiToken' defaultConnectInfo = \"secret\"+-- @+defaultMaster :: Master+defaultMaster = Master+  { _url      = "http://example.com/jenkins"+  , _user     = "jenkins"+  , _apiToken = "secret"+  } --- | @POST@ query (with a payload)-post :: (forall f. Method Complete f) -> Lazy.ByteString -> Jenkins ()++-- | Perform a @GET@ request+--+-- While the return type is the /lazy/ @Bytestring@, the entire response+-- sits in the memory anyway: lazy I/O is not used at the least+get :: Formatter f -> (forall g. Method Complete g) -> JenkinsT m Lazy.ByteString+get (Formatter f) m = liftJ (Get (f m) id)++-- | Perform a @POST@ request+post :: (forall f. Method Complete f) -> Lazy.ByteString -> JenkinsT m () post m body = liftJ (Post m body ()) --- | @POST@ query (without payload)-post_ :: (forall f. Method Complete f) -> Jenkins ()+-- | Perform a @POST@ request without a payload+post_ :: (forall f. Method Complete f) -> JenkinsT m () post_ m = post m mempty --- | Do both queries 'concurrently'-concurrently :: Jenkins a -> Jenkins b -> Jenkins (a, b)-concurrently ja jb = liftJ (Conc ja jb (,))+-- | A simple exception handler. If an exception is raised while the action is+-- executed the handler is executed with it as an argument+orElse :: JenkinsT m a -> (JenkinsException -> JenkinsT m a) -> JenkinsT m a+orElse a b = liftJ (Or a b) --- | @orElse a b@ runs @a@ and only runs @b@ if @a@ has thrown a @JenkinsException@-orElse :: Jenkins a -> Jenkins a -> Jenkins a-orElse ja jb = liftJ (Or ja jb)+-- | A simpler exception handler+--+-- @+-- orElse_ a b = 'orElse' a (\_ -> b)+-- @+orElse_ :: JenkinsT m a -> JenkinsT m a -> JenkinsT m a+orElse_ a b = orElse a (\_ -> b)+{-# ANN orElse_ ("HLint: ignore Use const" :: String) #-} --- | Make local changes to the 'Request'-with :: (Request -> Request) -> Jenkins a -> Jenkins a-with f j = liftJ $ With f j id+-- | @locally f x@ modifies the base 'Request' with @f@ for the execution of @x@+-- (think 'Control.Monad.Trans.Reader.local')+--+-- This is useful for setting the appropriate headers, response timeouts and the like+locally :: (Request -> Request) -> JenkinsT m a -> JenkinsT m a+locally f j = liftJ (With f j id) --- | @POST@ job's @config.xml@ (or any other xml, really) in @xml-conduit@ format-postXML :: (forall f. Method Complete f) -> Document -> Jenkins ()-postXML m = with (requestHeaders <>~ [("Content-Type", "text/xml")]) . post m . renderLBS def+-- | Disconnect from Jenkins. The following actions are ignored.+disconnect :: JenkinsT m a+disconnect = liftJ Dcon --- | Make a bunch of queries 'concurrently'-traverseC :: (a -> Jenkins b) -> [a] -> Jenkins [b]-traverseC f = foldr go (return [])++-- | Run two actions concurrently+concurrently :: JenkinsT m a -> JenkinsT m b -> JenkinsT m (a, b)+concurrently ja jb = liftJ (Conc ja jb (,))++-- | Map every list element to an action, run them concurrently and collect the results+--+-- @'traverse' : 'Data.Traversable.traverse' :: 'concurrently' : 'Control.Applicative.liftA2' (,)@+traverse :: (a -> JenkinsT m b) -> [a] -> JenkinsT m [b]+traverse f = foldr go (return [])  where   go x xs = do (y, ys) <- concurrently (f x) xs; return (y : ys) --- | Make a bunch of queries 'concurrently' ignoring their results-traverseC_ :: (a -> Jenkins b) -> [a] -> Jenkins ()-traverseC_ f = foldr (\x xs -> () <$ concurrently (f x) xs) (return ())+-- | Map every list element to an action and run them concurrently ignoring the results+--+-- @'traverse_' : 'Data.Foldable.traverse_' :: 'concurrently' : 'Control.Applicative.liftA2' (,)@+traverse_ :: F.Foldable f => (a -> JenkinsT m b) -> f a -> JenkinsT m ()+traverse_ f = F.foldr (\x xs -> () <$ concurrently (f x) xs) (return ()) ++-- | Perform a @POST@ request to Jenkins with the XML document+--+-- Sets up the correct @Content-Type@ header. Mostly useful for updating @config.xml@+-- files for jobs, views, etc+postXml :: (forall f. Method Complete f) -> Lazy.ByteString -> JenkinsT m ()+postXml m = locally (\r -> r { requestHeaders = xmlHeader : requestHeaders r }) . post m+ where+  xmlHeader = ("Content-Type", "text/xml")+ -- | Reload jenkins configuration from disk ----- Calls @/reload@ and disconnects-reload :: Jenkins a+-- Performs @/reload@ and disconnects+reload :: JenkinsT m a reload = do post_ "reload"; disconnect  -- | Restart jenkins safely ----- Calls @/safeRestart@ and disconnects+-- Performs @/safeRestart@ and /disconnects/ -- -- @/safeRestart@ allows all running jobs to complete-restart :: Jenkins a+restart :: JenkinsT m a restart = do post_ "safeRestart"; disconnect --- | Force jenkins to restart without waiting for running jobs to finish+-- | Restart jenkins ----- Calls @/restart@ and disconnects-forceRestart :: Jenkins a+-- Performs @/restart@ and /disconnects/+--+-- @/restart@ restart Jenkins immediately, without waiting for the completion of+-- the building and/or waiting jobs+forceRestart :: JenkinsT m a forceRestart = do post_ "restart"; disconnect---- Disconnect from Jenkins. Any following queries won't be executed-disconnect :: Jenkins a-disconnect = liftJ Dcon-{-# INLINE disconnect #-}
src/Jenkins/Rest/Internal.hs view
@@ -1,289 +1,237 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_HADDOCK hide #-} -- | Jenkins REST API interface internals module Jenkins.Rest.Internal where  import           Control.Applicative-import           Control.Concurrent.Async (concurrently)-import           Control.Lens+#if ! MIN_VERSION_free(5,0,0)+import           Control.Applicative.Backwards (Backwards(..))+#endif+import           Control.Concurrent.Async.Lifted (concurrently)+import           Control.Exception (Exception(..))+import           Control.Exception.Lifted (bracket, catch, throwIO) import           Control.Monad-import           Control.Monad.Catch (MonadCatch, Exception(..), try, catch, throwM)-import           Control.Monad.Free.Church (F, iterM, liftF)+import           Control.Monad.Free.Church (liftF)+import           Control.Monad.Error (MonadError(..)) import           Control.Monad.IO.Class (MonadIO(..))-import           Control.Monad.Trans.Class (lift)-import           Control.Monad.Trans.Control (MonadTransControl(..))-import           Control.Monad.Trans.Reader (ReaderT, runReaderT, ask, local)-import           Control.Monad.Trans.Resource (ResourceT)+import           Control.Monad.Reader (MonadReader(..))+import           Control.Monad.State (MonadState(..))+import           Control.Monad.Trans.Free.Church (FT, iterTM)+import           Control.Monad.Trans.Class (MonadTrans(..))+import           Control.Monad.Trans.Control (MonadTransControl(..), MonadBaseControl(..))+import           Control.Monad.Trans.Reader (ReaderT)+import qualified Control.Monad.Trans.Reader as Reader import           Control.Monad.Trans.Maybe (MaybeT(..), mapMaybeT)+import           Control.Monad.Writer (MonadWriter(..)) import           Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString as Strict-import           Data.Conduit (ResumableSource)-import qualified Data.Conduit as C-import           Data.Data (Data, Typeable) import           Data.Text (Text) import qualified Data.Text.Encoding as Text-import           GHC.Generics (Generic)-import           Network.HTTP.Conduit+import           Data.Typeable (Typeable)+import           Network.HTTP.Client (Request, HttpException)+import qualified Network.HTTP.Client as Http+import qualified Network.HTTP.Client.TLS as Http import           Network.HTTP.Types (Status(..)) -import           Jenkins.Rest.Method (Method, Type(..), render, slash)-import qualified Network.HTTP.Conduit.Lens as Lens+import           Jenkins.Rest.Method.Internal (Method, Type(..), render, slash) -{-# ANN module ("HLint: ignore Use const" :: String) #-} {-# ANN module ("HLint: ignore Use join" :: String) #-}  --- | Jenkins REST API query sequence description-newtype Jenkins a = Jenkins { unJenkins :: F JenkinsF a }-  deriving (Functor, Applicative, Monad)+-- | The value of this type describes Jenkins REST API requests sequence+newtype JenkinsT m a = JenkinsT { unJenkinsT :: FT (JF m) m a }+  deriving (Functor) -instance MonadIO Jenkins where-  liftIO = liftJ . IO+instance MonadIO m => MonadIO (JenkinsT m) where+  liftIO = JenkinsT . liftIO --- | Jenkins REST API query-data JenkinsF a where-  Get  :: Method Complete f -> (ResumableSource (ResourceT IO) Strict.ByteString -> a) -> JenkinsF a-  Post :: (forall f. Method Complete f) -> ByteString -> a -> JenkinsF a-  Conc :: Jenkins a -> Jenkins b -> (a -> b -> c) -> JenkinsF c-  Or   :: Jenkins a -> Jenkins a -> JenkinsF a-  IO   :: IO a -> JenkinsF a-  With :: (Request -> Request) -> Jenkins b -> (b -> a) -> JenkinsF a-  Dcon :: JenkinsF a+instance MonadTrans JenkinsT where+  lift = JenkinsT . lift -instance Functor JenkinsF where+instance Applicative (JenkinsT m) where+  pure = JenkinsT . pure+#if MIN_VERSION_free(5,0,0)+  JenkinsT f <*> JenkinsT x = JenkinsT (f <*> x)+#else+  -- https://github.com/ekmett/free/pull/80+  JenkinsT f <*> JenkinsT x = JenkinsT (forwards (Backwards f <*> Backwards x))+#endif++instance Monad (JenkinsT m) where+  return = JenkinsT . return+  JenkinsT m >>= k = JenkinsT (m >>= unJenkinsT . k)++instance MonadReader r m => MonadReader r (JenkinsT m) where+  ask = JenkinsT ask+  local f = JenkinsT . local f . unJenkinsT++instance MonadWriter w m => MonadWriter w (JenkinsT m) where+  tell = JenkinsT . tell+  listen = JenkinsT . listen . unJenkinsT+  pass = JenkinsT . pass . unJenkinsT+  writer = JenkinsT . writer++instance MonadState s m => MonadState s (JenkinsT m) where+  get = JenkinsT get+  put = JenkinsT . put+  state = JenkinsT . state++instance MonadError e m => MonadError e (JenkinsT m) where+  throwError = JenkinsT . throwError+  m `catchError` f = JenkinsT (unJenkinsT m `catchError` (unJenkinsT . f))+++data JF m a where+  Get :: Method Complete f -> (ByteString -> a) -> JF n a+  Post :: (forall f. Method Complete f) -> ByteString -> a -> JF m a+  Conc :: JenkinsT m a -> JenkinsT m b -> (a -> b -> c) -> JF m c+  Or   :: JenkinsT m a -> (JenkinsException -> JenkinsT m a) -> JF m a+  With :: (Request -> Request) -> JenkinsT m b -> (b -> a) -> JF m a+  Dcon :: JF m a++instance Functor (JF m) where   fmap f (Get  m g)      = Get  m      (f . g)   fmap f (Post m body a) = Post m body (f a)   fmap f (Conc m n g)    = Conc m n    (\a b -> f (g a b))-  fmap f (Or a b)        = Or (fmap f a) (fmap f b)-  fmap f (IO a)          = IO (fmap f a)+  fmap f (Or a b)        = Or (fmap f a) (fmap f . b)   fmap f (With h j g)    = With h j    (f . g)   fmap _ Dcon            = Dcon --- | Lift 'JenkinsF' to 'Jenkins'-liftJ :: JenkinsF a -> Jenkins a-liftJ = Jenkins . liftF+-- | Lift 'JF' to 'JenkinsT'+liftJ :: JF m a -> JenkinsT m a+liftJ = JenkinsT . liftF --- | Jenkins connection settings------ '_jenkinsApiToken' may be user's password, Jenkins--- does not make any distinction between these concepts-data ConnectInfo = ConnectInfo-  { _jenkinsUrl      :: String -- ^ Jenkins URL, e.g. @http:\/\/example.com\/jenkins@-  , _jenkinsPort     :: Int    -- ^ Jenkins port, e.g. @8080@-  , _jenkinsUser     :: Text   -- ^ Jenkins user, e.g. @jenkins@-  , _jenkinsApiToken :: Text   -- ^ Jenkins user API token-  } deriving (Show, Eq, Typeable, Data, Generic) --- | The result of Jenkins REST API queries-data Result e v =-    Error e    -- ^ Exception @e@ was thrown while querying Jenkins-  | Disconnect -- ^ The client was explicitly disconnected-  | Result v   -- ^ Querying successfully finished the with value @v@-    deriving (Show, Eq, Ord, Typeable, Data, Generic)-+-- | The kind of exceptions that can be thrown by performing requests+-- to the Jenkins REST API newtype JenkinsException   = JenkinsHttpException HttpException     deriving (Show, Typeable)  instance Exception JenkinsException --- | Query Jenkins API using 'Jenkins' description------ Successful result is either 'Disconnect' or @ 'Result' v @------ If 'HttpException' was thrown by @http-conduit@, 'runJenkins' catches it--- and wraps in 'Error'. Other exceptions are /not/ catched-runJenkins :: HasConnectInfo t => t -> Jenkins a -> IO (Result JenkinsException a)-runJenkins conn jenk = either Error (maybe Disconnect Result) <$> try (runJenkinsInternal conn jenk) -runJenkinsInternal :: HasConnectInfo t => t -> Jenkins a -> IO (Maybe a)-runJenkinsInternal (view connectInfo -> ConnectInfo h p user token) jenk = do-  url <- parseUrl h-  withManager $ \m ->-    runReaderT (runMaybeT (iterJenkinsIO m jenk))-      . set Lens.port p-      . applyBasicAuth (Text.encodeUtf8 user) (Text.encodeUtf8 token)+runInternal+  :: (MonadIO m, MonadBaseControl IO m)+  => String -> Text -> Text -> JenkinsT m a -> m (Maybe a)+runInternal h user token jenk = do+  url <- wrapException (liftIO (Http.parseUrl h))+  bracket (newManager Http.tlsManagerSettings) closeManager $ \m ->+    Reader.runReaderT (runMaybeT (runInterpT (iterInterpT m jenk)))+      . Http.applyBasicAuth (Text.encodeUtf8 user) (Text.encodeUtf8 token)       $ url +newManager :: MonadIO m => Http.ManagerSettings -> m Http.Manager+newManager = liftIO . Http.newManager --- | A prism into Jenkins error-_Error :: Prism (Result e a) (Result e' a) e e'-_Error = prism Error $ \case-  Error e    -> Right e-  Disconnect -> Left Disconnect-  Result a   -> Left (Result a)-{-# INLINE _Error #-}+closeManager :: MonadIO m => Http.Manager -> m ()+closeManager = liftIO . Http.closeManager --- | A prism into disconnect-_Disconnect :: Prism' (Result e a) ()-_Disconnect = prism' (\_ -> Disconnect) $ \case-  Disconnect -> Just ()-  _          -> Nothing-{-# INLINE _Disconnect #-}+newtype InterpT m a = InterpT+  { runInterpT :: MaybeT (ReaderT Request m) a+  } deriving (Functor) --- | A prism into result-_Result :: Prism (Result e a) (Result e b) a b-_Result = prism Result $ \case-  Error e    -> Left (Error e)-  Disconnect -> Left Disconnect-  Result a   -> Right a-{-# INLINE _Result #-}+instance (Functor m, Monad m) => Applicative (InterpT m) where+  pure = return+  (<*>) = ap --- | Interpret 'JenkinsF' AST in 'IO'-iterJenkinsIO-  :: Manager-  -> Jenkins a-  -> MaybeT (ReaderT Request (ResourceT IO)) a-iterJenkinsIO manager = iterJenkins (interpreter manager)+instance Monad m => Monad (InterpT m) where+  return = InterpT . return+  InterpT m >>= k = InterpT (m >>= runInterpT . k) --- | Tear down 'JenkinsF' AST with a 'JenkinsF'-algebra-iterJenkins :: Monad m => (JenkinsF (m a) -> m a) -> Jenkins a -> m a-iterJenkins go = iterM go . unJenkins+instance (Functor m, Monad m) => Alternative (InterpT m) where+  empty = mzero+  (<|>) = mplus --- | 'JenkinsF' AST interpreter+instance Monad m => MonadPlus (InterpT m) where+  mzero = InterpT mzero+  InterpT x `mplus` InterpT y = InterpT (x `mplus` y)++instance MonadTrans InterpT where+  lift = InterpT . lift . lift++-- | Interpret the 'JF' AST in 'InterpT'+iterInterpT :: (MonadIO m, MonadBaseControl IO m) => Http.Manager -> JenkinsT m a -> InterpT m a+iterInterpT manager = iter (interpreter manager)++-- | Tear down the 'JF' AST with a 'JF'-algebra+iter+  :: (Monad m, Monad (t m), MonadTrans t)+  => (JF m (t m a) -> t m a) -> JenkinsT m a -> t m a+iter go = iterTM go . unJenkinsT++-- | 'JF' AST interpreter interpreter-  :: Manager-  -> JenkinsF (MaybeT (ReaderT Request (ResourceT IO)) a)-  -> MaybeT (ReaderT Request (ResourceT IO)) a+  :: forall m a. (MonadIO m, MonadBaseControl IO m)+  => Http.Manager+  -> JF m (InterpT m a) -> InterpT m a interpreter man = go where-  go (Get m next) = do-    req <- lift ask-    res <- lift . lift $ http (prepareGet m req) man `withException` JenkinsHttpException-    next (responseBody res)-  go (Post m body next) = do-    req <- lift ask-    res <- lift . lift $ http (preparePost m body req) man `withException` JenkinsHttpException-    ()  <- lift . lift $ C.closeResumableSource (responseBody res)-    next+  go :: JF m (InterpT m a) -> InterpT m a+  go (Get m next) = InterpT $ do+    req <- lift Reader.ask+    res <- liftIO $ wrapException (liftM Http.responseBody (Http.httpLbs (prepareGet m req) man))+    runInterpT (next res)+  go (Post m body next) = InterpT $ do+    req <- lift Reader.ask+    _   <- liftIO $ wrapException (Http.httpNoBody (preparePost m body req) man)+    runInterpT next   go (Conc ja jb next) = do-    (a, b) <- intoIO man $ \run -> concurrently (run ja) (run jb)-    c      <- outoIO (return a)-    d      <- outoIO (return b)+    (a, b) <- intoM man $ \run -> concurrently (run ja) (run jb)+    c      <- outoM (return a)+    d      <- outoM (return b)     next c d   go (Or ja jb) = do-    res  <- intoIO man $ \run -> run ja `catch` \(JenkinsHttpException _) -> run jb-    next <- outoIO (return res)+    res  <- intoM man $ \run -> run ja `catch` (run . jb)+    next <- outoM (return res)     next-  go (IO action) = join (liftIO action)-  go (With f jenk next) = do-    res <- mapMaybeT (local f) (iterJenkinsIO man jenk)-    next res+  go (With f jenk next) = InterpT $ do+    res <- mapMaybeT (Reader.local f) (runInterpT (iterInterpT man jenk))+    runInterpT (next res)   go Dcon = mzero -intoIO-  :: Monad m-  => Manager-  -> ((forall b. Jenkins b -> IO (StT ResourceT (StT (ReaderT Request) (StT MaybeT b)))) -> m a)-  -> MaybeT (ReaderT Request (ResourceT m)) a-intoIO m f =-  liftWith $ \run' -> liftWith $ \run'' -> liftWith $ \run''' ->+intoM+  :: forall m a. (MonadIO m, MonadBaseControl IO m)+  => Http.Manager+  -> ((forall b. JenkinsT m b -> m (StT (ReaderT Request) (StT MaybeT b))) -> m a)+  -> InterpT m a+intoM m f = InterpT $+  liftWith $ \run' -> liftWith $ \run''' ->     let-      run :: Jenkins t -> IO (StT ResourceT (StT (ReaderT Request) (StT MaybeT t)))-      run = run''' . run'' . run' . iterJenkinsIO m+      run :: JenkinsT m t -> m (StT (ReaderT Request) (StT MaybeT t))+      run = run''' . run' . runInterpT . iterInterpT m     in       f run -outoIO-  :: IO (StT ResourceT (StT (ReaderT Request) (StT MaybeT b)))-  -> MaybeT (ReaderT Request (ResourceT IO)) b-outoIO = restoreT . restoreT . restoreT+outoM :: Monad m => m (StT (ReaderT Request) (StT MaybeT b)) -> InterpT m b+outoM = InterpT . restoreT . restoreT  prepareGet :: Method Complete f -> Request -> Request-prepareGet m = set Lens.method "GET" . over Lens.path (`slash` render m)+prepareGet m r = r+  { Http.method = "GET"+  , Http.path   = Http.path r `slash` render m+  }  preparePost :: Method Complete f -> ByteString -> Request -> Request-preparePost m body =-    set Lens.checkStatus statusCheck-  . set Lens.redirectCount 0-  . set Lens.requestBody (RequestBodyLBS body)-  . set Lens.method "POST"-  . over Lens.path (`slash` render m)+preparePost m body r = r+  { Http.checkStatus   = statusCheck+  , Http.redirectCount = 0+  , Http.requestBody   = Http.RequestBodyLBS body+  , Http.method        = "POST"+  , Http.path          = Http.path r `slash` render m+  }  where   statusCheck s@(Status st _) hs cookie_jar =-    if 200 <= st && st < 400 then Nothing else Just . toException $ StatusCodeException s hs cookie_jar--withException :: (MonadCatch m, Exception e, Exception e') => m a -> (e -> e') -> m a-withException io f = io `catch` \e -> throwM (f e)----- | Default Jenkins connection settings------ @--- defaultConnectInfo = ConnectInfo---   { _jenkinsUrl      = \"http:\/\/example.com\/jenkins\"---   , _jenkinsPort     = 8080---   , _jenkinsUser     = \"jenkins\"---   , _jenkinsApiToken = \"\"---   }--- @-defaultConnectInfo :: ConnectInfo-defaultConnectInfo = ConnectInfo-  { _jenkinsUrl      = "http://example.com/jenkins"-  , _jenkinsPort     = 8080-  , _jenkinsUser     = "jenkins"-  , _jenkinsApiToken = ""-  }---- | Convenience class aimed at elimination of long--- chains of lenses to access jenkins connection configuration------ For example, if you have a configuration record in your application:------ @--- data Config = Config---   { ...---   , _jenkinsConnectInfo :: ConnectInfo---   , ...---   }--- @------ you can make it an instance of 'HasConnectInfo':------ @--- instance HasConnectInfo Config where---   connectInfo f x = (\p -> x { _jenkinsConnectInfo = p }) \<$\> f (_jenkinsConnectInfo x)--- @------ and then use e.g. @view jenkinsUrl config@ to get the url part of the jenkins connection-class HasConnectInfo t where-  connectInfo :: Lens' t ConnectInfo--instance HasConnectInfo ConnectInfo where-  connectInfo = id-  {-# INLINE connectInfo #-}---- | A lens into Jenkins URL-jenkinsUrl :: HasConnectInfo t => Lens' t String-jenkinsUrl = connectInfo . \f x ->  f (_jenkinsUrl x) <&> \p -> x { _jenkinsUrl = p }-{-# INLINE jenkinsUrl #-}---- | A lens into Jenkins port-jenkinsPort :: HasConnectInfo t => Lens' t Int-jenkinsPort = connectInfo . \f x -> f (_jenkinsPort x) <&> \p -> x { _jenkinsPort = p }-{-# INLINE jenkinsPort #-}---- | A lens into Jenkins user-jenkinsUser :: HasConnectInfo t => Lens' t Text-jenkinsUser = connectInfo . \f x -> f (_jenkinsUser x) <&> \p -> x { _jenkinsUser = p }-{-# INLINE jenkinsUser #-}---- | A lens into Jenkins user API token-jenkinsApiToken :: HasConnectInfo t => Lens' t Text-jenkinsApiToken = connectInfo . \f x -> f (_jenkinsApiToken x) <&> \p -> x { _jenkinsApiToken = p }-{-# INLINE jenkinsApiToken #-}+    if 200 <= st && st < 400 then Nothing else Just . toException $ Http.StatusCodeException s hs cookie_jar --- | A lens into Jenkins password------ @--- jenkinsPassword = jenkinsApiToken--- @-jenkinsPassword :: HasConnectInfo t => Lens' t Text-jenkinsPassword = jenkinsApiToken-{-# INLINE jenkinsPassword #-}+wrapException :: MonadBaseControl IO m => m a -> m a+wrapException m = m `catch` (throwIO .  JenkinsHttpException)
src/Jenkins/Rest/Method.hs view
@@ -1,303 +1,195 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE StandaloneDeriving #-}--- | Jenkins REST API method construction+{-# LANGUAGE RankNTypes #-}+-- | Jenkins REST API methods module Jenkins.Rest.Method-  ( -- * Types-    Method-  , Type(..)-  , Format-  , As-    -- * Method construction-  , text, int-  , (-?-), (-/-), (-=-), (-&-)+  ( -- * Construct URLs+    -- ** Path+    text+  , int+  , (-/-)+    -- ** Query+  , (-=-)+  , (-&-)   , query-  , as-  , JSONy(..)-  , XMLy(..)-  , Pythony(..)-    -- * Shortcuts-  , job+    -- ** Put together the segments and the query+  , (-?-)+    -- ** Format+  , Formatter+  , json+  , xml+  , python+  , plain+  , -- * Shortcuts+    job   , build   , view   , queue   , overallLoad   , computer-    -- * Rendering-  , render-  , slash+    -- * Types+  , Method+  , Type(..)+  , Format(..)   ) where -import           Data.ByteString (ByteString)-import qualified Data.ByteString as B-import           Data.Data (Data, Typeable)-import           Data.Monoid (Monoid(..), (<>))-import           Data.String (IsString(..))-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import           Data.Text (Text)-import           GHC.Generics (Generic)-import           Network.URI (escapeURIChar, isUnreserved)+import Data.Text (Text) +import Jenkins.Rest.Method.Internal++ -- $setup+-- >>> :set -XDataKinds -- >>> :set -XOverloadedStrings+-- >>> class P t where pp :: Method t f -> Data.ByteString.ByteString+-- >>> instance P Complete where pp = render+-- >>> instance P Query    where pp = renderQ'+-- >>> let pp' = render  -infix  1 :~?, -?--infix  3 :~@, `as`-infix  7 :~=, -=--infixr 5 :~/, -/-, :~&, -&-+infix  1 -?-+infix  7 -=-+infixr 5 -/-, -&- --- | Jenkins RESTFul API method encoding-data Method :: Type -> Format -> * where-  Empty :: Method t f-  Text  :: Text -> Method Complete f-  (:~/)  :: Method Complete f -> Method Complete f -> Method Complete f-  (:~@)  :: Method Complete f -> As f -> Method Complete f-  (:~=)  :: Text -> Maybe Text -> Method Query f-  (:~&)  :: Method Query f -> Method Query f -> Method Query f-  (:~?)  :: Method Complete f -> Method Query f -> Method Complete f -deriving instance Show (As f) => Show (Method t f)---- | Only to support number literals-instance t ~ Complete => Num (Method t f) where-  (+)         = error "Method.(+): not supposed to be used"-  (-)         = error "Method.(-): not supposed to be used"-  (*)         = error "Method.(*): not supposed to be used"-  abs         = error "Method.abs: not supposed to be used"-  signum      = error "Method.signum: not supposed to be used"-  fromInteger = fromString . show--instance IsString (Method Complete f) where-  fromString = Text . T.pack--instance IsString (Method Query f) where-  fromString str = T.pack str :~= Nothing---- | Method types-data Type = Query | Complete-  deriving (Show, Eq, Typeable, Data, Generic)---- | Response formats-data Format = JSON | XML | Python-  deriving (Show, Eq, Typeable, Data, Generic)---- | Response format singleton type-data As :: Format -> * where-  AsJSON   :: As JSON-  AsXML    :: As XML-  AsPython :: As Python--deriving instance Show (As f)-deriving instance Eq (As f)---- | Convert 'Text' to 'Method'+-- | Use a string as an URI segment+--+-- >>> pp (text "foo")+-- "foo"+--+-- /Note:/ with @-XOverloadedStrings@ extension enabled it's possible to use string+-- literals as segments of the Jenkins API method URL+--+-- >>> pp' "foo"+-- "foo"+--+-- /Note:/ don't put @/@ in the string literal unless you want it URL-encoded,+-- use @(-/-)@ instead+--+-- >>> pp' "foo/bar"+-- "foo%2Fbar" text :: Text -> Method Complete f text = Text --- | Convert 'Integer' to 'Method'-int :: Integer -> Method Complete f-int = fromInteger+-- | Use an integer as an URI segment+--+-- >>> pp (int 4)+-- "4"+int :: Int -> Method Complete f+int = fromIntegral --- | Combine 2 paths+-- | Combine two paths+--+-- >>> pp ("foo" -/- "bar" -/- "baz")+-- "foo/bar/baz" (-/-) :: Method Complete f -> Method Complete f -> Method Complete f-(-/-) = (:~/)---- | Combine 2 queries-(-&-) :: Method Query f -> Method Query f -> Method Query f-(-&-) = (:~&)+(-/-) = (:/) --- | Make a field-value pair+-- | Make a key-value pair+--+-- >>> pp ("foo" -=- "bar")+-- "foo=bar" (-=-) :: Text -> Text -> Method Query f-x -=- y = x :~= Just y---- | Choose response format-as :: Method Complete f -> As f -> Method Complete f-as = (:~@)---- | JSON response format-class JSONy t where-  json :: t JSON--instance JSONy As where-  json = AsJSON--instance t ~ Complete => JSONy (Method t) where-  json = "" `as` json---- | XML response format-class XMLy t where-  xml :: t XML--instance XMLy As where-  xml = AsXML--instance t ~ Complete => XMLy (Method t) where-  xml = "" `as` xml---- | Python response format-class Pythony t where-  python :: t Python--instance Pythony As where-  python = AsPython--instance t ~ Complete => Pythony (Method t) where-  python = "" `as` python+x -=- y = x := Just y --- | Combine path and query-(-?-) :: Method Complete f -> Method Query f -> Method Complete f-(-?-) = (:~?)+-- | Create the union of two queries+--+-- >>> pp ("foo" -=- "bar" -&- "baz")+-- "foo=bar&baz"+(-&-) :: Method Query f -> Method Query f -> Method Query f+(-&-) = (:&) --- | List-to-query convenience combinator+-- | Take a list of key-value pairs and render them as a query ----- >>> render (query [("foo", Nothing), ("bar", Just "baz"), ("quux", Nothing)])+-- >>> pp (query [("foo", Nothing), ("bar", Just "baz"), ("quux", Nothing)]) -- "foo&bar=baz&quux" ----- >>> render (query [])+-- >>> pp (query []) -- "" query :: [(Text, Maybe Text)] -> Method Query f-query [] = Empty-query xs = foldr1 (:~&) (map (uncurry (:~=)) xs)-+query = foldr ((:&) . uncurry (:=)) Empty --- | Render 'Method' to something that can be sent over the wire------ >>> render ("" `as` xml)--- "api/xml"------ >>> render xml--- "api/xml"------ >>> render ("job" -/- 7 `as` xml)--- "job/7/api/xml"------ >>> render ("job" -/- 7 `as` xml)--- "job/7/api/xml"------ >>> render ("job" -/- 7 `as` json)--- "job/7/api/json"------ >>> render (text "restart")--- "restart"------ >>> render ("job" -?- "name" -=- "foo" -&- "title" -=- "bar")--- "job?name=foo&title=bar"------ >>> render ("job" -?- "name" -&- "title" -=- "bar")--- "job?name&title=bar"------ >>> render ("job" -/- 7 `as` json -?- "name" -&- "title" -=- "bar")--- "job/7/api/json?name&title=bar"+-- | Put path and query together ----- >>> render ("job" -/- "ДМИТРИЙ" `as` xml)--- "job/%D0%94%D0%9C%D0%98%D0%A2%D0%A0%D0%98%D0%99/api/xml"-render :: Method t f -> ByteString-render Empty            = ""-render (Text s)         = renderText s-render (x :~/ y)        = render x `slash` render y-render (x :~@ f)        =-  let prefix  = render x-      postfix = renderFormat f-  in if B.null prefix then  "api" `slash` postfix else prefix `slash` "api" `slash` postfix-render (x :~= Just y)   = renderText x `equals` renderText y-render (x :~= Nothing)  = renderText x-render (x :~& y)        = render x `ampersand` render y-render (x :~? y)        = render x `question` render y--renderFormat :: IsString s => As f -> s-renderFormat AsJSON   = "json"-renderFormat AsXML    = "xml"-renderFormat AsPython = "python"+-- >>> pp ("qux" -/- "quux" -?- "foo" -=- "bar" -&- "baz")+-- "qux/quux?foo=bar&baz"+(-?-) :: Method Complete f -> Method Query f -> Method Complete f+(-?-) = (:?) --- | Render unicode text as a query string------ >>> renderText "foo-bar-baz"--- "foo-bar-baz"------ >>> renderText "foo bar baz"--- "foo%20bar%20baz"+-- | Append the JSON formatting request to the method URL ----- >>> renderText "ДМИТРИЙ МАЛИКОВ"--- "%D0%94%D0%9C%D0%98%D0%A2%D0%A0%D0%98%D0%99%20%D0%9C%D0%90%D0%9B%D0%98%D0%9A%D0%9E%D0%92"-renderText :: Text -> ByteString-renderText = T.encodeUtf8 . T.concatMap (T.pack . escapeURIChar isUnreserved)---- | Insert \"\/\" between two 'String'-like things and concatenate everything.-slash :: (IsString m, Monoid m) => m -> m -> m-slash = insert "/"---- | Insert \"=\" between two 'String'-like things and concatenate everything.-equals :: (IsString m, Monoid m) => m -> m -> m-equals = insert "="---- | Insert \"&\" between two 'String'-like things and concatenate everything.-ampersand :: (IsString m, Monoid m) => m -> m -> m-ampersand = insert "&"---- | Insert \"?\" between two 'String'-like things and concatenate everything.-question :: (IsString m, Monoid m) => m -> m -> m-question = insert "?"+-- >>> format json "foo"+-- "foo/api/json"+json :: Formatter Json+json = Formatter (\m -> m :@ SJson)+{-# ANN json ("HLint: ignore Avoid lambda" :: String) #-} --- | Insert 'String'-like thing between two 'String'-like things and concatenate everything.+-- | Append the XML formatting request to the method URL ----- >>> "foo" `slash` "bar"--- "foo/bar"+-- >>> format xml "foo"+-- "foo/api/xml"+xml :: Formatter Xml+xml = Formatter (\m -> m :@ SXml)+{-# ANN xml ("HLint: ignore Avoid lambda" :: String) #-}++-- | Append the Python formatting request to the method URL ----- >>> "" `ampersand` "foo"--- "&foo"+-- >>> format python "foo"+-- "foo/api/python"+python :: Formatter Python+python = Formatter (\m -> m :@ SPython)+{-# ANN python ("HLint: ignore Avoid lambda" :: String) #-}++-- | The formatter that does exactly nothing ----- >>> "foo" `question` ""--- "foo?"-insert :: (IsString m, Monoid m) => m -> m -> m -> m-insert t x y = x <> t <> y+-- >>> format plain "foo"+-- "foo"+plain :: Formatter f+plain = Formatter (\m -> m)+{-# ANN plain ("HLint: ignore Use id" :: String) #-}  --- | Job API method+-- | Job data ----- >>> render (job "name" `as` json)+-- >>> format json (job "name") -- "job/name/api/json"+--+-- >>> pp (job "name" -/- "config.xml")+-- "job/name/config.xml" job :: Text -> Method Complete f job name = "job" -/- text name --- | Job build API method+-- | Job build data ----- >>> render (build "name" 4 `as` json)+-- >>> format json (build "name" 4) -- "job/name/4/api/json"-build :: Integral a => Text -> a -> Method Complete f-build name num = "job" -/- text name -/- int (toInteger num)+build :: Text -> Int -> Method Complete f+build name num = "job" -/- text name -/- int num --- | View API method+-- | View data ----- >>> render (view "name" `as` xml)+-- >>> format xml (view "name") -- "view/name/api/xml" view :: Text -> Method Complete f view name = "view" -/- text name --- | Queue API method+-- | Build queue data ----- >>> render (queue `as` python)+-- >>> format python queue -- "queue/api/python" queue :: Method Complete f queue = "queue" --- | Statistics API method+-- | Server statistics ----- >>> render (overallLoad `as` xml)+-- >>> format xml overallLoad -- "overallLoad/api/xml" overallLoad :: Method Complete f overallLoad = "overallLoad" --- | Node API method+-- | Nodes data ----- >>> render (computer `as` python)+-- >>> format python computer -- "computer/api/python" computer :: Method Complete f computer = "computer"
+ src/Jenkins/Rest/Method/Internal.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# OPTIONS_HADDOCK hide #-}+-- | Jenkins REST API method construction+module Jenkins.Rest.Method.Internal where++import           Control.Applicative+import           Data.ByteString (ByteString)+import           Data.Data (Data, Typeable)+import           Data.Monoid (Monoid(..), (<>))+import           Data.String (IsString(..))+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import           Data.Text (Text)+import           Network.URI (escapeURIChar, isUnreserved)++-- $setup+-- >>> :set -XOverloadedStrings+++infix  1 :?+infix  3 :@+infix  7 :=+infixr 5 :/, :&++-- | Jenkins RESTFul API method encoding+data Method :: Type -> Format -> * where+  Empty :: Method Query f+  Text  :: Text -> Method Complete f+  (:/)  :: Method Complete f -> Method Complete f -> Method Complete f+  (:=)  :: Text -> Maybe Text -> Method Query f+  (:&)  :: Method Query f -> Method Query f -> Method Query f+  (:?)  :: Method Complete f -> Method Query f -> Method Complete f+  (:@)  :: Method Complete f -> SFormat f -> Method Complete f++deriving instance Show (SFormat f) => Show (Method t f)++-- | Only to support numeric literals+instance t ~ Complete => Num (Method t f) where+  (+)         = error "Method.(+): not supposed to be used"+  (-)         = error "Method.(-): not supposed to be used"+  (*)         = error "Method.(*): not supposed to be used"+  abs         = error "Method.abs: not supposed to be used"+  signum      = error "Method.signum: not supposed to be used"+  fromInteger = fromString . show++instance IsString (Method Complete f) where+  fromString = Text . fromString++instance IsString (Method Query f) where+  fromString str = fromString str := Nothing++-- | Method types+data Type = Query | Complete+  deriving (Show, Eq, Typeable, Data)++-- | Response formats+data Format = Json | Xml | Python+  deriving (Show, Eq, Typeable, Data)++data SFormat :: Format -> * where+  SJson   :: SFormat Json+  SXml    :: SFormat Xml+  SPython :: SFormat Python+++-- | 'Formatter's know how to append the \"api/$format\" string to the method URL+newtype Formatter g = Formatter+  { unFormatter :: (forall f. Method Complete f) -> Method Complete g+  }++format :: Formatter f -> (forall g. Method Complete g) -> ByteString+format f m = render (unFormatter f m)++-- | Render 'Method' to something that can be sent over the wire+render :: Method Complete f -> ByteString+render m = maybe id (flip (insert "?")) (renderQ m) . maybe id (flip (insert "/")) (renderF m) . renderP $ m++-- | Render the method path+renderP :: Method Complete f -> ByteString+renderP (Text s) = renderT s+renderP (x :/ y) = renderP x `slash` renderP y+renderP (x :? _) = renderP x+renderP (x :@ _) = renderP x++-- | Render the query string+renderQ :: Method Complete f -> Maybe ByteString+renderQ (Text _)  = Nothing+renderQ (q :/ q') = renderQ q <|> renderQ q'+renderQ (q :@ _)  = renderQ q+renderQ (_ :? q)  = Just (renderQ' q)++renderQ' :: Method Query f -> ByteString+renderQ' (x :& y)       = insert "&" (renderQ' x) (renderQ' y)+renderQ' (x := Just y)  = insert "=" (renderT x)  (renderT y)+renderQ' (x := Nothing) = renderT x+renderQ' Empty          = renderT ""++-- | Render the response format string+renderF :: Method Complete f -> Maybe ByteString+renderF (_ :@ SJson)   = Just "api/json"+renderF (_ :@ SXml)    = Just "api/xml"+renderF (_ :@ SPython) = Just "api/python"+renderF _              = Nothing++-- | Render unicode text as a query string+--+-- >>> renderT "foo-bar-baz"+-- "foo-bar-baz"+--+-- >>> renderT "foo bar baz"+-- "foo%20bar%20baz"+--+-- >>> renderT "ДМИТРИЙ МАЛИКОВ"+-- "%D0%94%D0%9C%D0%98%D0%A2%D0%A0%D0%98%D0%99%20%D0%9C%D0%90%D0%9B%D0%98%D0%9A%D0%9E%D0%92"+renderT :: Text -> ByteString+renderT = Text.encodeUtf8 . Text.concatMap (fromString . escapeURIChar isUnreserved)++-- | Insert \"\/\" between two 'String'-like things and concatenate everything.+slash :: (IsString m, Monoid m, Eq m) => m -> m -> m+slash = insert "/"++-- | Insert 'String'-like thing between two 'String'-like things and concatenate everything.+--+-- >>> "foo" `slash` "bar"+-- "foo/bar"+--+-- >>> "foo" `slash` ""+-- "foo"+insert :: (Monoid m, Eq m) => m -> m -> m -> m+insert t x y+  | x == mempty = y+  | y == mempty = x+  | otherwise   = x <> t <> y
+ src/Network/HTTP/Client/Lens.hs view
@@ -0,0 +1,316 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+-- | Optics for @http-client@ types+module Network.HTTP.Client.Lens+  ( -- * 'Request' lenses+    method+  , secure+  , host+  , port+  , path+  , queryString+  , requestBody+  , requestHeaders+  , proxy+  , hostAddress+  , rawBody+  , decompress+  , redirectCount+  , checkStatus+  , responseTimeout+  , cookieJar+  , getConnectionWrapper+    -- * 'HttpException' prisms+  , AsHttpException(..)+  , _StatusCodeException+  , _InvalidUrlException+  , _TooManyRedirects+  , _UnparseableRedirect+  , _TooManyRetries+  , _HttpParserException+  , _HandshakeFailed+  , _OverlongHeaders+  , _ResponseTimeout+  , _FailedConnectionException+  , _ExpectedBlankAfter100Continue+  , _InvalidStatusLine+  , _InvalidHeader+  , _InternalIOException+  , _ProxyConnectException+  , _NoResponseDataReceived+  , _TlsException+  , _TlsNotSupported+  , _ResponseBodyTooShort+  , _InvalidChunkHeaders+  , _IncompleteHeaders+  ) where++import           Control.Exception (SomeException, IOException)+import           Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as Lazy+import           Data.Word (Word64)+import qualified Network.HTTP.Client as H+import qualified Network.HTTP.Client.Internal as H+import qualified Network.HTTP.Types as H+import           Network.Socket (HostAddress)++import           Network.HTTP.Client.Lens.Internal+++-- | 'H.method' lens+method :: Lens' H.Request H.Method+method f req = f (H.method req) <&> \m' -> req { H.method = m' }+{-# INLINE method #-}++-- | 'H.secure' lens+secure :: Lens' H.Request Bool+secure f req = f (H.secure req) <&> \s' -> req { H.secure = s' }+{-# INLINE secure #-}++-- | 'H.host' lens+host :: Lens' H.Request ByteString+host f req = f (H.host req) <&> \h' -> req { H.host = h' }+{-# INLINE host #-}++-- | 'H.port' lens+port :: Lens' H.Request Int+port f req = f (H.port req) <&> \p' -> req { H.port = p' }+{-# INLINE port #-}++-- | 'H.path' lens+path :: Lens' H.Request ByteString+path f req = f (H.path req) <&> \p' -> req { H.path = p' }+{-# INLINE path #-}++-- | 'H.queryString' lens+queryString :: Lens' H.Request ByteString+queryString f req = f (H.queryString req) <&> \qs' -> req { H.queryString = qs' }+{-# INLINE queryString #-}++-- | 'H.requestBody' lens+requestBody :: Lens' H.Request H.RequestBody+requestBody f req = f (H.requestBody req) <&> \rb' -> req { H.requestBody = rb' }+{-# INLINE requestBody #-}++-- | 'H.requestHeaders' lens+requestHeaders :: Lens' H.Request H.RequestHeaders+requestHeaders f req = f (H.requestHeaders req) <&> \rh' -> req { H.requestHeaders = rh' }+{-# INLINE requestHeaders #-}++-- | 'H.proxy'+proxy :: Lens' H.Request (Maybe H.Proxy)+proxy f req = f (H.proxy req) <&> \mp' -> req { H.proxy = mp' }+{-# INLINE proxy #-}++-- | 'H.hostAddress'+hostAddress :: Lens' H.Request (Maybe HostAddress)+hostAddress f req = f (H.hostAddress req) <&> \ha' -> req { H.hostAddress = ha' }+{-# INLINE hostAddress #-}++-- | 'H.rawBody'+rawBody :: Lens' H.Request Bool+rawBody f req = f (H.rawBody req) <&> \b' -> req { H.rawBody = b' }+{-# INLINE rawBody #-}++-- | 'H.decompress'+decompress :: Lens' H.Request (ByteString -> Bool)+decompress f req = f (H.decompress req) <&> \btb' -> req { H.decompress = btb' }+{-# INLINE decompress #-}++-- | 'H.redirectCount' lens+redirectCount :: Lens' H.Request Int+redirectCount f req = f (H.redirectCount req) <&> \rc' -> req { H.redirectCount = rc' }+{-# INLINE redirectCount #-}++-- | 'H.checkStatus' lens+checkStatus :: Lens' H.Request (H.Status -> H.ResponseHeaders -> H.CookieJar -> Maybe SomeException)+checkStatus f req = f (H.checkStatus req) <&> \cs' -> req { H.checkStatus = cs' }+{-# INLINE checkStatus #-}++-- | 'H.responseTimeout' lens+responseTimeout :: Lens' H.Request (Maybe Int)+responseTimeout f req = f (H.responseTimeout req) <&> \rt' -> req { H.responseTimeout = rt' }+{-# INLINE responseTimeout #-}++-- | 'H.cookieJar'+cookieJar :: Lens' H.Request (Maybe H.CookieJar)+cookieJar f req = f (H.cookieJar req) <&> \mcj' -> req { H.cookieJar = mcj' }+{-# INLINE cookieJar #-}++-- | 'H.getConnectionWrapper'+getConnectionWrapper+  :: Lens' H.Request+      ( Maybe Int+      -> H.HttpException+      -> IO (H.ConnRelease, H.Connection, H.ManagedConn)+      -> IO (Maybe Int, (H.ConnRelease, H.Connection, H.ManagedConn))+      )+getConnectionWrapper f req =+  f (H.getConnectionWrapper req) <&> \wat' -> req { H.getConnectionWrapper = wat' }+{-# INLINE getConnectionWrapper #-}+++-- | @http-conduit@ exceptions+class AsHttpException t where+  -- | @http-conduit@ exceptions overloading+  _HttpException :: Prism' t H.HttpException++instance AsHttpException H.HttpException where+  _HttpException = id+  {-# INLINE _HttpException #-}++instance AsHttpException SomeException where+  _HttpException = exception+  {-# INLINE _HttpException #-}++-- | 'H.StatusCodeException' exception+_StatusCodeException :: AsHttpException t => Prism' t (H.Status, H.ResponseHeaders, H.CookieJar)+_StatusCodeException = _HttpException . prism' (uncurry3 H.StatusCodeException) go where+  go (H.StatusCodeException s rh cj) = Just (s, rh, cj)+  go _ = Nothing+{-# INLINE _StatusCodeException #-}++-- | 'H.InvalidUrlException' exception+_InvalidUrlException :: AsHttpException t => Prism' t (String, String)+_InvalidUrlException = _HttpException . prism' (uncurry H.InvalidUrlException) go where+  go (H.InvalidUrlException s s') = Just (s, s')+  go _ = Nothing+{-# INLINE _InvalidUrlException #-}++-- | 'H.TooManyRedirects' exception+_TooManyRedirects :: AsHttpException t => Prism' t [H.Response Lazy.ByteString]+_TooManyRedirects = _HttpException . prism' H.TooManyRedirects go where+  go (H.TooManyRedirects rs) = Just rs+  go _ = Nothing+{-# INLINE _TooManyRedirects #-}++-- | 'H.UnparseableRedirect' exception+_UnparseableRedirect :: AsHttpException t => Prism' t (H.Response Lazy.ByteString)+_UnparseableRedirect = _HttpException . prism' H.UnparseableRedirect go where+  go (H.UnparseableRedirect r) = Just r+  go _ = Nothing+{-# INLINE _UnparseableRedirect #-}++-- | 'H.TooManyRetries' exception+_TooManyRetries :: AsHttpException t => Prism' t ()+_TooManyRetries = _HttpException . prism' (const H.TooManyRetries) go where+  go H.TooManyRetries = Just ()+  go _ = Nothing+{-# INLINE _TooManyRetries #-}++-- | 'H.HttpParserException' exception+_HttpParserException :: AsHttpException t => Prism' t String+_HttpParserException = _HttpException . prism' H.HttpParserException go where+  go (H.HttpParserException s) = Just s+  go _ = Nothing+{-# INLINE _HttpParserException #-}++-- | 'H.HandshakeFailed' exception+_HandshakeFailed :: AsHttpException t => Prism' t ()+_HandshakeFailed = _HttpException . prism' (const H.HandshakeFailed) go where+  go H.HandshakeFailed = Just ()+  go _ = Nothing+{-# INLINE _HandshakeFailed #-}++-- | 'H.OverlongHeaders' exception+_OverlongHeaders :: AsHttpException t => Prism' t ()+_OverlongHeaders = _HttpException . prism' (const H.OverlongHeaders) go where+  go H.OverlongHeaders = Just ()+  go _ = Nothing+{-# INLINE _OverlongHeaders #-}++-- | 'H.ResponseTimeout' exception+_ResponseTimeout :: AsHttpException t => Prism' t ()+_ResponseTimeout = _HttpException . prism' (const H.ResponseTimeout) go where+  go H.ResponseTimeout = Just ()+  go _ = Nothing+{-# INLINE _ResponseTimeout #-}++-- | 'H.FailedConnectionException' exception+_FailedConnectionException :: AsHttpException t => Prism' t (String, Int)+_FailedConnectionException = _HttpException . prism' (uncurry H.FailedConnectionException) go where+  go (H.FailedConnectionException s i) = Just (s, i)+  go _ = Nothing+{-# INLINE _FailedConnectionException #-}++-- | 'H.ExpectedBlankAfter100Continue' exception+_ExpectedBlankAfter100Continue :: AsHttpException t => Prism' t ()+_ExpectedBlankAfter100Continue =+  _HttpException . prism' (const H.ExpectedBlankAfter100Continue) go where+    go H.ExpectedBlankAfter100Continue = Just ()+    go _ = Nothing+{-# INLINE _ExpectedBlankAfter100Continue #-}++-- | 'H.InvalidStatusLine' exception+_InvalidStatusLine :: AsHttpException t => Prism' t ByteString+_InvalidStatusLine = _HttpException . prism' H.InvalidStatusLine go where+  go (H.InvalidStatusLine b) = Just b+  go _ = Nothing+{-# INLINE _InvalidStatusLine #-}++-- | 'H.InvalidHeader' exception+_InvalidHeader :: AsHttpException t => Prism' t ByteString+_InvalidHeader = _HttpException . prism' H.InvalidHeader go where+  go (H.InvalidHeader b) = Just b+  go _ = Nothing+{-# INLINE _InvalidHeader #-}++-- | 'H.InternalIOException' exception+_InternalIOException :: AsHttpException t => Prism' t IOException+_InternalIOException = _HttpException . prism' H.InternalIOException go where+  go (H.InternalIOException ioe) = Just ioe+  go _ = Nothing+{-# INLINE _InternalIOException #-}++-- | 'H.ProxyConnectException' exception+_ProxyConnectException :: AsHttpException t => Prism' t (ByteString, Int, Either ByteString H.HttpException)+_ProxyConnectException = _HttpException . prism' (uncurry3 H.ProxyConnectException) go where+  go (H.ProxyConnectException b i ebhe) = Just (b, i, ebhe)+  go _ = Nothing+{-# INLINE _ProxyConnectException #-}++-- | 'H.NoResponseDataReceived' exception+_NoResponseDataReceived :: AsHttpException t => Prism' t ()+_NoResponseDataReceived = _HttpException . prism' (const H.NoResponseDataReceived) go where+  go H.NoResponseDataReceived = Just ()+  go _ = Nothing+{-# INLINE _NoResponseDataReceived #-}++-- | 'H.TlsException' exception+_TlsException :: AsHttpException t => Prism' t SomeException+_TlsException = _HttpException . prism' H.TlsException go where+  go (H.TlsException se) = Just se+  go _ = Nothing+{-# INLINE _TlsException #-}++-- | 'H.TlsNotSupported' exception+_TlsNotSupported :: AsHttpException t => Prism' t ()+_TlsNotSupported = _HttpException . prism' (const H.TlsNotSupported) go where+  go H.TlsNotSupported = Just ()+  go _ = Nothing+{-# INLINE _TlsNotSupported #-}++-- | 'H.ResponseBodyTooShort' exception+_ResponseBodyTooShort :: AsHttpException t => Prism' t (Word64, Word64)+_ResponseBodyTooShort = _HttpException . prism' (uncurry H.ResponseBodyTooShort) go where+  go (H.ResponseBodyTooShort w w') = Just (w, w')+  go _ = Nothing+{-# INLINE _ResponseBodyTooShort #-}++-- | 'H.InvalidChunkHeaders' exception+_InvalidChunkHeaders :: AsHttpException t => Prism' t ()+_InvalidChunkHeaders = _HttpException . prism' (const H.InvalidChunkHeaders) go where+  go H.InvalidChunkHeaders = Just ()+  go _ = Nothing+{-# INLINE _InvalidChunkHeaders #-}++-- | 'H.IncompleteHeaders' exception+_IncompleteHeaders :: AsHttpException t => Prism' t ()+_IncompleteHeaders = _HttpException . prism' (const H.IncompleteHeaders) go where+  go H.IncompleteHeaders = Just ()+  go _ = Nothing+{-# INLINE _IncompleteHeaders #-}++uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d+uncurry3 f (a, b, c) = f a b c+{-# INLINE uncurry3 #-}
+ src/Network/HTTP/Client/Lens/Internal.hs view
@@ -0,0 +1,42 @@+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_HADDOCK hide #-}+module Network.HTTP.Client.Lens.Internal where++import Control.Applicative+import Control.Exception (Exception(..), SomeException)+import Data.Profunctor (Profunctor(..), Choice(..))+++type Prism s t a b = forall p f. (Choice p, Applicative f) => p a (f b) -> p s (f t)++type Prism' s a = Prism s s a a++type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t++type Lens' s a = Lens s s a a+++view :: Lens' s a -> s -> a+view l = getConst . l Const+{-# INLINE view #-}++(^.) :: s -> Lens' s a -> a+(^.) = flip view+{-# INLINE (^.) #-}++prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b+prism bt seta = dimap seta (either pure (fmap bt)) . right'+{-# INLINE prism #-}++prism' :: (b -> s) -> (s -> Maybe a) -> Prism s s a b+prism' bs sma = prism bs (\s -> maybe (Left s) Right (sma s))+{-# INLINE prism' #-}++exception :: Exception a => Prism' SomeException a+exception = prism' toException fromException+{-# INLINE exception #-}++infixl 1 <&>+(<&>) :: Functor f => f a -> (a -> b) -> f b+(<&>) = flip fmap+{-# INLINE (<&>) #-}
− src/Network/HTTP/Conduit/Lens.hs
@@ -1,316 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-}--- | Optics for @http-conduit@ types-module Network.HTTP.Conduit.Lens-  ( -- * 'Request' lenses-    method-  , secure-  , host-  , port-  , path-  , queryString-  , requestBody-  , requestHeaders-  , proxy-  , hostAddress-  , rawBody-  , decompress-  , redirectCount-  , checkStatus-  , responseTimeout-  , cookieJar-  , getConnectionWrapper-    -- * 'HttpException' prisms-  , AsHttpException(..)-  , _StatusCodeException-  , _InvalidUrlException-  , _TooManyRedirects-  , _UnparseableRedirect-  , _TooManyRetries-  , _HttpParserException-  , _HandshakeFailed-  , _OverlongHeaders-  , _ResponseTimeout-  , _FailedConnectionException-  , _ExpectedBlankAfter100Continue-  , _InvalidStatusLine-  , _InvalidHeader-  , _InternalIOException-  , _ProxyConnectException-  , _NoResponseDataReceived-  , _TlsException-  , _TlsNotSupported-  , _ResponseBodyTooShort-  , _InvalidChunkHeaders-  , _IncompleteHeaders-  ) where--import           Control.Exception (SomeException, IOException)-import           Control.Exception.Lens (exception)-import           Control.Lens-import           Data.ByteString (ByteString)-import qualified Data.ByteString.Lazy as Lazy-import           Data.Word (Word64)-import qualified Network.HTTP.Conduit as H-import qualified Network.HTTP.Client.Internal as H-import qualified Network.HTTP.Types as H-import           Network.Socket (HostAddress)----- | 'H.method' lens-method :: Lens' H.Request H.Method-method f req = f (H.method req) <&> \m' -> req { H.method = m' }-{-# INLINE method #-}---- | 'H.secure' lens-secure :: Lens' H.Request Bool-secure f req = f (H.secure req) <&> \s' -> req { H.secure = s' }-{-# INLINE secure #-}---- | 'H.host' lens-host :: Lens' H.Request ByteString-host f req = f (H.host req) <&> \h' -> req { H.host = h' }-{-# INLINE host #-}---- | 'H.port' lens-port :: Lens' H.Request Int-port f req = f (H.port req) <&> \p' -> req { H.port = p' }-{-# INLINE port #-}---- | 'H.path' lens-path :: Lens' H.Request ByteString-path f req = f (H.path req) <&> \p' -> req { H.path = p' }-{-# INLINE path #-}---- | 'H.queryString' lens-queryString :: Lens' H.Request ByteString-queryString f req = f (H.queryString req) <&> \qs' -> req { H.queryString = qs' }-{-# INLINE queryString #-}---- | 'H.requestBody' lens-requestBody :: Lens' H.Request H.RequestBody-requestBody f req = f (H.requestBody req) <&> \rb' -> req { H.requestBody = rb' }-{-# INLINE requestBody #-}---- | 'H.requestHeaders' lens-requestHeaders :: Lens' H.Request H.RequestHeaders-requestHeaders f req = f (H.requestHeaders req) <&> \rh' -> req { H.requestHeaders = rh' }-{-# INLINE requestHeaders #-}---- | 'H.proxy'-proxy :: Lens' H.Request (Maybe H.Proxy)-proxy f req = f (H.proxy req) <&> \mp' -> req { H.proxy = mp' }-{-# INLINE proxy #-}---- | 'H.hostAddress'-hostAddress :: Lens' H.Request (Maybe HostAddress)-hostAddress f req = f (H.hostAddress req) <&> \ha' -> req { H.hostAddress = ha' }-{-# INLINE hostAddress #-}---- | 'H.rawBody'-rawBody :: Lens' H.Request Bool-rawBody f req = f (H.rawBody req) <&> \b' -> req { H.rawBody = b' }-{-# INLINE rawBody #-}---- | 'H.decompress'-decompress :: Lens' H.Request (ByteString -> Bool)-decompress f req = f (H.decompress req) <&> \btb' -> req { H.decompress = btb' }-{-# INLINE decompress #-}---- | 'H.redirectCount' lens-redirectCount :: Lens' H.Request Int-redirectCount f req = f (H.redirectCount req) <&> \rc' -> req { H.redirectCount = rc' }-{-# INLINE redirectCount #-}---- | 'H.checkStatus' lens-checkStatus :: Lens' H.Request (H.Status -> H.ResponseHeaders -> H.CookieJar -> Maybe SomeException)-checkStatus f req = f (H.checkStatus req) <&> \cs' -> req { H.checkStatus = cs' }-{-# INLINE checkStatus #-}---- | 'H.responseTimeout' lens-responseTimeout :: Lens' H.Request (Maybe Int)-responseTimeout f req = f (H.responseTimeout req) <&> \rt' -> req { H.responseTimeout = rt' }-{-# INLINE responseTimeout #-}---- | 'H.cookieJar'-cookieJar :: Lens' H.Request (Maybe H.CookieJar)-cookieJar f req = f (H.cookieJar req) <&> \mcj' -> req { H.cookieJar = mcj' }-{-# INLINE cookieJar #-}---- | 'H.getConnectionWrapper'-getConnectionWrapper-  :: Lens' H.Request-      ( Maybe Int-      -> H.HttpException-      -> IO (H.ConnRelease, H.Connection, H.ManagedConn)-      -> IO (Maybe Int, (H.ConnRelease, H.Connection, H.ManagedConn))-      )-getConnectionWrapper f req =-  f (H.getConnectionWrapper req) <&> \wat' -> req { H.getConnectionWrapper = wat' }-{-# INLINE getConnectionWrapper #-}----- | @http-conduit@ exceptions-class AsHttpException t where-  -- | @http-conduit@ exceptions overloading-  _HttpException :: Prism' t H.HttpException--instance AsHttpException H.HttpException where-  _HttpException = id-  {-# INLINE _HttpException #-}--instance AsHttpException SomeException where-  _HttpException = exception-  {-# INLINE _HttpException #-}---- | 'H.StatusCodeException' exception-_StatusCodeException :: AsHttpException t => Prism' t (H.Status, H.ResponseHeaders, H.CookieJar)-_StatusCodeException = _HttpException . prism' (uncurry3 H.StatusCodeException) go where-  go (H.StatusCodeException s rh cj) = Just (s, rh, cj)-  go _ = Nothing-{-# INLINE _StatusCodeException #-}---- | 'H.InvalidUrlException' exception-_InvalidUrlException :: AsHttpException t => Prism' t (String, String)-_InvalidUrlException = _HttpException . prism' (uncurry H.InvalidUrlException) go where-  go (H.InvalidUrlException s s') = Just (s, s')-  go _ = Nothing-{-# INLINE _InvalidUrlException #-}---- | 'H.TooManyRedirects' exception-_TooManyRedirects :: AsHttpException t => Prism' t [H.Response Lazy.ByteString]-_TooManyRedirects = _HttpException . prism' H.TooManyRedirects go where-  go (H.TooManyRedirects rs) = Just rs-  go _ = Nothing-{-# INLINE _TooManyRedirects #-}---- | 'H.UnparseableRedirect' exception-_UnparseableRedirect :: AsHttpException t => Prism' t (H.Response Lazy.ByteString)-_UnparseableRedirect = _HttpException . prism' H.UnparseableRedirect go where-  go (H.UnparseableRedirect r) = Just r-  go _ = Nothing-{-# INLINE _UnparseableRedirect #-}---- | 'H.TooManyRetries' exception-_TooManyRetries :: AsHttpException t => Prism' t ()-_TooManyRetries = _HttpException . prism' (const H.TooManyRetries) go where-  go H.TooManyRetries = Just ()-  go _ = Nothing-{-# INLINE _TooManyRetries #-}---- | 'H.HttpParserException' exception-_HttpParserException :: AsHttpException t => Prism' t String-_HttpParserException = _HttpException . prism' H.HttpParserException go where-  go (H.HttpParserException s) = Just s-  go _ = Nothing-{-# INLINE _HttpParserException #-}---- | 'H.HandshakeFailed' exception-_HandshakeFailed :: AsHttpException t => Prism' t ()-_HandshakeFailed = _HttpException . prism' (const H.HandshakeFailed) go where-  go H.HandshakeFailed = Just ()-  go _ = Nothing-{-# INLINE _HandshakeFailed #-}---- | 'H.OverlongHeaders' exception-_OverlongHeaders :: AsHttpException t => Prism' t ()-_OverlongHeaders = _HttpException . prism' (const H.OverlongHeaders) go where-  go H.OverlongHeaders = Just ()-  go _ = Nothing-{-# INLINE _OverlongHeaders #-}---- | 'H.ResponseTimeout' exception-_ResponseTimeout :: AsHttpException t => Prism' t ()-_ResponseTimeout = _HttpException . prism' (const H.ResponseTimeout) go where-  go H.ResponseTimeout = Just ()-  go _ = Nothing-{-# INLINE _ResponseTimeout #-}---- | 'H.FailedConnectionException' exception-_FailedConnectionException :: AsHttpException t => Prism' t (String, Int)-_FailedConnectionException = _HttpException . prism' (uncurry H.FailedConnectionException) go where-  go (H.FailedConnectionException s i) = Just (s, i)-  go _ = Nothing-{-# INLINE _FailedConnectionException #-}---- | 'H.ExpectedBlankAfter100Continue' exception-_ExpectedBlankAfter100Continue :: AsHttpException t => Prism' t ()-_ExpectedBlankAfter100Continue =-  _HttpException . prism' (const H.ExpectedBlankAfter100Continue) go where-    go H.ExpectedBlankAfter100Continue = Just ()-    go _ = Nothing-{-# INLINE _ExpectedBlankAfter100Continue #-}---- | 'H.InvalidStatusLine' exception-_InvalidStatusLine :: AsHttpException t => Prism' t ByteString-_InvalidStatusLine = _HttpException . prism' H.InvalidStatusLine go where-  go (H.InvalidStatusLine b) = Just b-  go _ = Nothing-{-# INLINE _InvalidStatusLine #-}---- | 'H.InvalidHeader' exception-_InvalidHeader :: AsHttpException t => Prism' t ByteString-_InvalidHeader = _HttpException . prism' H.InvalidHeader go where-  go (H.InvalidHeader b) = Just b-  go _ = Nothing-{-# INLINE _InvalidHeader #-}---- | 'H.InternalIOException' exception-_InternalIOException :: AsHttpException t => Prism' t IOException-_InternalIOException = _HttpException . prism' H.InternalIOException go where-  go (H.InternalIOException ioe) = Just ioe-  go _ = Nothing-{-# INLINE _InternalIOException #-}---- | 'H.ProxyConnectException' exception-_ProxyConnectException :: AsHttpException t => Prism' t (ByteString, Int, Either ByteString H.HttpException)-_ProxyConnectException = _HttpException . prism' (uncurry3 H.ProxyConnectException) go where-  go (H.ProxyConnectException b i ebhe) = Just (b, i, ebhe)-  go _ = Nothing-{-# INLINE _ProxyConnectException #-}---- | 'H.NoResponseDataReceived' exception-_NoResponseDataReceived :: AsHttpException t => Prism' t ()-_NoResponseDataReceived = _HttpException . prism' (const H.NoResponseDataReceived) go where-  go H.NoResponseDataReceived = Just ()-  go _ = Nothing-{-# INLINE _NoResponseDataReceived #-}---- | 'H.TlsException' exception-_TlsException :: AsHttpException t => Prism' t SomeException-_TlsException = _HttpException . prism' H.TlsException go where-  go (H.TlsException se) = Just se-  go _ = Nothing-{-# INLINE _TlsException #-}---- | 'H.TlsNotSupported' exception-_TlsNotSupported :: AsHttpException t => Prism' t ()-_TlsNotSupported = _HttpException . prism' (const H.TlsNotSupported) go where-  go H.TlsNotSupported = Just ()-  go _ = Nothing-{-# INLINE _TlsNotSupported #-}---- | 'H.ResponseBodyTooShort' exception-_ResponseBodyTooShort :: AsHttpException t => Prism' t (Word64, Word64)-_ResponseBodyTooShort = _HttpException . prism' (uncurry H.ResponseBodyTooShort) go where-  go (H.ResponseBodyTooShort w w') = Just (w, w')-  go _ = Nothing-{-# INLINE _ResponseBodyTooShort #-}---- | 'H.InvalidChunkHeaders' exception-_InvalidChunkHeaders :: AsHttpException t => Prism' t ()-_InvalidChunkHeaders = _HttpException . prism' (const H.InvalidChunkHeaders) go where-  go H.InvalidChunkHeaders = Just ()-  go _ = Nothing-{-# INLINE _InvalidChunkHeaders #-}---- | 'H.IncompleteHeaders' exception-_IncompleteHeaders :: AsHttpException t => Prism' t ()-_IncompleteHeaders = _HttpException . prism' (const H.IncompleteHeaders) go where-  go H.IncompleteHeaders = Just ()-  go _ = Nothing-{-# INLINE _IncompleteHeaders #-}--uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d-uncurry3 f (a, b, c) = f a b c-{-# INLINE uncurry3 #-}
test/Doctest.hs view
@@ -9,7 +9,7 @@   main :: IO ()-main = sources "src" >>= doctest+main = sources "src" >>= doctest . (["-optP-include", "-optPdist/build/autogen/cabal_macros.h"] ++)  sources :: FilePath -> IO [FilePath] sources root = do
+ test/Jenkins/DiscoverSpec.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE OverloadedStrings #-}+module Jenkins.DiscoverSpec (spec) where++import Data.ByteString (ByteString)+import Test.Hspec++import Jenkins.Discover+++spec :: Spec+spec =+  describe "parse" $ do+    it "parses a real Jenkins response" $+      parseXml fullResponse+      `shouldBe`+      Just Discover+        { version  = "1.587"+        , url      = "https://example.com/jenkins/"+        , serverId = Just "1abcfaefb01da8b19ede7c35ced24f0f"+        , port     = Just "51667"+        }++    it "parses a Jenkins response without a server id" $+        parseXml noServerId+       `shouldBe`+        Just Discover+          { version  = "1.587"+          , url      = "https://example.com/jenkins/"+          , serverId = Nothing+          , port     = Just "51667"+          }++    it "parses a Jenkins response without a slave port" $+        parseXml noSlavePort+       `shouldBe`+        Just Discover+          { version  = "1.587"+          , url      = "https://example.com/jenkins/"+          , serverId = Just "1abcfaefb01da8b19ede7c35ced24f0f"+          , port     = Nothing+          }++fullResponse :: ByteString+fullResponse =+  "<hudson>\+  \<version>1.587</version>\+  \<url>https://example.com/jenkins/</url>\+  \<server-id>1abcfaefb01da8b19ede7c35ced24f0f</server-id>\+  \<slave-port>51667</slave-port>\+  \</hudson>"++noServerId :: ByteString+noServerId =+  "<hudson>\+  \<version>1.587</version>\+  \<url>https://example.com/jenkins/</url>\+  \<slave-port>51667</slave-port>\+  \</hudson>"++noSlavePort :: ByteString+noSlavePort =+  "<hudson>\+  \<version>1.587</version>\+  \<url>https://example.com/jenkins/</url>\+  \<server-id>1abcfaefb01da8b19ede7c35ced24f0f</server-id>\+  \</hudson>"
test/Jenkins/Rest/InternalSpec.hs view
@@ -1,18 +1,19 @@ {-# LANGUAGE OverloadedStrings #-} module Jenkins.Rest.InternalSpec (spec) where -import Control.Lens-import Control.Exception (throwIO)-import Control.Exception.Lens (throwingM, _IOException)-import Control.Monad.IO.Class (liftIO)-import Network.HTTP.Conduit (HttpException)-import Network.HTTP.Types (Status(..))-import Test.Hspec.Lens-import System.IO.Error-import System.IO.Error.Lens (errorType, _NoSuchThing)+import           Control.Lens+import           Control.Exception (throwIO)+import           Control.Exception.Lens (throwingM, _IOException)+import           Network.HTTP.Client (HttpException)+import           Network.HTTP.Types (Status(..))+import           Test.Hspec.Lens+import           System.IO.Error+import           System.IO.Error.Lens (errorType, _NoSuchThing) -import Jenkins.Rest.Internal-import Network.HTTP.Conduit.Lens (_StatusCodeException, _TooManyRetries)+import           Jenkins.Rest (Jenkins, liftIO)+import qualified Jenkins.Rest as Jenkins+import           Jenkins.Rest.Internal+import           Network.HTTP.Client.Lens (_StatusCodeException, _InvalidUrlException, _TooManyRetries)  _JenkinsException :: Iso' JenkinsException HttpException _JenkinsException = iso (\(JenkinsHttpException e) -> e) JenkinsHttpException@@ -25,22 +26,29 @@    describe "runJenkins" $ do     it "wraps uncatched 'HttpException' exceptions from the queries in 'Error'" $-      runJenkins (defaultConnectInfo & jenkinsPort .~ 80) (liftJ (Get "hi" id))+      Jenkins.run Jenkins.defaultMaster (Jenkins.get Jenkins.plain "hi")      `shouldPerform`       Status 404 ""      `through`-      _Error._JenkinsException._StatusCodeException._1+      Jenkins._Exception._JenkinsException._StatusCodeException._1 +    it "wraps uncatched 'HttpException' exceptions from the URL parsing in 'Error'" $+      Jenkins.run (Jenkins.defaultMaster & Jenkins.url .~ "foo") (Jenkins.get Jenkins.plain "hi")+     `shouldPerform`+      ("foo", "Invalid URL")+     `through`+      Jenkins._Exception._JenkinsException._InvalidUrlException+     it "can catch 'HttpException' exceptions related from the queries" $-      runJenkins (defaultConnectInfo & jenkinsPort .~ 80)-        (liftJ (Or (liftJ (Get "hi" id) >> return 4) (return 7)))+      Jenkins.run Jenkins.defaultMaster+        (liftJ (Or (Jenkins.get Jenkins.plain "hi" >> return 4) (\_ -> return 7)))      `shouldPerform`       7      `through`-      _Result+      Jenkins._Ok      it "does not catch (and wrap) 'HttpException's not from the queries" $-      runJenkins defaultConnectInfo raiseHttp `shouldThrow` _TooManyRetries+      Jenkins.run Jenkins.defaultMaster raiseHttp `shouldThrow` _TooManyRetries      it "does not catch (and wrap) 'IOException's" $-      runJenkins defaultConnectInfo raiseIO `shouldThrow` _IOException.errorType._NoSuchThing+      Jenkins.run Jenkins.defaultMaster raiseIO `shouldThrow` _IOException.errorType._NoSuchThing
+ test/Jenkins/Rest/Method/InternalSpec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+module Jenkins.Rest.Method.InternalSpec where++import Test.Hspec++import Jenkins.Rest.Method.Internal+import Jenkins.Rest.Method++spec :: Spec+spec =+  describe "render" $++    it "has a bunch of example input/output pairs" $ do++      format xml ""+        `shouldBe` "api/xml"++      format xml ("job" -/- 7)+        `shouldBe` "job/7/api/xml"++      format json ("job" -/- 7)+        `shouldBe` "job/7/api/json"++      render (text "restart")+        `shouldBe` "restart"++      render ("job" -?- "name" -=- "foo" -&- "title" -=- "bar")+        `shouldBe` "job?name=foo&title=bar"++      render ("job" -?- "name" -&- "title" -=- "bar")+        `shouldBe` "job?name&title=bar"++      format json ("job" -/- 7 -?- "name" -&- "title" -=- "bar")+        `shouldBe` "job/7/api/json?name&title=bar"++      format xml ("job" -/- "ДМИТРИЙ")+        `shouldBe` "job/%D0%94%D0%9C%D0%98%D0%A2%D0%A0%D0%98%D0%99/api/xml"
test/Jenkins/RestSpec.hs view
@@ -1,15 +1,18 @@+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} module Jenkins.RestSpec (spec) where  import           Control.Applicative import           Control.Monad.Trans.State (State, evalState, get, put) import qualified Data.ByteString as Strict import qualified Data.ByteString.Lazy as Lazy-import qualified Data.Conduit as C+import           Data.Functor.Identity (Identity) import           Data.Monoid (mempty) import           Test.Hspec-import qualified Jenkins.Rest as Rest+import qualified Jenkins.Rest as Jenkins import           Jenkins.Rest.Internal+import qualified Jenkins.Rest.Method.Internal as Method   spec :: Spec@@ -17,26 +20,26 @@   context "POST requests" $ do     it "post_ sends POST request with empty body" $ do       interpret $ do-        Rest.post_ "foo"-        Rest.post_ "bar"-        Rest.post_ "baz"+        Jenkins.post_ "foo"+        Jenkins.post_ "bar"+        Jenkins.post_ "baz"      `shouldBe`       [QPost 0 "" "foo", QPost 1 "" "bar", QPost 2 "" "baz"]      it "post sends POST request with non-empty body" $ do       interpret $ do-        Rest.post "foo" "qux"-        Rest.post "bar" "quux"-        Rest.post "baz" "xyzzy"+        Jenkins.post "foo" "qux"+        Jenkins.post "bar" "quux"+        Jenkins.post "baz" "xyzzy"      `shouldBe`       [QPost 0 "qux" "foo", QPost 1 "quux" "bar", QPost 2 "xyzzy" "baz"]    context "GET requests" $     it "get sends GET requests" $ do       interpret $ do-        Rest.getS "foo"-        Rest.getS "bar"-        Rest.getS "baz"+        Jenkins.get Jenkins.plain "foo"+        Jenkins.get Jenkins.plain "bar"+        Jenkins.get Jenkins.plain "baz"      `shouldBe`       [QGet 0 "foo", QGet 1 "bar", QGet 2 "baz"] @@ -44,24 +47,24 @@   describe "reload" $     it "calls $jenkins_url/reload with POST query and then disconnects" $ do       interpret $ do-        Rest.reload-        Rest.post_ "foo"+        Jenkins.reload+        Jenkins.post_ "foo"      `shouldBe`       [QPost 0 "" "reload", QDisconnect]    describe "restart" $     it "calls $jenkins_url/safeRestart with POST query and then disconnects" $ do       interpret $ do-        Rest.restart-        Rest.post_ "bar"+        Jenkins.restart+        Jenkins.post_ "bar"      `shouldBe`       [QPost 0 "" "safeRestart", QDisconnect]    describe "forceRestart" $     it "calls $jenkins_url/restart with POST query and then disconnects" $ do       interpret $ do-        Rest.forceRestart-        Rest.post_ "baz"+        Jenkins.forceRestart+        Jenkins.post_ "baz"      `shouldBe`       [QPost 0 "" "restart", QDisconnect] @@ -75,22 +78,22 @@ newtype Requests a = Requests [a]   deriving (Show, Eq) -interpret :: Rest.Jenkins a -> [Query]-interpret adt = evalState (iterJenkins go ([] <$ adt)) (Requests [0..]) where-  go :: JenkinsF (State (Requests Int) [Query]) -> State (Requests Int) [Query]+interpret :: JenkinsT Identity a -> [Query]+interpret adt = evalState (iter go ([] <$ adt)) (Requests [0..]) where+  go :: JF Identity (State (Requests Int) [Query]) -> State (Requests Int) [Query]   go (Get m n) = do     r <- render QGet m-    fmap (r :) (n (C.newResumableSource (C.yield mempty)))+    fmap (r :) (n mempty)   go (Post m body n) = do     r <- render (\x y -> QPost x body y) m     fmap (r :) n   go Dcon =     return [QDisconnect] -render :: (a -> Strict.ByteString -> Query) -> Rest.Method f x -> State (Requests a) Query+render :: (a -> Strict.ByteString -> Query) -> Jenkins.Method Method.Complete f -> State (Requests a) Query render f m = do   n <- next-  return $ f n (Rest.render m)+  return $ f n (Method.render m)  next :: State (Requests a) a next = do