Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f795990
QueryParams should produce val[]=key pairs in the generated url.
stoeffel Jun 11, 2019
097ed97
add spec to check if an attribute is a list
stoeffel Jun 11, 2019
05abdc3
add code to join lists with a ","
stoeffel Jun 11, 2019
bc35311
cleanup
stoeffel Jun 11, 2019
4e9da12
cleanup
stoeffel Jun 11, 2019
dda9666
add blankline after clean capture code
stoeffel Jun 11, 2019
9b47d0a
Merge branch 'query-params' into next-version
stoeffel Jun 11, 2019
ed9bd61
expose api uri
stoeffel Jun 12, 2019
2c18da4
Merge branch 'expose-urls' into next-version
stoeffel Jun 12, 2019
166c90a
set timeout of requests
stoeffel Jun 13, 2019
be10195
Merge branch 'set-timeout' into next-version
stoeffel Jun 13, 2019
58399a7
clean captures in uri method
stoeffel Jun 13, 2019
99af3bb
It seems you have to explicitly tell the client to use TLS
Jun 25, 2019
25c48a7
Merge branch 'use-ssl' into next-version
stoeffel Jun 25, 2019
6e83e84
Merge pull request #2 from NoRedInk/next-version
jwoudenberg Oct 18, 2019
f399bfd
Move doc tests into a subdirectory
jwoudenberg Oct 22, 2019
d562337
Add golden tests
jwoudenberg Oct 22, 2019
5f960bd
Fix issue with query flags
jwoudenberg Oct 22, 2019
953e801
Merge pull request #3 from NoRedInk/fix-query-param-generation
stoeffel Oct 22, 2019
1b5295e
Query flags result in optional args
jwoudenberg Dec 29, 2020
900dd92
Merge pull request #4 from NoRedInk/query-flags-make-optional-args
jwoudenberg Dec 29, 2020
f4e65cf
Fix bug in query flag casing
jwoudenberg Dec 29, 2020
e5d85f5
Restyled by rubocop
restyled-commits Dec 29, 2020
b3010d7
Restyled by stylish-haskell
restyled-commits Dec 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion servant-ruby.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,26 @@ library
test-suite doc-test
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: test
hs-source-dirs: test/doc
build-depends: base
, doctest >= 0.11
, QuickCheck >= 2.9
default-language: Haskell2010

test-suite golden-test
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: test/golden
build-depends: base
, bytestring >= 0.10.8.2
, servant >= 0.9 && < 0.16
, servant-foreign >= 0.9 && < 0.16
, servant-ruby
, tasty >= 0.11.3
, tasty-golden >= 2.3.1.1
, text >= 1.2 && < 1.3
default-language: Haskell2010

source-repository head
type: git
location: https://github.com/joneshf/servant-ruby
141 changes: 115 additions & 26 deletions src/Servant/Ruby.hs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,23 @@ require "net/http"
require "uri"
<BLANKLINE>
class Baz
def initialize(origin)
def initialize(origin, timeout = nil)
@origin = URI(origin)
@http = Net::HTTP.new(@origin.host, @origin.port)
<BLANKLINE>
unless timeout.nil?
@http.open_timeout = timeout
@http.read_timeout = timeout
end
@http.use_ssl = @origin.scheme == 'https'
end
<BLANKLINE>
def get()
uri = URI("#{@origin}")
def get_uri()
URI("#{@origin}")
end
<BLANKLINE>
req = Net::HTTP::Get.new(uri)
def get()
req = Net::HTTP::Get.new(get_uri())
<BLANKLINE>
@http.request(req)
end
Expand All @@ -114,15 +122,23 @@ require "uri"
module Foo
module Bar
class Baz
def initialize(origin)
def initialize(origin, timeout = nil)
@origin = URI(origin)
@http = Net::HTTP.new(@origin.host, @origin.port)
<BLANKLINE>
unless timeout.nil?
@http.open_timeout = timeout
@http.read_timeout = timeout
end
@http.use_ssl = @origin.scheme == 'https'
end
<BLANKLINE>
def get()
uri = URI("#{@origin}")
def get_uri()
URI("#{@origin}")
end
<BLANKLINE>
req = Net::HTTP::Get.new(uri)
def get()
req = Net::HTTP::Get.new(get_uri())
<BLANKLINE>
@http.request(req)
end
Expand All @@ -134,22 +150,34 @@ Captures and query parameters are translated into required arguments, in that or

The request body and headers are translated into keyword arguments, in that order.

