packages feed

lackey-2.0.0.4: 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