lackey 0.2.0 → 0.3.0
raw patch · 14 files changed
+322/−487 lines, 14 filesdep +servant-foreigndep +textdep ~basedep ~servantdep ~tasty
Dependencies added: servant-foreign, text
Dependency ranges changed: base, servant, tasty
Files
- README.md +22/−30
- lackey.cabal +10/−15
- library/Lackey.hs +182/−13
- library/Lackey/Internal.hs +0/−19
- library/Lackey/Internal/Endpoint.hs +0/−23
- library/Lackey/Internal/HasCode.hs +0/−112
- library/Lackey/Internal/HasRuby.hs +0/−137
- library/Lackey/Internal/Header.hs +0/−4
- library/Lackey/Internal/Method.hs +0/−9
- library/Lackey/Internal/PathSegment.hs +0/−6
- library/Lackey/Internal/QueryItem.hs +0/−7
- package.yaml +14/−8
- stack.yaml +3/−2
- test-suite/Main.hs +91/−102
README.md view
@@ -5,46 +5,38 @@ Lackey is a Haskell library for generating Ruby consumers of [Servant][] APIs. -- [Installation](#installation)-- [Usage](#usage)--## Installation--You can install Lackey by adding it to your Cabal file.--```-build-depends:- lackey ==0.1.*-```--You can also install it manually.--``` sh-cabal update-cabal install 'lackey ==0.1.*'-```--Please see [the change log][] for a detailed list of changes.--## Usage- Use `Lackey.rubyForAPI` to generate a string of Ruby source code for consuming a Servant API. For example: ``` hs-type API = "things" :> Get '[JSON] [Thing]+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-} -api :: Proxy API-api = Proxy+import qualified Data.Proxy as Proxy+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import qualified Lackey+import Servant.API -ruby :: String-ruby = rubyForAPI api+type API = "words" :> Get '[JSON] [String]++api :: Proxy.Proxy API+api = Proxy.Proxy++ruby :: Text.Text+ruby = Lackey.rubyForAPI api++main :: IO ()+main = Text.putStrLn ruby+-- def get_words(excon)excon.request(:method=>:get,:path=>"/words",:headers=>{},:body=>nil)end ``` +The generated functions require [Excon][].+ [Lackey]: https://github.com/tfausak/lackey [Version badge]: https://www.stackage.org/package/lackey/badge/nightly?label=version [version]: https://www.stackage.org/package/lackey [Build badge]: https://travis-ci.org/tfausak/lackey.svg?branch=master [build]: https://travis-ci.org/tfausak/lackey-[Servant]: http://haskell-servant.github.io-[the change log]: ./CHANGELOG.md+[Servant]: https://haskell-servant.readthedocs.org/en/stable/+[Excon]: https://rubygems.org/gems/excon
lackey.cabal view
@@ -3,9 +3,9 @@ -- see: https://github.com/sol/hpack name: lackey-version: 0.2.0-synopsis: Generate Ruby consumers of Servant APIs.-description: Lackey generates Ruby consumers of Servant APIs.+version: 0.3.0+synopsis: Generate Ruby clients from Servant APIs.+description: Lackey generates Ruby clients from Servant APIs. category: Web homepage: https://github.com/tfausak/lackey#readme bug-reports: https://github.com/tfausak/lackey/issues@@ -30,18 +30,12 @@ library ghc-options: -Wall build-depends:- base >=4.7 && <4.9- , servant >=0.4 && <0.6+ base >=4.8 && <4.9+ , servant >=0.5 && <0.7+ , servant-foreign >=0.5 && <0.7+ , text >=1.2 && <1.3 exposed-modules: Lackey- Lackey.Internal- Lackey.Internal.Endpoint- Lackey.Internal.HasCode- Lackey.Internal.HasRuby- Lackey.Internal.Header- Lackey.Internal.Method- Lackey.Internal.PathSegment- Lackey.Internal.QueryItem default-language: Haskell2010 test-suite lackey-test-suite@@ -49,11 +43,12 @@ main-is: Main.hs hs-source-dirs: test-suite- ghc-options: -Wall+ ghc-options: -Wall -rtsopts -threaded -with-rtsopts=-N build-depends: base , lackey , servant- , tasty >=0.10 && <0.12+ , tasty >=0.11 && <0.12 , tasty-hspec >=1.1 && <1.2+ , text default-language: Haskell2010
library/Lackey.hs view
@@ -1,17 +1,186 @@ {-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} -{- |- This module allows you to generate Ruby code for APIs defined by Servant.--}-module Lackey- ( rubyForAPI- ) where+module Lackey (rubyForAPI) where -import Data.Proxy (Proxy)-import Lackey.Internal+import qualified Data.Char as Char+import Data.Function ((&))+import qualified Data.Maybe as Maybe+import Data.Monoid ((<>))+import qualified Data.Proxy as Proxy+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Servant.Foreign as Servant -{- |- Generate Ruby code from an API description.--}-rubyForAPI :: (HasCode a, HasRuby (Ruby a)) => Proxy a -> String-rubyForAPI proxy = rubyFor (codeFor proxy defaultEndpoint)+type Language = Servant.NoTypes++languageProxy :: Proxy.Proxy Language+languageProxy = Proxy.Proxy++type Request = ()++requestProxy :: Proxy.Proxy Request+requestProxy = Proxy.Proxy++renderRequests :: [Servant.Req Request] -> Text.Text+renderRequests requests = requests & map renderRequest & Text.intercalate ";"++functionName :: Servant.Req Request -> Text.Text+functionName request = request & Servant._reqFuncName & Servant.snakeCase++hasBody :: Servant.Req Request -> Bool+hasBody request =+ 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 '_')++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++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++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 ","+ , ")"]++requestMethod :: Servant.Req Request -> Text.Text+requestMethod request =+ 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 <> "\""++requestHeaders :: Servant.Req Request -> Text.Text+requestHeaders request =+ [ ["{"]+ , 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"++functionBody :: Servant.Req Request -> Text.Text+functionBody 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"]++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 api = api & requestsForAPI & renderRequests
− library/Lackey/Internal.hs
@@ -1,19 +0,0 @@-{- |- This module is for internal use only and should be considered private. If- you need anything exported from this module, please contact the maintainer.--}-module Lackey.Internal- ( module Lackey.Internal.Endpoint- , module Lackey.Internal.HasCode- , module Lackey.Internal.HasRuby- , module Lackey.Internal.Method- , module Lackey.Internal.PathSegment- , module Lackey.Internal.QueryItem- ) where--import Lackey.Internal.Endpoint-import Lackey.Internal.HasCode-import Lackey.Internal.HasRuby-import Lackey.Internal.Method-import Lackey.Internal.PathSegment-import Lackey.Internal.QueryItem
− library/Lackey/Internal/Endpoint.hs
@@ -1,23 +0,0 @@-module Lackey.Internal.Endpoint where--import Lackey.Internal.Header (Header)-import Lackey.Internal.Method (Method (Get))-import Lackey.Internal.PathSegment (PathSegment)-import Lackey.Internal.QueryItem (QueryItem)--data Endpoint = Endpoint- { endpointMethod :: Method- , endpointPathSegments :: [PathSegment]- , endpointQueryItems :: [QueryItem]- , endpointHeaders :: [Header]- , endpointHasBody :: Bool- } deriving (Eq, Ord, Read, Show)--defaultEndpoint :: Endpoint-defaultEndpoint = Endpoint- { endpointMethod = Get- , endpointPathSegments = []- , endpointQueryItems = []- , endpointHeaders = []- , endpointHasBody = False- }
− library/Lackey/Internal/HasCode.hs
@@ -1,112 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}--module Lackey.Internal.HasCode where--import Data.Proxy (Proxy (Proxy))-import GHC.TypeLits (KnownSymbol, symbolVal)-import Lackey.Internal.Endpoint-import Lackey.Internal.Header-import Lackey.Internal.Method-import Lackey.Internal.PathSegment-import Lackey.Internal.QueryItem-import Servant.API ((:>), (:<|>)((:<|>)))--import qualified Servant.API as S--class HasCode a where- type Ruby a- codeFor :: Proxy a -> Endpoint -> Ruby a--instance HasCode S.Raw where- type Ruby S.Raw = ()- codeFor _ _ = ()--instance (HasCode a, HasCode b) => HasCode (a :<|> b) where- type Ruby (a :<|> b) = Ruby a :<|> Ruby b- codeFor _ e = codeFor a e :<|> codeFor b e where- a = Proxy :: Proxy a- b = Proxy :: Proxy b--instance (HasCode c) => HasCode (S.ReqBody a b :> c) where- type Ruby (S.ReqBody a b :> c) = Ruby c- codeFor _ e = codeFor c (e { endpointHasBody = True}) where- c = Proxy :: Proxy c---- Methods--instance HasCode (S.Delete a b) where- type Ruby (S.Delete a b) = Endpoint- codeFor _ e = e { endpointMethod = Delete }--instance HasCode (S.Get a b) where- type Ruby (S.Get a b) = Endpoint- codeFor _ e = e { endpointMethod = Get }--instance HasCode (S.Patch a b) where- type Ruby (S.Patch a b) = Endpoint- codeFor _ e = e { endpointMethod = Patch }--instance HasCode (S.Post a b) where- type Ruby (S.Post a b) = Endpoint- codeFor _ e = e { endpointMethod = Post }--instance HasCode (S.Put a b) where- type Ruby (S.Put a b) = Endpoint- codeFor _ e = e { endpointMethod = Put }---- Path segments--instance (KnownSymbol s, HasCode a) => HasCode (s :> a) where- type Ruby (s :> a) = Ruby a- codeFor _ e = codeFor a (e { endpointPathSegments = segments }) where- a = Proxy :: Proxy a- segments = endpointPathSegments e ++ [segment]- segment = PathLiteral (symbolVal s)- s = Proxy :: Proxy s--instance (KnownSymbol s, HasCode b) => HasCode (S.Capture s a :> b) where- type Ruby (S.Capture s a :> b) = Ruby b- codeFor _ e = codeFor b (e { endpointPathSegments = segments }) where- b = Proxy :: Proxy b- segments = endpointPathSegments e ++ [segment]- segment = PathCapture (symbolVal s)- s = Proxy :: Proxy s---- Query items--instance (KnownSymbol s, HasCode a) => HasCode (S.QueryFlag s :> a) where- type Ruby (S.QueryFlag s :> a) = Ruby a- codeFor _ e = codeFor a (e { endpointQueryItems = items }) where- a = Proxy :: Proxy a- items = endpointQueryItems e ++ [item]- item = QueryFlag (symbolVal s)- s = Proxy :: Proxy s--instance (KnownSymbol s, HasCode b) => HasCode (S.QueryParam s a :> b) where- type Ruby (S.QueryParam s a :> b) = Ruby b- codeFor _ e = codeFor b (e { endpointQueryItems = items }) where- b = Proxy :: Proxy b- items = endpointQueryItems e ++ [item]- item = QueryParam (symbolVal s)- s = Proxy :: Proxy s--instance (KnownSymbol s, HasCode b) => HasCode (S.QueryParams s a :> b) where- type Ruby (S.QueryParams s a :> b) = Ruby b- codeFor _ e = codeFor b (e { endpointQueryItems = items }) where- b = Proxy :: Proxy b- items = endpointQueryItems e ++ [item]- item = QueryParams (symbolVal s)- s = Proxy :: Proxy s---- Headers--instance (KnownSymbol s, HasCode b) => HasCode (S.Header s a :> b) where- type Ruby (S.Header s a :> b) = Ruby b- codeFor _ e = codeFor b (e { endpointHeaders = headers }) where- b = Proxy :: Proxy b- headers = endpointHeaders e ++ [header]- header = Header (symbolVal s)- s = Proxy :: Proxy s
− library/Lackey/Internal/HasRuby.hs
@@ -1,137 +0,0 @@-{-# LANGUAGE TypeOperators #-}--module Lackey.Internal.HasRuby where--import Lackey.Internal.Endpoint-import Lackey.Internal.Header-import Lackey.Internal.PathSegment-import Lackey.Internal.QueryItem-import Servant.API ((:<|>)(..))--import qualified Data.Char as Char-import qualified Data.List as List-import qualified Data.Maybe as Maybe--infixl 1 &-(&) :: a -> (a -> b) -> b-x & f = f x--class HasRuby a where- rubyFor :: a -> String--instance HasRuby () where- rubyFor _ = ""--instance HasRuby Endpoint where- rubyFor endpoint = "\- \def " ++ renderName endpoint ++ "(" ++ renderParams endpoint ++ ")\n\- \ excon.request(\n\- \ method: :" ++ renderMethod endpoint ++ ",\n\- \ path: \"" ++ renderPath endpoint ++ "\",\n\- \ headers: {" ++ renderHeaders endpoint ++ "},\n\- \ body: " ++ (if endpointHasBody endpoint then "body" else "nil") ++ "\n\- \ )\n\- \end\- \"--instance (HasRuby a, HasRuby b) => HasRuby (a :<|> b) where- rubyFor (x :<|> y) =- let rx = rubyFor x- ry = rubyFor y- s = if null rx || null ry then "" else "\n\n"- in concat [rx, s, ry]--renderName :: Endpoint -> String-renderName endpoint =- let method = renderMethod endpoint-- renderPathSegment (PathLiteral literal) = literal- renderPathSegment (PathCapture capture) = capture- pathSegments =- let segments = endpointPathSegments endpoint- renderedSegments = map renderPathSegment segments- in if null segments- then "index" : renderedSegments- else renderedSegments- path = List.intercalate "_" pathSegments-- renderQueryItem (QueryFlag flag) = flag- renderQueryItem (QueryParam param) = param- renderQueryItem (QueryParams params) = params- queryItems = map renderQueryItem (endpointQueryItems endpoint)- query = if null queryItems- then ""- else "_" ++ List.intercalate "_" queryItems-- renderHeader (Header x) = x- headers = map renderHeader (endpointHeaders endpoint)- header = if null headers- then ""- else "_" ++ List.intercalate "_" headers-- in sanitize (method ++ "_" ++ path ++ query ++ header)--renderParams :: Endpoint -> String-renderParams endpoint =- let renderPathSegment (PathCapture capture) = Just (sanitize capture)- renderPathSegment _ = Nothing- pathSegments- = endpoint- & endpointPathSegments- & map renderPathSegment- & Maybe.catMaybes-- renderQueryItem (QueryFlag flag) = Just (sanitize flag ++ ": false")- renderQueryItem (QueryParam param) = Just (sanitize param ++ ": nil")- renderQueryItem (QueryParams params) = Just (sanitize params ++ ": []")- queryItems- = endpoint- & endpointQueryItems- & Maybe.mapMaybe renderQueryItem-- renderHeader (Header header) = sanitize header ++ ": nil"- headers = endpoint & endpointHeaders & map renderHeader-- body = ["body" | endpointHasBody endpoint]-- in List.intercalate ", "- (concat [["excon"], pathSegments, body, queryItems, headers])--renderMethod :: Endpoint -> String-renderMethod endpoint = endpoint & endpointMethod & show & map Char.toLower--renderPath :: Endpoint -> String-renderPath endpoint =- let renderPathSegment (PathLiteral literal) = '/' : literal- renderPathSegment (PathCapture capture) = concat ["/#{", sanitize capture, "}"]- pathSegments =- let segments = endpointPathSegments endpoint- renderedSegments = concatMap renderPathSegment segments- in case segments of- [] -> '/' : renderedSegments- _ -> renderedSegments-- renderQueryItem (QueryFlag flag) = concat ["#{\"&", flag, "\" if ", sanitize flag, "}"]- renderQueryItem (QueryParam param) = concat ["&", param, "=#{", sanitize param, "}"]- renderQueryItem (QueryParams params) = concat ["#{", sanitize params, ".map { |x| \"&", params, "[]=#{x}\" }.join}"]- queryItems = case endpointQueryItems endpoint of- [] -> ""- items -> '?' : concatMap renderQueryItem items-- in pathSegments ++ queryItems--renderHeaders :: Endpoint -> String-renderHeaders endpoint =- let headers = endpointHeaders endpoint- in if null headers- then ""- else- let y = headers- & map (\ (Header x) -> concat ["\"", x, "\" => ", sanitize x])- & List.intercalate ", "- in " " ++ y ++ " "--sanitize :: String -> String-sanitize xs- = xs- & map (\ x -> if Char.isAlphaNum x then Char.toLower x else '_')
− library/Lackey/Internal/Header.hs
@@ -1,4 +0,0 @@-module Lackey.Internal.Header where--newtype Header = Header String- deriving (Eq, Ord, Read, Show)
− library/Lackey/Internal/Method.hs
@@ -1,9 +0,0 @@-module Lackey.Internal.Method where--data Method- = Delete- | Get- | Patch- | Post- | Put- deriving (Bounded, Enum, Eq, Ord, Read, Show)
− library/Lackey/Internal/PathSegment.hs
@@ -1,6 +0,0 @@-module Lackey.Internal.PathSegment where--data PathSegment- = PathLiteral String- | PathCapture String- deriving (Eq, Ord, Read, Show)
− library/Lackey/Internal/QueryItem.hs
@@ -1,7 +0,0 @@-module Lackey.Internal.QueryItem where--data QueryItem- = QueryFlag String- | QueryParam String- | QueryParams String- deriving (Eq, Ord, Read, Show)
package.yaml view
@@ -1,31 +1,37 @@ category: Web-description: Lackey generates Ruby consumers of Servant APIs.+description: Lackey generates Ruby clients from Servant APIs. extra-source-files: - CHANGELOG.md - LICENSE.md - package.yaml - README.md - stack.yaml-ghc-options:-- -Wall+ghc-options: -Wall github: tfausak/lackey library: dependencies:- - base >=4.7 && <4.9- - servant >=0.4 && <0.6+ - base >=4.8 && <4.9+ - servant >=0.5 && <0.7+ - servant-foreign >=0.5 && <0.7+ - text >=1.2 && <1.3 source-dirs: library license: MIT maintainer: Taylor Fausak name: lackey-synopsis: Generate Ruby consumers of Servant APIs.+synopsis: Generate Ruby clients from Servant APIs. tests: lackey-test-suite: dependencies: - base - lackey - servant- - tasty >=0.10 && <0.12+ - tasty >=0.11 && <0.12 - tasty-hspec >=1.1 && <1.2+ - text+ ghc-options:+ - -rtsopts+ - -threaded+ - -with-rtsopts=-N source-dirs: test-suite main: Main.hs-version: '0.2.0'+version: '0.3.0'
stack.yaml view
@@ -1,5 +1,6 @@ extra-deps:-- servant-0.5+- servant-0.6.1+- servant-foreign-0.6.1 install-ghc: true require-stack-version: ! '>=1.0.4'-resolver: lts-5.8+resolver: nightly-2016-04-07
test-suite/Main.hs view
@@ -1,152 +1,141 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeOperators #-} -module Main where--import Data.Proxy-import Lackey-import Servant.API-import Test.Tasty+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 = defaultMain =<< testSpec "Lackey" spec---- Since every generated function has the same structure, it can be abstracted--- away behind this function.-ruby :: String -> String -> String -> String -> String -> String -> String-ruby name params method path headers body = "\- \def " ++ name ++ "(excon" ++ params ++ ")\n\- \ excon.request(\n\- \ method: :" ++ method ++ ",\n\- \ path: \"/" ++ path ++ "\",\n\- \ headers: {" ++ headers ++ "},\n\- \ body: " ++ body ++ "\n\- \ )\n\- \end\-\"+main = do+ test <- testSpec "Lackey" spec+ Tasty.defaultMain test spec :: Spec-spec = do+spec = parallel $ do describe "rubyForAPI" $ do it "supports delete requests" $ do- let api = Proxy :: Proxy (Delete '[] ())- rubyForAPI api `shouldBe`- ruby "delete_index" "" "delete" "" "" "nil"+ 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 (Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index" "" "get" "" "" "nil"+ 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 (Patch '[] ())- rubyForAPI api `shouldBe`- ruby "patch_index" "" "patch" "" "" "nil"+ 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 (Post '[] ())- rubyForAPI api `shouldBe`- ruby "post_index" "" "post" "" "" "nil"+ 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 (Put '[] ())- rubyForAPI api `shouldBe`- ruby "put_index" "" "put" "" "" "nil"-- it "supports path components" $ do- let api = Proxy :: Proxy ("resource" :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_resource" "" "get" "resource" "" "nil"+ 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 (Get '[] () :<|> Delete '[] ())- rubyForAPI api `shouldBe` concat- [ ruby "get_index" "" "get" "" "" "nil"- , "\n\n"- , ruby "delete_index" "" "delete" "" "" "nil"+ 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 (Capture "id" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_id" ", id" "get" "#{id}" "" "nil"+ 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 (QueryFlag "flag" :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_flag" ", flag: false" "get" "?#{\"&flag\" if flag}" "" "nil"+ 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 (QueryParam "param" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_param" ", param: nil" "get" "?¶m=#{param}" "" "nil"+ 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 (QueryParams "params" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_params" ", params: []" "get" "?#{params.map { |x| \"¶ms[]=#{x}\" }.join}" "" "nil"+ 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 (ReqBody '[] () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index" ", body" "get" "" "" "body"+ 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 (Header "cookie" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_cookie" ", cookie: nil" "get" "" " \"cookie\" => cookie " "nil"+ 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 (Get '[] (Headers '[] ()))- rubyForAPI api `shouldBe`- ruby "get_index" "" "get" "" "" "nil"-- it "supports raw" $ do- let api = Proxy :: Proxy Raw- rubyForAPI api `shouldBe` ""-- it "supports raw as part of a larger API" $ do- let api = Proxy :: Proxy (Get '[] () :<|> Raw)- rubyForAPI api `shouldBe`- ruby "get_index" "" "get" "" "" "nil"+ 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 (Capture "segment" () :> ReqBody '[] () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_segment" ", segment, body" "get" "#{segment}" "" "body"+ 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 before query params" $ do- let api = Proxy :: Proxy (QueryFlag "flag" :> ReqBody '[] () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_flag" ", body, flag: false" "get" "?#{\"&flag\" if flag}" "" "body"+ 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 ("A!" :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_a_" "" "get" "A!" "" "nil"+ 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 (Capture "A!" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_a_" ", a_" "get" "#{a_}" "" "nil"+ 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 (QueryFlag "A!" :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_a_" ", a_: false" "get" "?#{\"&A!\" if a_}" "" "nil"+ 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 (QueryParam "A!" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_a_" ", a_: nil" "get" "?&A!=#{a_}" "" "nil"+ 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 (QueryParams "A!" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_a_" ", a_: []" "get" "?#{a_.map { |x| \"&A![]=#{x}\" }.join}" "" "nil"+ 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 (Header "A!" () :> Get '[] ())- rubyForAPI api `shouldBe`- ruby "get_index_a_" ", a_: nil" "get" "" " \"A!\" => a_ " "nil"+ 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\+\"