>>> let api = Proxy :: Proxy ("foo" :> Capture "fooId" Int :> ReqBody '[JSON] () :> QueryParam "barId" Bool :> Header "Max-Forwards" Int :> Post '[JSON] ())
>>> let api = Proxy :: Proxy ("foo" :> Capture "fooId" Int :> ReqBody '[JSON] () :> QueryParam "barId" Bool :> QueryParams "ids" Int :> Header "Max-Forwards" Int :> Post '[JSON] ())
>>> Data.Text.IO.putStr $ ruby (NameSpace [] "Foo") api
require "json"
require "net/http"
require "uri"
<BLANKLINE>
class Foo
def initialize(origin)
def initialize(origin, timeout = nil)
@origin = URI(origin)
@http = Net::HTTP.new(@origin.host, @origin.port)
<BLANKLINE>
unless timeout.nil?
@http.open_timeout = timeout
@http.read_timeout = timeout
end
@http.use_ssl = @origin.scheme == 'https'
end
<BLANKLINE>
def post_foo_by_foo_id_uri(foo_id, bar_id, ids)
foo_id = if foo_id.kind_of?(Array) then foo_id.join(',') else foo_id end
<BLANKLINE>
URI("#{@origin}/foo/#{foo_id}?barId=#{bar_id}&#{ ids.collect { |x| 'ids[]=' + x.to_s }.join('&') }")
end
<BLANKLINE>
def post_foo_by_foo_id(foo_id, bar_id, body:, max_forwards:)
uri = URI("#{@origin}/foo/#{foo_id}?barId=#{bar_id}")
def post_foo_by_foo_id(foo_id, bar_id, ids, body:, max_forwards:)
foo_id = if foo_id.kind_of?(Array) then foo_id.join(',') else foo_id end
<BLANKLINE>
req = Net::HTTP::Post.new(uri)
req = Net::HTTP::Post.new(post_foo_by_foo_id_uri(foo_id, bar_id, ids))
req["Content-Type"] = "application/json"
req["Max-Forwards"] = max_forwards
<BLANKLINE>
Expand Down Expand Up @@ -197,20 +225,34 @@ properIndent indent =
initialize :: Int -> [Text]
initialize indent =
properIndent indent
[ Just "def initialize(origin)"
[ Just "def initialize(origin, timeout = nil)"
, Just " @origin = URI(origin)"
, Just " @http = Net::HTTP.new(@origin.host, @origin.port)"
, Nothing
, Just " unless timeout.nil?"
, Just " @http.open_timeout = timeout"
, Just " @http.read_timeout = timeout"
, Just " end"
, Just " @http.use_ssl = @origin.scheme == 'https'"
, Just "end"
]

public :: Int -> Req NoContent -> [Text]
public indent req =
properIndent indent $
[ Nothing
, Just $ "def " <> functionName <> "(" <> argsStr <> ")"
, Just $ " uri = URI(" <> url <> ")"
, Just $ "def " <> functionName <> "_uri(" <> argsStr <> ")"
]
++ cleanCaptures
++
[ Just $ " URI(" <> url <> ")"
, Just "end"
, Nothing
, Just $ " req = Net::HTTP::" <> method <> ".new(uri)"
, Just $ "def " <> functionName <> "(" <> allArgsStr <> ")"
]
++ cleanCaptures
++
[ Just $ " req = Net::HTTP::" <> method <> ".new(" <> functionName <> "_uri(" <> callArgsStr <> "))"
]
++ requestHeaders
++
Expand All @@ -222,15 +264,44 @@ public indent req =
functionName :: Text
functionName = req ^. reqFuncName.snakeCaseL.to snake

cleanCaptures :: [Maybe Text]
cleanCaptures =
case captures of
[] -> []
xs ->
(Just . (<>) " " . cleanCapture . snake <$> xs)
++ [ Nothing ]

cleanCapture :: Text -> Text
cleanCapture c =
T.concat
[ c
, " = if "
, c
, ".kind_of?(Array) then "
, c
, ".join(',') else "
, c
, " end"
]

argsStr :: Text
argsStr = T.intercalate ", " $ snake <$> args
argsStr = T.intercalate ", " args

callArgsStr :: Text
callArgsStr = T.intercalate ", " callArgs

callArgs :: [Text]
callArgs = captures ++ (paramToCallArg <$> queryparams)

allArgsStr :: Text
allArgsStr = T.intercalate ", " (args ++ bodyAndHeader)

args :: [Text]
args =
captures
++ ((^. queryArgName.argPath) <$> queryparams)
++ body
++ headerArgs
args = captures ++ (paramToArg <$> queryparams)

bodyAndHeader :: [Text]
bodyAndHeader = snake <$> (body ++ headerArgs)

segments :: [Segment NoContent]
segments = filter isCapture paths
Expand All @@ -241,7 +312,7 @@ public indent req =
queryparams = req ^.. reqUrl.queryStr.traverse

captures :: [Text]
captures = view argPath . captureArg <$> segments
captures = fmap snake $ view argPath . captureArg <$> segments

body :: [Text]
body =
Expand Down Expand Up @@ -318,12 +389,30 @@ paramToStr :: QueryArg f -> Text
paramToStr qarg =
case qarg ^. queryArgType of
Normal -> key <> "=#{" <> val <> "}"
Flag -> key
List -> key <> "[]=#{" <> val <> "}"
Flag -> "#{" <> snake key <> " ? '" <> key <> "' : ''}"
List -> "#{ " <> val <> ".collect { |x| '" <> key <> "[]=' + x.to_s }.join('&') }"
where
key = qarg ^. queryArgName.argName._PathSegment
val = snake key

paramToArg :: QueryArg f -> Text
paramToArg qarg =
case qarg ^. queryArgType of
Normal -> snake name
Flag -> snake name <> ": false"
List -> snake name
where
name = qarg ^. queryArgName.argPath

paramToCallArg :: QueryArg f -> Text
paramToCallArg qarg =
case qarg ^. queryArgType of
Normal -> snake name
Flag -> snake name <> ": " <> snake name
List -> snake name
where
name = qarg ^. queryArgName.argPath

snake :: Text -> Text
snake = T.pack . quietSnake . T.unpack

Expand Down
File renamed without changes.
59 changes: 59 additions & 0 deletions test/golden/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators #-}

module Main where

import Data.ByteString.Lazy (ByteString)
import Data.Proxy (Proxy(Proxy))
import Data.Text.Lazy (fromStrict)
import Data.Text.Lazy.Encoding (encodeUtf8)
import Servant.API
( (:>)
, Capture
, Get
, Header
, JSON
, Post
, QueryFlag
, QueryParam
, QueryParams
, ReqBody
)
import Servant.Foreign (Foreign, GenerateList, HasForeign, NoContent, NoTypes)
import Servant.Ruby (NameSpace(NameSpace), ruby)
import Test.Tasty (defaultMain, testGroup)
import Test.Tasty.Golden (goldenVsString)

main :: IO ()
main = defaultMain $ testGroup "golden tests"
[ goldenVsString "static" "test/golden/expected/static.rb" (test (Proxy :: Proxy StaticApi))
, goldenVsString "parameters" "test/golden/expected/parameters.rb" (test (Proxy :: Proxy ParametersApi))
, goldenVsString "body" "test/golden/expected/body.rb" (test (Proxy :: Proxy BodyApi))
, goldenVsString "header" "test/golden/expected/header.rb" (test (Proxy :: Proxy HeaderApi))
, goldenVsString "query param" "test/golden/expected/query_param.rb" (test (Proxy :: Proxy QueryParamApi))
, goldenVsString "query params" "test/golden/expected/query_params.rb" (test (Proxy :: Proxy QueryParamsApi))
, goldenVsString "query flag" "test/golden/expected/query_flag.rb" (test (Proxy :: Proxy QueryFlagApi))
]


type StaticApi = "hello" :> "world" :> Get '[JSON] ()

type ParametersApi = Capture "butterfly" () :> Get '[JSON] ()

type BodyApi = ReqBody '[JSON] () :> Post '[JSON] ()

type HeaderApi = Header "moth" () :> Post '[JSON] ()

type QueryParamApi = QueryParam "spider" () :> Get '[JSON] ()

type QueryParamsApi = QueryParams "spiders" () :> Get '[JSON] ()

type QueryFlagApi = QueryFlag "vw-beetle" :> Get '[JSON] ()

test
:: (GenerateList NoContent (Foreign NoContent api), HasForeign NoTypes NoContent api)
=> Proxy api
-> IO ByteString
test = pure . encodeUtf8 . fromStrict . ruby (NameSpace ["Generated", "V1"] "Things")
33 changes: 33 additions & 0 deletions test/golden/expected/body.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require 'json'
require 'net/http'
require 'uri'

module Generated
module V1
class Things
def initialize(origin, timeout = nil)
@origin = URI(origin)
@http = Net::HTTP.new(@origin.host, @origin.port)

unless timeout.nil?
@http.open_timeout = timeout
@http.read_timeout = timeout
end
@http.use_ssl = @origin.scheme == 'https'
end

def post_uri
URI(@origin.to_s)
end

def post(body:)
req = Net::HTTP::Post.new(post_uri)
req['Content-Type'] = 'application/json'

@http.request(req, body)
end
end
end
end
33 changes: 33 additions & 0 deletions test/golden/expected/header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

require 'json'
require 'net/http'
require 'uri'

module Generated
module V1
class Things
def initialize(origin, timeout = nil)
@origin = URI(origin)
@http = Net::HTTP.new(@origin.host, @origin.port)

unless timeout.nil?
@http.open_timeout = timeout
@http.read_timeout = timeout
end
@http.use_ssl = @origin.scheme == 'https'
end

def post_uri
URI(@origin.to_s)
end

def post(moth:)
req = Net::HTTP::Post.new(post_uri)
req['moth'] = moth

@http.request(req)
end
end
end
end
Loading