packages feed

lackey-2.0.0.0: source/library/Lackey.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}

module Lackey
  ( rubyForAPI
  ) where

import qualified Data.Char as Char
import Data.Function ((&))
import qualified Data.Maybe as Maybe
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

type Language = Servant.NoTypes

languageProxy :: Proxy.Proxy Language
languageProxy = Proxy.Proxy

type Request = Servant.NoContent

requestProxy :: Proxy.Proxy Request
requestProxy = Proxy.Proxy

renderRequests :: [Servant.Req Request] -> Text.Text
renderRequests requests = requests & fmap 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
        )
    & fmap (Servant.unPathSegment . Servant._argName)

getURLPieces :: Servant.Req Request -> [Either Text.Text Text.Text]
getURLPieces request =
  let
    url = request & Servant._reqUrl
    path =
      url
        & Servant._path
        & Maybe.mapMaybe
            ((\segment -> case segment of
               Servant.Static _ -> Nothing
               Servant.Cap arg -> Just arg
             )
            . Servant.unSegment
            )
        & fmap (Servant.unPathSegment . Servant._argName)
    query = url & Servant._queryStr & fmap
      (Servant.unPathSegment . Servant._argName . Servant._queryArgName)
  in fmap Left path <> fmap Right query

functionArguments :: Servant.Req Request -> Text.Text
functionArguments request = Text.concat
  [ "("
  , [ [Just "excon"]
    , request & getURLPieces & fmap
      (\piece -> Just $ case piece of
        Left capture -> underscore capture
        Right param -> underscore param <> ": nil"
      )
    , request & getHeaders & fmap (Just . (<> ": nil") . underscore)
    , [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
        & fmap
            ((\x -> case x of
               Servant.Static y -> Servant.unPathSegment y
               Servant.Cap y ->
                 let
                   z =
                     y & Servant._argName & Servant.unPathSegment & underscore
                 in "#{" <> z <> "}"
             )
            . Servant.unSegment
            )
        & Text.intercalate "/"
    query =
      request
        & Servant._reqUrl
        & Servant._queryStr
        & fmap
            ((\x -> x <> "=#{" <> underscore x <> "}")
            . Servant.unPathSegment
            . Servant._argName
            . Servant._queryArgName
            )
        & Text.intercalate "&"
    url = "/" <> path <> (if Text.null query then "" else "?" <> query)
  in "\"" <> url <> "\""

requestHeaders :: Servant.Req Request -> Text.Text
requestHeaders request =
  [ ["{"]
    , request & getHeaders & fmap (\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