packages feed

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 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" "?&param=#{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| \"&params[]=#{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"