lackey (empty) → 0.1.0
raw patch · 16 files changed
+718/−0 lines, 16 filesdep +basedep +lackeydep +servantsetup-changed
Dependencies added: base, lackey, servant, tasty, tasty-hspec
Files
- CHANGELOG.md +9/−0
- LICENSE.md +21/−0
- README.md +46/−0
- Setup.hs +6/−0
- lackey.cabal +50/−0
- library/Lackey.hs +17/−0
- library/Lackey/Internal.hs +21/−0
- library/Lackey/Internal/Endpoint.hs +23/−0
- library/Lackey/Internal/HasCode.hs +139/−0
- library/Lackey/Internal/HasRuby.hs +154/−0
- library/Lackey/Internal/Header.hs +4/−0
- library/Lackey/Internal/MatrixItem.hs +7/−0
- library/Lackey/Internal/Method.hs +9/−0
- library/Lackey/Internal/PathSegment.hs +13/−0
- library/Lackey/Internal/QueryItem.hs +7/−0
- test-suite/Main.hs +192/−0
+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# Change log++Lackey uses [Semantic Versioning][].++## v0.1.0 (2015-08-25)++- Initially created.++[semantic versioning]: http://semver.org/spec/v2.0.0.html
+ LICENSE.md view
@@ -0,0 +1,21 @@+The MIT License (MIT)++Copyright (c) 2015 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 the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ README.md view
@@ -0,0 +1,46 @@+# [Lackey][]++[![Build][]](https://travis-ci.org/tfausak/lackey)++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]++api :: Proxy API+api = Proxy++ruby :: String+ruby = rubyForAPI api+```++[lackey]: https://github.com/tfausak/lackey+[build]: https://img.shields.io/travis/tfausak/lackey.svg+[servant]: http://haskell-servant.github.io+[the change log]: ./CHANGELOG.md
+ Setup.hs view
@@ -0,0 +1,6 @@+module Main (main) where++import Distribution.Simple (defaultMain)++main :: IO ()+main = defaultMain
+ lackey.cabal view
@@ -0,0 +1,50 @@+name: lackey+version: 0.1.0+cabal-version: >=1.10+build-type: Simple+license: MIT+license-file: LICENSE.md+maintainer: Taylor Fausak+synopsis: A library for generating Ruby consumers of Servant APIs.+description:+ <https://github.com/tfausak/lackey#readme>+category: Web+extra-source-files:+ CHANGELOG.md+ README.md++source-repository head+ type: git+ location: https://github.com/tfausak/lackey++library+ exposed-modules:+ Lackey+ Lackey.Internal+ Lackey.Internal.Endpoint+ Lackey.Internal.HasCode+ Lackey.Internal.HasRuby+ Lackey.Internal.Header+ Lackey.Internal.MatrixItem+ Lackey.Internal.Method+ Lackey.Internal.PathSegment+ Lackey.Internal.QueryItem+ build-depends:+ base >=4.7 && <4.9,+ servant >=0.4 && <0.5+ default-language: Haskell2010+ hs-source-dirs: library+ ghc-options: -Wall++test-suite lackey-test-suite+ type: exitcode-stdio-1.0+ main-is: Main.hs+ build-depends:+ base -any,+ lackey -any,+ servant -any,+ tasty >=0.10 && <0.11,+ tasty-hspec >=1.1 && <1.2+ default-language: Haskell2010+ hs-source-dirs: test-suite+ ghc-options: -threaded -Wall
+ library/Lackey.hs view
@@ -0,0 +1,17 @@+{-# LANGUAGE FlexibleContexts #-}++{- |+ This module allows you to generate Ruby code for APIs defined by Servant.+-}+module Lackey+ ( rubyForAPI+ ) where++import Data.Proxy (Proxy)+import Lackey.Internal++{- |+ Generate Ruby code from an API description.+-}+rubyForAPI :: (HasCode a, HasRuby (Ruby a)) => Proxy a -> String+rubyForAPI proxy = rubyFor (codeFor proxy defaultEndpoint)
+ library/Lackey/Internal.hs view
@@ -0,0 +1,21 @@+{- |+ 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.MatrixItem+ , 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.MatrixItem+import Lackey.Internal.Method+import Lackey.Internal.PathSegment+import Lackey.Internal.QueryItem
+ library/Lackey/Internal/Endpoint.hs view
@@ -0,0 +1,23 @@+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 view
@@ -0,0 +1,139 @@+{-# 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.MatrixItem+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++-- Matrix items++instance (KnownSymbol s, HasCode a) => HasCode (S.MatrixFlag s :> a) where+ type Ruby (S.MatrixFlag s :> a) = Ruby a+ codeFor _ e = codeFor a (e { endpointPathSegments = segments }) where+ a = Proxy :: Proxy a+ segments = endpointPathSegments e ++ [segment]+ segment = PathMatrix (MatrixFlag (symbolVal s))+ s = Proxy :: Proxy s++instance (KnownSymbol s, HasCode b) => HasCode (S.MatrixParam s a :> b) where+ type Ruby (S.MatrixParam s a :> b) = Ruby b+ codeFor _ e = codeFor b (e { endpointPathSegments = segments }) where+ b = Proxy :: Proxy b+ segments = endpointPathSegments e ++ [segment]+ segment = PathMatrix (MatrixParam (symbolVal s))+ s = Proxy :: Proxy s++instance (KnownSymbol s, HasCode b) => HasCode (S.MatrixParams s a :> b) where+ type Ruby (S.MatrixParams s a :> b) = Ruby b+ codeFor _ e = codeFor b (e { endpointPathSegments = segments }) where+ b = Proxy :: Proxy b+ segments = endpointPathSegments e ++ [segment]+ segment = PathMatrix (MatrixParams (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 view
@@ -0,0 +1,154 @@+{-# LANGUAGE TypeOperators #-}++module Lackey.Internal.HasRuby where++import Lackey.Internal.Endpoint+import Lackey.Internal.Header+import Lackey.Internal.MatrixItem+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+ renderPathSegment (PathMatrix (MatrixFlag flag)) = flag+ renderPathSegment (PathMatrix (MatrixParam param)) = param+ renderPathSegment (PathMatrix (MatrixParams params)) = params+ pathSegments =+ let segments = endpointPathSegments endpoint+ renderedSegments = map renderPathSegment segments+ in if all isPathMatrix 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++ renderMatrixItem (PathMatrix (MatrixFlag flag)) = Just (sanitize flag ++ ": false")+ renderMatrixItem (PathMatrix (MatrixParam param)) = Just (sanitize param ++ ": nil")+ renderMatrixItem (PathMatrix (MatrixParams params)) = Just (sanitize params ++ ": []")+ renderMatrixItem _ = Nothing+ matrixItems+ = endpoint+ & endpointPathSegments+ & Maybe.mapMaybe renderMatrixItem++ 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, matrixItems, 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, "}"]+ renderPathSegment (PathMatrix (MatrixFlag flag)) = concat ["#{\";", flag, "\" if ", sanitize flag, "}"]+ renderPathSegment (PathMatrix (MatrixParam param)) = concat [";", param, "=#{", sanitize param, "}"]+ renderPathSegment (PathMatrix (MatrixParams params)) = concat ["#{", sanitize params, ".map { |x| \";", params, "[]=#{x}\" }.join}"]+ pathSegments =+ let segments = endpointPathSegments endpoint+ renderedSegments = concatMap renderPathSegment segments+ in case segments of+ [] -> '/' : renderedSegments+ (PathMatrix _ : _) -> '/' : 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 view
@@ -0,0 +1,4 @@+module Lackey.Internal.Header where++newtype Header = Header String+ deriving (Eq, Ord, Read, Show)
+ library/Lackey/Internal/MatrixItem.hs view
@@ -0,0 +1,7 @@+module Lackey.Internal.MatrixItem where++data MatrixItem+ = MatrixFlag String+ | MatrixParam String+ | MatrixParams String+ deriving (Eq, Ord, Read, Show)
+ library/Lackey/Internal/Method.hs view
@@ -0,0 +1,9 @@+module Lackey.Internal.Method where++data Method+ = Delete+ | Get+ | Patch+ | Post+ | Put+ deriving (Bounded, Enum, Eq, Ord, Read, Show)
+ library/Lackey/Internal/PathSegment.hs view
@@ -0,0 +1,13 @@+module Lackey.Internal.PathSegment where++import Lackey.Internal.MatrixItem (MatrixItem)++data PathSegment+ = PathLiteral String+ | PathCapture String+ | PathMatrix MatrixItem+ deriving (Eq, Ord, Read, Show)++isPathMatrix :: PathSegment -> Bool+isPathMatrix (PathMatrix _) = True+isPathMatrix _ = False
+ library/Lackey/Internal/QueryItem.hs view
@@ -0,0 +1,7 @@+module Lackey.Internal.QueryItem where++data QueryItem+ = QueryFlag String+ | QueryParam String+ | QueryParams String+ deriving (Eq, Ord, Read, Show)
+ test-suite/Main.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}++module Main where++import Data.Proxy+import Lackey+import Servant.API+import Test.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\+\"++spec :: Spec+spec = do+ describe "rubyForAPI" $ do+ it "supports delete requests" $ do+ let api = Proxy :: Proxy (Delete '[] ())+ rubyForAPI api `shouldBe`+ ruby "delete_index" "" "delete" "" "" "nil"++ it "supports get requests" $ do+ let api = Proxy :: Proxy (Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index" "" "get" "" "" "nil"++ it "supports patch requests" $ do+ let api = Proxy :: Proxy (Patch '[] ())+ rubyForAPI api `shouldBe`+ ruby "patch_index" "" "patch" "" "" "nil"++ it "supports post requests" $ do+ let api = Proxy :: Proxy (Post '[] ())+ rubyForAPI api `shouldBe`+ ruby "post_index" "" "post" "" "" "nil"++ 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"++ 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"+ ]++ it "supports captures" $ do+ let api = Proxy :: Proxy (Capture "id" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_id" ", id" "get" "#{id}" "" "nil"++ it "supports matrix flags" $ do+ let api = Proxy :: Proxy (MatrixFlag "flag" :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_flag" ", flag: false" "get" "#{\";flag\" if flag}" "" "nil"++ it "supports matrix params" $ do+ let api = Proxy :: Proxy (MatrixParam "param" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_param" ", param: nil" "get" ";param=#{param}" "" "nil"++ it "supports multiple matrix params" $ do+ let api = Proxy :: Proxy (MatrixParams "params" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_params" ", params: []" "get" "#{params.map { |x| \";params[]=#{x}\" }.join}" "" "nil"++ 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"++ 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"++ 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"++ it "supports request bodies" $ do+ let api = Proxy :: Proxy (ReqBody '[] () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index" ", body" "get" "" "" "body"++ 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"++ 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"++ it "always starts the path with a slash" $ do+ let api = Proxy :: Proxy (MatrixFlag "a" :> "b" :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_a_b" ", a: false" "get" "#{\";a\" if a}/b" "" "nil"++ 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"++ 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 before matrix params" $ do+ let api = Proxy :: Proxy (MatrixFlag "flag" :> ReqBody '[] () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_flag" ", body, flag: false" "get" "#{\";flag\" if flag}" "" "body"++ it "sanitizes path segments" $ do+ let api = Proxy :: Proxy ("A!" :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_a_" "" "get" "A!" "" "nil"++ it "sanitizes captures" $ do+ let api = Proxy :: Proxy (Capture "A!" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_a_" ", a_" "get" "#{a_}" "" "nil"++ it "sanitizes matrix flags" $ do+ let api = Proxy :: Proxy (MatrixFlag "A!" :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_a_" ", a_: false" "get" "#{\";A!\" if a_}" "" "nil"++ it "sanitizes matrix params" $ do+ let api = Proxy :: Proxy (MatrixParam "A!" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_a_" ", a_: nil" "get" ";A!=#{a_}" "" "nil"++ it "sanitizes multiple matrix params" $ do+ let api = Proxy :: Proxy (MatrixParams "A!" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_a_" ", a_: []" "get" "#{a_.map { |x| \";A![]=#{x}\" }.join}" "" "nil"++ 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"++ 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"++ 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"++ it "sanitizes headers" $ do+ let api = Proxy :: Proxy (Header "A!" () :> Get '[] ())+ rubyForAPI api `shouldBe`+ ruby "get_index_a_" ", a_: nil" "get" "" " \"A!\" => a_ " "nil"