lackey 0.4.5 → 0.4.6
raw patch · 8 files changed
+300/−301 lines, 8 filesdep +hspecdep −tastydep −tasty-hspecdep ~basedep ~servantdep ~textsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies added: hspec
Dependencies removed: tasty, tasty-hspec
Dependency ranges changed: base, servant, text
API changes (from Hackage documentation)
Files
- LICENSE.md +1/−1
- Setup.hs +2/−2
- lackey.cabal +12/−12
- library/Lackey.hs +124/−118
- package.yaml +27/−26
- stack.yaml +1/−1
- test-suite/Main.hs +0/−141
- tests/Main.hs +133/−0
LICENSE.md view
@@ -1,6 +1,6 @@ [The MIT License (MIT)][] -Copyright (c) 2016 Taylor Fausak+Copyright (c) 2017 Taylor Fausak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in
Setup.hs view
@@ -1,4 +1,4 @@-import qualified Distribution.Simple+import qualified Distribution.Simple as Cabal main :: IO ()-main = Distribution.Simple.defaultMain+main = Cabal.defaultMain
lackey.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: lackey-version: 0.4.5+version: 0.4.6 synopsis: Generate Ruby clients from Servant APIs. description: Lackey generates Ruby clients from Servant APIs. category: Web@@ -30,25 +30,25 @@ library ghc-options: -Wall build-depends:- base >=4.8 && <4.11- , servant >=0.8 && <0.12- , servant-foreign >=0.8 && <0.11- , text >=1.2 && <1.3+ base >= 4.8 && < 4.11+ , servant >= 0.8 && < 0.13+ , servant-foreign >= 0.8 && < 0.11+ , text >= 1.2 && < 1.3 exposed-modules: Lackey default-language: Haskell2010 -test-suite lackey-test-suite+test-suite test type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs:- test-suite+ tests ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N build-depends:- base+ base >= 4.8 && < 4.11+ , servant >= 0.8 && < 0.13+ , servant-foreign >= 0.8 && < 0.11+ , text >= 1.2 && < 1.3 , lackey- , servant- , tasty >=0.11 && <0.13- , tasty-hspec >=1.1 && <1.2- , text+ , hspec >= 2.4.4 && < 2.5 default-language: Haskell2010
library/Lackey.hs view
@@ -1,7 +1,9 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} -module Lackey (rubyForAPI) where+module Lackey+ ( rubyForAPI+ ) where import qualified Data.Char as Char import Data.Function ((&))@@ -30,157 +32,161 @@ hasBody :: Servant.Req Request -> Bool hasBody request =- case Servant._reqBody request of- Nothing -> False- Just _ -> True+ case Servant._reqBody request of+ Nothing -> False+ Just _ -> True bodyArgument :: Text.Text bodyArgument = "body" underscore :: Text.Text -> Text.Text underscore text =- text & Text.toLower &- Text.map- (\c ->- if Char.isAlphaNum c- then c- else '_')+ text & Text.toLower &+ Text.map+ (\c ->+ if Char.isAlphaNum c+ then c+ else '_') getHeaders :: Servant.Req Request -> [Text.Text] getHeaders request =- request & Servant._reqHeaders &- Maybe.mapMaybe- (\h ->- case h of- Servant.HeaderArg x -> Just x- Servant.ReplaceHeaderArg _ _ -> Nothing) &- map Servant._argName &- map Servant.unPathSegment+ request & Servant._reqHeaders &+ Maybe.mapMaybe+ (\h ->+ case h of+ Servant.HeaderArg x -> Just x+ Servant.ReplaceHeaderArg _ _ -> Nothing) &+ map Servant._argName &+ map Servant.unPathSegment getURLPieces :: Servant.Req Request -> [Either Text.Text Text.Text] getURLPieces request =- let url = request & Servant._reqUrl- path =- url & Servant._path & map Servant.unSegment &- Maybe.mapMaybe- (\segment ->- case segment of- Servant.Static _ -> Nothing- Servant.Cap arg -> Just arg) &- map Servant._argName &- map Servant.unPathSegment- query =- url & Servant._queryStr & map Servant._queryArgName &- map Servant._argName &- map Servant.unPathSegment- in map Left path ++ map Right query+ let url = request & Servant._reqUrl+ path =+ url & Servant._path & map Servant.unSegment &+ Maybe.mapMaybe+ (\segment ->+ case segment of+ Servant.Static _ -> Nothing+ Servant.Cap arg -> Just arg) &+ map Servant._argName &+ map Servant.unPathSegment+ query =+ url & Servant._queryStr & map Servant._queryArgName &+ map Servant._argName &+ map Servant.unPathSegment+ in map Left path ++ map Right query functionArguments :: Servant.Req Request -> Text.Text functionArguments request =- Text.concat- [ "("- , [ [Just "excon"]- , request & getURLPieces &- map- (\piece ->- case piece of- Left capture -> underscore capture- Right param -> underscore param <> ": nil") &- map Just- , request & getHeaders & map underscore & map (<> ": nil") & map Just- , [ if hasBody request- then Just bodyArgument- else Nothing]] &- concat &- Maybe.catMaybes &- Text.intercalate ","- , ")"]+ Text.concat+ [ "("+ , [ [Just "excon"]+ , request & getURLPieces &+ map+ (\piece ->+ case piece of+ Left capture -> underscore capture+ Right param -> underscore param <> ": nil") &+ map Just+ , request & getHeaders & map underscore & map (<> ": nil") & map Just+ , [ if hasBody request+ then Just bodyArgument+ else Nothing+ ]+ ] &+ concat &+ Maybe.catMaybes &+ Text.intercalate ","+ , ")"+ ] requestMethod :: Servant.Req Request -> Text.Text requestMethod request =- request & Servant._reqMethod & Text.decodeUtf8 & Text.toLower &- Text.cons ':'+ request & Servant._reqMethod & Text.decodeUtf8 & Text.toLower & Text.cons ':' requestPath :: Servant.Req Request -> Text.Text requestPath request =- let path =- request & Servant._reqUrl & Servant._path & map Servant.unSegment &- map- (\x ->- case x of- Servant.Static y -> Servant.unPathSegment y- Servant.Cap y ->- let z =- y & Servant._argName &- Servant.unPathSegment &- underscore- in "#{" <> z <> "}") &- Text.intercalate "/"- query =- request & Servant._reqUrl & Servant._queryStr &- map Servant._queryArgName &- map Servant._argName &- map Servant.unPathSegment &- map- (\x ->- x <> "=#{" <> underscore x <> "}") &- Text.intercalate "&"- url =- "/" <> path <>- (if Text.null query- then ""- else "?" <> query)- in "\"" <> url <> "\""+ let path =+ request & Servant._reqUrl & Servant._path & map Servant.unSegment &+ map+ (\x ->+ case x of+ Servant.Static y -> Servant.unPathSegment y+ Servant.Cap y ->+ let z =+ y & Servant._argName & Servant.unPathSegment & underscore+ in "#{" <> z <> "}") &+ Text.intercalate "/"+ query =+ request & Servant._reqUrl & Servant._queryStr &+ map Servant._queryArgName &+ map Servant._argName &+ map Servant.unPathSegment &+ map (\x -> x <> "=#{" <> underscore x <> "}") &+ Text.intercalate "&"+ url =+ "/" <> path <>+ (if Text.null query+ then ""+ else "?" <> query)+ in "\"" <> url <> "\"" requestHeaders :: Servant.Req Request -> Text.Text requestHeaders request =- [ ["{"]- , request & getHeaders &- map- (\x ->- "\"" <> x <> "\"=>" <> underscore x)- , ["}"]] &- concat &- Text.concat+ [ ["{"]+ , request & getHeaders & map (\x -> "\"" <> x <> "\"=>" <> underscore x)+ , ["}"]+ ] &+ concat &+ Text.concat requestBody :: Servant.Req Request -> Text.Text requestBody request =- if hasBody request- then bodyArgument- else "nil"+ if hasBody request+ then bodyArgument+ else "nil" functionBody :: Servant.Req Request -> Text.Text functionBody request =- Text.concat- [ "excon.request("- , ":method=>"- , requestMethod request- , ","- , ":path=>"- , requestPath request- , ","- , ":headers=>"- , requestHeaders request- , ","- , ":body=>"- , requestBody request- , ")"]+ Text.concat+ [ "excon.request("+ , ":method=>"+ , requestMethod request+ , ","+ , ":path=>"+ , requestPath request+ , ","+ , ":headers=>"+ , requestHeaders request+ , ","+ , ":body=>"+ , requestBody request+ , ")"+ ] renderRequest :: Servant.Req Request -> Text.Text renderRequest request =- Text.concat- [ "def "- , functionName request- , functionArguments request- , functionBody request- , "end"]+ Text.concat+ [ "def "+ , functionName request+ , functionArguments request+ , functionBody request+ , "end"+ ] -requestsForAPI- :: (Servant.HasForeign Language Request api, Servant.GenerateList Request (Servant.Foreign Request api))- => Proxy.Proxy api -> [Servant.Req Request]+requestsForAPI ::+ ( Servant.HasForeign Language Request api+ , Servant.GenerateList Request (Servant.Foreign Request api)+ )+ => Proxy.Proxy api+ -> [Servant.Req Request] requestsForAPI api = api & Servant.listFromAPI languageProxy requestProxy -rubyForAPI- :: (Servant.HasForeign Language Request api, Servant.GenerateList Request (Servant.Foreign Request api))- => Proxy.Proxy api -> Text.Text+rubyForAPI ::+ ( Servant.HasForeign Language Request api+ , Servant.GenerateList Request (Servant.Foreign Request api)+ )+ => Proxy.Proxy api+ -> Text.Text rubyForAPI api = api & requestsForAPI & renderRequests
package.yaml view
@@ -1,37 +1,38 @@+name: lackey+version: 0.4.6+ category: Web description: Lackey generates Ruby clients from Servant APIs. extra-source-files:-- CHANGELOG.md-- LICENSE.md-- package.yaml-- README.md-- stack.yaml-ghc-options: -Wall+ - CHANGELOG.md+ - LICENSE.md+ - package.yaml+ - README.md+ - stack.yaml github: tfausak/lackey-library:- dependencies:- - base >=4.8 && <4.11- - servant >=0.8 && <0.12- - servant-foreign >=0.8 && <0.11- - text >=1.2 && <1.3- source-dirs: library license: MIT maintainer: Taylor Fausak-name: lackey synopsis: Generate Ruby clients from Servant APIs.++dependencies:+ - base >= 4.8 && < 4.11+ - servant >= 0.8 && < 0.13+ - servant-foreign >= 0.8 && < 0.11+ - text >= 1.2 && < 1.3+ghc-options:+ - -Wall++library:+ source-dirs: library+ tests:- lackey-test-suite:+ test: dependencies:- - base- - lackey- - servant- - tasty >=0.11 && <0.13- - tasty-hspec >=1.1 && <1.2- - text+ - lackey+ - hspec >= 2.4.4 && < 2.5 ghc-options:- - -rtsopts- - -threaded- - -with-rtsopts=-N- source-dirs: test-suite+ - -rtsopts+ - -threaded+ - -with-rtsopts=-N+ source-dirs: tests main: Main.hs-version: '0.4.5'
stack.yaml view
@@ -1,1 +1,1 @@-resolver: lts-8.0+resolver: lts-9.0
− test-suite/Main.hs
@@ -1,141 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeOperators #-}--import Data.Monoid ((<>))-import qualified Data.Proxy as Proxy-import qualified Data.Text as Text-import qualified Lackey-import qualified Servant.API as Servant-import qualified Test.Tasty as Tasty-import Test.Tasty.Hspec--main :: IO ()-main = do- test <- testSpec "Lackey" spec- Tasty.defaultMain test--spec :: Spec-spec = parallel $ do- describe "rubyForAPI" $ do- it "supports delete requests" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Delete '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "delete" "" "delete" "" "" False-- it "supports get requests" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" "" "get" "" "" False-- it "supports patch requests" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Patch '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "patch" "" "patch" "" "" False-- it "supports post requests" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Post '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "post" "" "post" "" "" False-- it "supports put requests" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Put '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "put" "" "put" "" "" False-- it "supports alternatives" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] () Servant.:<|> Servant.Delete '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe` Text.concat- [ ruby "get" "" "get" "" "" False- , ";"- , ruby "delete" "" "delete" "" "" False- ]-- it "supports captures" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "id" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get_by_id" ",id" "get" "#{id}" "" False-- it "supports query flags" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "flag" Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",flag: nil" "get" "?flag=#{flag}" "" False-- it "supports query params" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParam "param" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",param: nil" "get" "?param=#{param}" "" False-- it "supports multiple query params" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParams "params" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",params: nil" "get" "?params=#{params}" "" False-- it "supports request bodies" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",body" "get" "" "" True-- it "supports request headers" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Header "cookie" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",cookie: nil" "get" "" "\"cookie\"=>cookie" False-- it "supports response headers" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Get '[Servant.JSON] (Servant.Headers '[] ()))- Lackey.rubyForAPI api `shouldBe`- ruby "get" "" "get" "" "" False-- it "puts the body param after captures" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "segment" () Servant.:> Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get_by_segment" ",segment,body" "get" "#{segment}" "" True-- it "puts the body param after query params" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "flag" Servant.:> Servant.ReqBody '[Servant.JSON] () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",flag: nil,body" "get" "?flag=#{flag}" "" True-- it "sanitizes path segments" $ do- let api = Proxy.Proxy :: Proxy.Proxy ("A!" Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get_A!" "" "get" "A!" "" False-- it "sanitizes captures" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Capture "A!" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get_by_A!" ",a_" "get" "#{a_}" "" False-- it "sanitizes query flags" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryFlag "A!" Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False-- it "sanitizes query params" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParam "A!" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False-- it "sanitizes multiple query params" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.QueryParams "A!" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False-- it "sanitizes headers" $ do- let api = Proxy.Proxy :: Proxy.Proxy (Servant.Header "A!" () Servant.:> Servant.Get '[Servant.JSON] ())- Lackey.rubyForAPI api `shouldBe`- ruby "get" ",a_: nil" "get" "" "\"A!\"=>a_" False---- Since every generated function has the same structure, it can be abstracted--- away behind this function.-ruby :: Text.Text -> Text.Text -> Text.Text -> Text.Text -> Text.Text -> Bool -> Text.Text-ruby name params method path headers body = "\- \def " <> name <> "(excon" <> params <> ")\- \excon.request(\- \:method=>:" <> method <> ",\- \:path=>\"/" <> path <> "\",\- \:headers=>{" <> headers <> "},\- \:body=>" <> (if body then "body" else "nil") <> "\- \)\- \end\-\"
+ tests/Main.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}++import Data.Proxy+import Data.Text+import Lackey+import Servant.API+import Test.Hspec++main :: IO ()+main = hspec . parallel . describe "Lackey" . describe "rubyForAPI" $ do++ it "supports delete requests" $ do+ let api = Proxy :: Proxy (Delete '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "delete" "" "delete" "" "" False++ it "supports get requests" $ do+ let api = Proxy :: Proxy (Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" "" "get" "" "" False++ it "supports patch requests" $ do+ let api = Proxy :: Proxy (Patch '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "patch" "" "patch" "" "" False++ it "supports post requests" $ do+ let api = Proxy :: Proxy (Post '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "post" "" "post" "" "" False++ it "supports put requests" $ do+ let api = Proxy :: Proxy (Put '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "put" "" "put" "" "" False++ it "supports alternatives" $ do+ let api = Proxy :: Proxy (Get '[JSON] () :<|> Delete '[JSON] ())+ rubyForAPI api `shouldBe` mconcat+ [ ruby "get" "" "get" "" "" False+ , singleton ';'+ , ruby "delete" "" "delete" "" "" False+ ]++ it "supports captures" $ do+ let api = Proxy :: Proxy (Capture "id" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get_by_id" ",id" "get" "#{id}" "" False++ it "supports query flags" $ do+ let api = Proxy :: Proxy (QueryFlag "flag" :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",flag: nil" "get" "?flag=#{flag}" "" False++ it "supports query params" $ do+ let api = Proxy :: Proxy (QueryParam "param" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",param: nil" "get" "?param=#{param}" "" False++ it "supports multiple query params" $ do+ let api = Proxy :: Proxy (QueryParams "params" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",params: nil" "get" "?params=#{params}" "" False++ it "supports request bodies" $ do+ let api = Proxy :: Proxy (ReqBody '[JSON] () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",body" "get" "" "" True++ it "supports request headers" $ do+ let api = Proxy :: Proxy (Header "cookie" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",cookie: nil" "get" "" "\"cookie\"=>cookie" False++ it "supports response headers" $ do+ let api = Proxy :: Proxy (Get '[JSON] (Headers '[] ()))+ rubyForAPI api `shouldBe`+ ruby "get" "" "get" "" "" False++ it "puts the body param after captures" $ do+ let api = Proxy :: Proxy (Capture "segment" () :> ReqBody '[JSON] () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get_by_segment" ",segment,body" "get" "#{segment}" "" True++ it "puts the body param after query params" $ do+ let api = Proxy :: Proxy (QueryFlag "flag" :> ReqBody '[JSON] () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",flag: nil,body" "get" "?flag=#{flag}" "" True++ it "sanitizes path segments" $ do+ let api = Proxy :: Proxy ("A!" :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get_A!" "" "get" "A!" "" False++ it "sanitizes captures" $ do+ let api = Proxy :: Proxy (Capture "A!" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get_by_A!" ",a_" "get" "#{a_}" "" False++ it "sanitizes query flags" $ do+ let api = Proxy :: Proxy (QueryFlag "A!" :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False++ it "sanitizes query params" $ do+ let api = Proxy :: Proxy (QueryParam "A!" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False++ it "sanitizes multiple query params" $ do+ let api = Proxy :: Proxy (QueryParams "A!" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",a_: nil" "get" "?A!=#{a_}" "" False++ it "sanitizes headers" $ do+ let api = Proxy :: Proxy (Header "A!" () :> Get '[JSON] ())+ rubyForAPI api `shouldBe`+ ruby "get" ",a_: nil" "get" "" "\"A!\"=>a_" False++-- Since every generated function has the same structure, it can be abstracted+-- away behind this function.+ruby :: String -> String -> String -> String -> String -> Bool -> Text+ruby name params method path headers body = pack (mconcat+ [ "def ", name, "(excon", params, ")"+ , "excon.request("+ , ":method=>:", method, ","+ , ":path=>\"/", path, "\","+ , ":headers=>{", headers, "},"+ , ":body=>", if body then "body" else "nil"+ , ")"+ , "end"+ ])