diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -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
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
--- /dev/null
+++ b/LICENSE.md
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -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
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Distribution.Simple (defaultMain)
+
+main :: IO ()
+main = defaultMain
diff --git a/lackey.cabal b/lackey.cabal
new file mode 100644
--- /dev/null
+++ b/lackey.cabal
@@ -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
diff --git a/library/Lackey.hs b/library/Lackey.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey.hs
@@ -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)
diff --git a/library/Lackey/Internal.hs b/library/Lackey/Internal.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal.hs
@@ -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
diff --git a/library/Lackey/Internal/Endpoint.hs b/library/Lackey/Internal/Endpoint.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/Endpoint.hs
@@ -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
+    }
diff --git a/library/Lackey/Internal/HasCode.hs b/library/Lackey/Internal/HasCode.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/HasCode.hs
@@ -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
diff --git a/library/Lackey/Internal/HasRuby.hs b/library/Lackey/Internal/HasRuby.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/HasRuby.hs
@@ -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 '_')
diff --git a/library/Lackey/Internal/Header.hs b/library/Lackey/Internal/Header.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/Header.hs
@@ -0,0 +1,4 @@
+module Lackey.Internal.Header where
+
+newtype Header = Header String
+    deriving (Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/MatrixItem.hs b/library/Lackey/Internal/MatrixItem.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/MatrixItem.hs
@@ -0,0 +1,7 @@
+module Lackey.Internal.MatrixItem where
+
+data MatrixItem
+    = MatrixFlag String
+    | MatrixParam String
+    | MatrixParams String
+    deriving (Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/Method.hs b/library/Lackey/Internal/Method.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/Method.hs
@@ -0,0 +1,9 @@
+module Lackey.Internal.Method where
+
+data Method
+    = Delete
+    | Get
+    | Patch
+    | Post
+    | Put
+    deriving (Bounded, Enum, Eq, Ord, Read, Show)
diff --git a/library/Lackey/Internal/PathSegment.hs b/library/Lackey/Internal/PathSegment.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/PathSegment.hs
@@ -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
diff --git a/library/Lackey/Internal/QueryItem.hs b/library/Lackey/Internal/QueryItem.hs
new file mode 100644
--- /dev/null
+++ b/library/Lackey/Internal/QueryItem.hs
@@ -0,0 +1,7 @@
+module Lackey.Internal.QueryItem where
+
+data QueryItem
+    = QueryFlag String
+    | QueryParam String
+    | QueryParams String
+    deriving (Eq, Ord, Read, Show)
diff --git a/test-suite/Main.hs b/test-suite/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/Main.hs
@@ -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"
