{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, TypeFamilies, ConstrainedClassMethods, ScopedTypeVariables, FunctionalDependencies, AllowAmbiguousTypes #-}
module IHP.ControllerSupport
( Action'
, (|>)
, getRequestBody
, getRequestPath
, getRequestPathAndQuery
, getHeader
, request
, requestHeaders
, getFiles
, Controller (..)
, runAction
, ControllerContext
, InitControllerContext (..)
, runActionWithNewContext
, initActionContext
, respondWith
, respondAndExit
, earlyReturn
, jumpToAction
, requestBodyJSON
, startWebSocketApp
, startWebSocketAppAndFailOnHTTP
, setHeader
, getAppConfig
, Respond
, Request
, rlsContextVaultKey
, initRequestContext
, ResponseReceived
) where
import Prelude
import Data.IORef (IORef, modifyIORef', readIORef)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LBS
import Data.Maybe (fromMaybe)
import Control.Exception.Safe (SomeException, fromException, try, throwIO)
import qualified Control.Exception as Exception
import qualified IHP.ErrorController as ErrorController
import Data.Typeable (Typeable)
import IHP.HaskellSupport
import Network.Wai
import qualified Network.HTTP.Types as HTTP
import IHP.ModelSupport
import Network.Wai.Parse as WaiParse
import qualified Data.ByteString.Lazy
import Wai.Request.Params.Middleware (Respond)
import qualified Data.CaseInsensitive
import qualified Data.Typeable as Typeable
import IHP.FrameworkConfig.Types (FrameworkConfig (..), ConfigProvider)
import IHP.Controller.Response
import IHP.RequestVault ()
import Network.Wai.Middleware.EarlyReturn (earlyReturnMiddleware)
import Network.HTTP.Types.Header
import qualified Data.Aeson as Aeson
import qualified Network.Wai.Handler.WebSockets as WebSockets
import qualified Network.WebSockets as WebSockets
import qualified Network.Wai.Internal as WaiInternal
import qualified IHP.WebSocket as WebSockets
import qualified Data.TMap as TypeMap
import IHP.RequestVault.ModelContext
import IHP.ActionType (setActionType, actionTypeVaultKey, ActionType(..))
import IHP.RequestVault.Helper (lookupRequestVault)
import qualified IHP.Environment as Environment
import qualified Data.Vault.Lazy as Vault
import qualified Data.Text as Text
import System.IO.Unsafe (unsafePerformIO)
type Action' = IO ResponseReceived
type ControllerContext = Request
class (Show controller, Eq controller) => Controller controller where
beforeAction :: (?context :: ControllerContext, ?modelContext :: ModelContext, ?theAction :: controller, ?respond :: Respond, ?request :: Request) => IO ()
beforeAction = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
{-# INLINABLE beforeAction #-}
action :: (?context :: ControllerContext, ?modelContext :: ModelContext, ?theAction :: controller, ?respond :: Respond, ?request :: Request) => controller -> IO ResponseReceived
class InitControllerContext application where
initContext :: (?modelContext :: ModelContext, ?request :: Request, ?respond :: Respond, ?context :: ControllerContext) => IO ()
initContext = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
{-# INLINABLE initContext #-}
instance InitControllerContext () where
initContext :: (?modelContext::ModelContext, ?request::ControllerContext,
?respond::Respond, ?context::ControllerContext) =>
IO ()
initContext = () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
{-# INLINE runAction #-}
runAction :: forall controller. (Controller controller, ?context :: ControllerContext, ?modelContext :: ModelContext, ?respond :: Respond) => controller -> IO ResponseReceived
runAction :: forall controller.
(Controller controller, ?context::ControllerContext,
?modelContext::ModelContext, ?respond::Respond) =>
controller -> IO ResponseReceived
runAction controller
controller = do
let ?theAction = controller
?theAction::controller
controller
let ?request = ?context::ControllerContext
?request::ControllerContext
ControllerContext
?context
authenticatedModelContext <- (?request::ControllerContext) => ModelContext -> IO ModelContext
ModelContext -> IO ModelContext
prepareRLSIfNeeded ?modelContext::ModelContext
ModelContext
?modelContext
let ?modelContext = authenticatedModelContext
beforeAction
action controller
{-# INLINE initActionContext #-}
initActionContext
:: forall application controller
. ( Controller controller
, ?request :: Request
, ?respond :: Respond
, InitControllerContext application
, ?application :: application
, Typeable application
, Typeable controller
)
=> controller -> IO ControllerContext
initActionContext :: forall application controller.
(Controller controller, ?request::ControllerContext,
?respond::Respond, InitControllerContext application,
?application::application, Typeable application,
Typeable controller) =>
controller -> IO ControllerContext
initActionContext controller
controller = do
let ?modelContext = ?request::ControllerContext
ControllerContext
?request.modelContext
let ?context = ?context::ControllerContext
?request::ControllerContext
ControllerContext
?request
IO () -> IO ()
wrapInitContextException (forall application.
(InitControllerContext application, ?modelContext::ModelContext,
?request::ControllerContext, ?respond::Respond,
?context::ControllerContext) =>
IO ()
forall {k} (application :: k).
(InitControllerContext application, ?modelContext::ModelContext,
?request::ControllerContext, ?respond::Respond,
?context::ControllerContext) =>
IO ()
initContext @application)
ControllerContext -> IO ControllerContext
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ?context::ControllerContext
ControllerContext
?context
{-# NOINLINE initRequestContext #-}
initRequestContext
:: forall application
. ( InitControllerContext application
, ?application :: application
, Typeable application
)
=> Typeable.TypeRep -> Request -> Respond
-> IO ControllerContext
initRequestContext :: forall application.
(InitControllerContext application, ?application::application,
Typeable application) =>
TypeRep -> ControllerContext -> Respond -> IO ControllerContext
initRequestContext TypeRep
controllerTypeRep ControllerContext
waiRequest Respond
waiRespond = do
let !request' :: ControllerContext
request' = ControllerContext
waiRequest { vault = Vault.insert actionTypeVaultKey (ActionType controllerTypeRep) waiRequest.vault }
let ?request = ?request::ControllerContext
ControllerContext
request'
let ?respond = ?respond::Respond
Respond
waiRespond
let ?modelContext = ControllerContext
request'.modelContext
let ?context = ?context::ControllerContext
?request::ControllerContext
ControllerContext
?request
IO () -> IO ()
wrapInitContextException (forall application.
(InitControllerContext application, ?modelContext::ModelContext,
?request::ControllerContext, ?respond::Respond,
?context::ControllerContext) =>
IO ()
forall {k} (application :: k).
(InitControllerContext application, ?modelContext::ModelContext,
?request::ControllerContext, ?respond::Respond,
?context::ControllerContext) =>
IO ()
initContext @application)
ControllerContext -> IO ControllerContext
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ?context::ControllerContext
ControllerContext
?context
wrapInitContextException :: IO () -> IO ()
wrapInitContextException :: IO () -> IO ()
wrapInitContextException IO ()
action =
IO ()
action IO () -> (SomeException -> IO ()) -> IO ()
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`Exception.catch` \(SomeException
e :: SomeException) ->
case SomeException -> Maybe EarlyReturnException
forall e. Exception e => SomeException -> Maybe e
fromException SomeException
e of
Just (EarlyReturnException ResponseReceived
_) -> SomeException -> IO ()
forall (m :: * -> *) e a.
(HasCallStack, MonadThrow m, Exception e) =>
e -> m a
throwIO SomeException
e
Maybe EarlyReturnException
Nothing -> InitContextException -> IO ()
forall (m :: * -> *) e a.
(HasCallStack, MonadThrow m, Exception e) =>
e -> m a
throwIO (SomeException -> InitContextException
ErrorController.InitContextException SomeException
e)
{-# INLINE runActionWithNewContext #-}
runActionWithNewContext :: forall application controller. (Controller controller, ?request :: Request, ?respond :: Respond, InitControllerContext application, ?application :: application, Typeable application, Typeable controller) => controller -> IO ResponseReceived
runActionWithNewContext :: forall application controller.
(Controller controller, ?request::ControllerContext,
?respond::Respond, InitControllerContext application,
?application::application, Typeable application,
Typeable controller) =>
controller -> IO ResponseReceived
runActionWithNewContext controller
controller =
Middleware
earlyReturnMiddleware (\ControllerContext
request Respond
respond -> do
let ?request = controller -> ControllerContext -> ControllerContext
forall controller.
Typeable controller =>
controller -> ControllerContext -> ControllerContext
setActionType controller
controller ControllerContext
request
let ?respond = ?respond::Respond
Respond
respond
context <- controller -> IO ControllerContext
forall application controller.
(Controller controller, ?request::ControllerContext,
?respond::Respond, InitControllerContext application,
?application::application, Typeable application,
Typeable controller) =>
controller -> IO ControllerContext
initActionContext controller
controller
let ?modelContext = requestModelContext ?request
let ?context = context
runAction controller
) ?request::ControllerContext
ControllerContext
?request ?respond::Respond
Respond
?respond
prepareRLSIfNeeded :: (?request :: Request) => ModelContext -> IO ModelContext
prepareRLSIfNeeded :: (?request::ControllerContext) => ModelContext -> IO ModelContext
prepareRLSIfNeeded ModelContext
modelContext = do
rowLevelSecurityContext <- IORef (Maybe RowLevelSecurityContext)
-> IO (Maybe RowLevelSecurityContext)
forall a. IORef a -> IO a
readIORef (Key (IORef (Maybe RowLevelSecurityContext))
-> ControllerContext -> IORef (Maybe RowLevelSecurityContext)
forall value.
Typeable value =>
Key value -> ControllerContext -> value
lookupRequestVault Key (IORef (Maybe RowLevelSecurityContext))
rlsContextVaultKey ?request::ControllerContext
ControllerContext
?request)
case rowLevelSecurityContext of
Just RowLevelSecurityContext
context -> ModelContext -> IO ModelContext
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ModelContext
modelContext { rowLevelSecurity = Just context }
Maybe RowLevelSecurityContext
Nothing -> ModelContext -> IO ModelContext
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ModelContext
modelContext
{-# INLINE prepareRLSIfNeeded #-}
rlsContextVaultKey :: Vault.Key (IORef (Maybe RowLevelSecurityContext))
rlsContextVaultKey :: Key (IORef (Maybe RowLevelSecurityContext))
rlsContextVaultKey = IO (Key (IORef (Maybe RowLevelSecurityContext)))
-> Key (IORef (Maybe RowLevelSecurityContext))
forall a. IO a -> a
unsafePerformIO IO (Key (IORef (Maybe RowLevelSecurityContext)))
forall a. IO (Key a)
Vault.newKey
{-# NOINLINE rlsContextVaultKey #-}
{-# INLINE startWebSocketApp #-}
startWebSocketApp :: forall webSocketApp application. (?request :: Request, ?respond :: Respond, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => webSocketApp -> IO ResponseReceived -> Application
startWebSocketApp :: forall webSocketApp application.
(?request::ControllerContext, ?respond::Respond,
InitControllerContext application, ?application::application,
Typeable application, WSApp webSocketApp) =>
webSocketApp
-> IO ResponseReceived
-> ControllerContext
-> Respond
-> IO ResponseReceived
startWebSocketApp webSocketApp
initialState IO ResponseReceived
onHTTP ControllerContext
waiRequest Respond
waiRespond = do
let ?modelContext = ControllerContext -> ModelContext
requestModelContext ?request::ControllerContext
ControllerContext
?request
let ?request = ?request::ControllerContext
ControllerContext
waiRequest
let ?respond = ?respond::Respond
Respond
waiRespond
let handleConnection :: PendingConnection -> IO ()
handleConnection PendingConnection
pendingConnection = do
connection <- PendingConnection -> IO Connection
WebSockets.acceptRequest PendingConnection
pendingConnection
let ?context = ?request
try (initContext @application) >>= \case
Left (SomeException
exception :: SomeException) -> String -> IO ()
putStrLn (String -> IO ()) -> String -> IO ()
forall a b. (a -> b) -> a -> b
$ String
"Unexpected exception in initContext, " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> SomeException -> String
forall a. Show a => a -> String
show SomeException
exception
Right ()
context -> do
webSocketApp -> Connection -> IO ()
forall state.
(WSApp state, ?request::ControllerContext,
?modelContext::ModelContext) =>
state -> Connection -> IO ()
WebSockets.startWSApp webSocketApp
initialState Connection
connection
let connectionOptions :: ConnectionOptions
connectionOptions = forall state. WSApp state => ConnectionOptions
WebSockets.connectionOptions @webSocketApp
ControllerContext
waiRequest
ControllerContext
-> (ControllerContext -> Maybe Response) -> Maybe Response
forall a b. a -> (a -> b) -> b
|> ConnectionOptions
-> (PendingConnection -> IO ())
-> ControllerContext
-> Maybe Response
WebSockets.websocketsApp ConnectionOptions
connectionOptions PendingConnection -> IO ()
handleConnection
Maybe Response
-> (Maybe Response -> IO ResponseReceived) -> IO ResponseReceived
forall a b. a -> (a -> b) -> b
|> \case
Just Response
response -> Respond
waiRespond (Response -> Response
rewriteWebSocketFallbackStatus Response
response)
Maybe Response
Nothing -> IO ResponseReceived
onHTTP
{-# INLINE startWebSocketAppAndFailOnHTTP #-}
startWebSocketAppAndFailOnHTTP :: forall webSocketApp application. (?request :: Request, ?respond :: Respond, InitControllerContext application, ?application :: application, Typeable application, WebSockets.WSApp webSocketApp) => webSocketApp -> Application
startWebSocketAppAndFailOnHTTP :: forall webSocketApp application.
(?request::ControllerContext, ?respond::Respond,
InitControllerContext application, ?application::application,
Typeable application, WSApp webSocketApp) =>
webSocketApp -> ControllerContext -> Respond -> IO ResponseReceived
startWebSocketAppAndFailOnHTTP webSocketApp
initialState = forall webSocketApp application.
(?request::ControllerContext, ?respond::Respond,
InitControllerContext application, ?application::application,
Typeable application, WSApp webSocketApp) =>
webSocketApp
-> IO ResponseReceived
-> ControllerContext
-> Respond
-> IO ResponseReceived
startWebSocketApp @webSocketApp @application webSocketApp
initialState (?respond::Respond
Respond
?respond Respond -> Respond
forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
HTTP.status400 [(HeaderName
hContentType, ByteString
"text/plain")] ByteString
"This endpoint is only available via a WebSocket")
rewriteWebSocketFallbackStatus :: Response -> Response
rewriteWebSocketFallbackStatus :: Response -> Response
rewriteWebSocketFallbackStatus (WaiInternal.ResponseRaw IO ByteString -> (ByteString -> IO ()) -> IO ()
handler Response
fallback) =
(IO ByteString -> (ByteString -> IO ()) -> IO ())
-> Response -> Response
WaiInternal.ResponseRaw IO ByteString -> (ByteString -> IO ()) -> IO ()
handler ((Status -> Status) -> Response -> Response
mapResponseStatus (Status -> Status -> Status
forall a b. a -> b -> a
const Status
HTTP.status200) Response
fallback)
rewriteWebSocketFallbackStatus Response
other = Response
other
jumpToAction :: forall action. (Controller action, ?context :: ControllerContext, ?modelContext :: ModelContext, ?respond :: Respond, ?request :: Request) => action -> IO ResponseReceived
jumpToAction :: forall action.
(Controller action, ?context::ControllerContext,
?modelContext::ModelContext, ?respond::Respond,
?request::ControllerContext) =>
action -> IO ResponseReceived
jumpToAction action
theAction = do
let ?theAction = action
?theAction::action
theAction
forall controller.
(Controller controller, ?context::ControllerContext,
?modelContext::ModelContext, ?theAction::controller,
?respond::Respond, ?request::ControllerContext) =>
IO ()
beforeAction @action
action -> IO ResponseReceived
forall controller.
(Controller controller, ?context::ControllerContext,
?modelContext::ModelContext, ?theAction::controller,
?respond::Respond, ?request::ControllerContext) =>
controller -> IO ResponseReceived
action action
theAction
getRequestBody :: (?request :: Request) => IO LBS.ByteString
getRequestBody :: (?request::ControllerContext) => IO ByteString
getRequestBody =
ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ?request::ControllerContext
ControllerContext
?request.parsedBody.rawPayload
getRequestPath :: (?request :: Request) => ByteString
getRequestPath :: (?request::ControllerContext) => ByteString
getRequestPath = ?request::ControllerContext
ControllerContext
?request.rawPathInfo
{-# INLINABLE getRequestPath #-}
getRequestPathAndQuery :: (?request :: Request) => ByteString
getRequestPathAndQuery :: (?request::ControllerContext) => ByteString
getRequestPathAndQuery = ?request::ControllerContext
ControllerContext
?request.rawPathInfo ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ?request::ControllerContext
ControllerContext
?request.rawQueryString
{-# INLINABLE getRequestPathAndQuery #-}
getHeader :: (?request :: Request) => ByteString -> Maybe ByteString
ByteString
name = HeaderName -> ResponseHeaders -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup (ByteString -> HeaderName
forall s. FoldCase s => s -> CI s
Data.CaseInsensitive.mk ByteString
name) ?request::ControllerContext
ControllerContext
?request.requestHeaders
{-# INLINABLE getHeader #-}
setHeader :: (?request :: Request) => Header -> IO ()
Header
header = do
let headersRef :: IORef ResponseHeaders
headersRef = Key (IORef ResponseHeaders)
-> ControllerContext -> IORef ResponseHeaders
forall value.
Typeable value =>
Key value -> ControllerContext -> value
lookupRequestVault Key (IORef ResponseHeaders)
responseHeadersVaultKey ?request::ControllerContext
ControllerContext
?request
IORef ResponseHeaders
-> (ResponseHeaders -> ResponseHeaders) -> IO ()
forall a. IORef a -> (a -> a) -> IO ()
modifyIORef' IORef ResponseHeaders
headersRef (Header
header Header -> ResponseHeaders -> ResponseHeaders
forall a. a -> [a] -> [a]
:)
{-# INLINABLE setHeader #-}
request :: (?request :: Request) => Request
request :: (?request::ControllerContext) => ControllerContext
request = ?request::ControllerContext
ControllerContext
?request
{-# INLINE request #-}
{-# INLINE getFiles #-}
getFiles :: (?request :: Request) => [File Data.ByteString.Lazy.ByteString]
getFiles :: (?request::ControllerContext) => [File ByteString]
getFiles =
case ?request::ControllerContext
ControllerContext
?request.parsedBody of
FormBody { [File ByteString]
files :: [File ByteString]
files :: RequestBody -> [File ByteString]
files } -> [File ByteString]
files
RequestBody
_ -> []
requestBodyJSON :: (?request :: Request, ?respond :: Respond) => IO Aeson.Value
requestBodyJSON :: (?request::ControllerContext, ?respond::Respond) => IO Value
requestBodyJSON =
case ?request::ControllerContext
ControllerContext
?request.parsedBody of
JSONBody { jsonPayload :: RequestBody -> Maybe Value
jsonPayload = Just Value
value } -> Value -> IO Value
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Value
value
JSONBody { jsonPayload :: RequestBody -> Maybe Value
jsonPayload = Maybe Value
Nothing, ByteString
rawPayload :: ByteString
rawPayload :: RequestBody -> ByteString
rawPayload } -> do
let isDev :: Bool
isDev = ?request::ControllerContext
ControllerContext
?request.frameworkConfig.environment Environment -> Environment -> Bool
forall a. Eq a => a -> a -> Bool
== Environment
Environment.Development
let errorMessage :: Text
errorMessage = Text
"Expected JSON body, but could not decode the request body"
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (if ByteString -> Bool
LBS.null ByteString
rawPayload
then Text
". The request body is empty."
else if Bool
isDev
then Text
". The raw request body was: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ByteString -> Text
forall {a}. Show a => a -> Text
truncatePayload ByteString
rawPayload
else Text
".")
Response -> IO Value
forall a.
(?request::ControllerContext, ?respond::Respond) =>
Response -> IO a
respondAndExit (Response -> IO Value) -> Response -> IO Value
forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
HTTP.status400 [(HeaderName
hContentType, ByteString
"application/json")] (ByteString -> Response) -> ByteString -> Response
forall a b. (a -> b) -> a -> b
$
Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode (Value -> ByteString) -> Value -> ByteString
forall a b. (a -> b) -> a -> b
$ [Pair] -> Value
Aeson.object [(Key
"error", Text -> Value
Aeson.String Text
errorMessage)]
where
truncatePayload :: a -> Text
truncatePayload a
payload =
let shown :: String
shown = a -> String
forall a. Show a => a -> String
show a
payload
maxLen :: Int
maxLen = Int
200
in if String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
shown Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
maxLen
then String -> Text
Text.pack (Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
maxLen String
shown) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"... (truncated)"
else String -> Text
Text.pack String
shown
FormBody {} ->
Response -> IO Value
forall a.
(?request::ControllerContext, ?respond::Respond) =>
Response -> IO a
respondAndExit (Response -> IO Value) -> Response -> IO Value
forall a b. (a -> b) -> a -> b
$ Status -> ResponseHeaders -> ByteString -> Response
responseLBS Status
HTTP.status400 [(HeaderName
hContentType, ByteString
"application/json")] (ByteString -> Response) -> ByteString -> Response
forall a b. (a -> b) -> a -> b
$
Value -> ByteString
forall a. ToJSON a => a -> ByteString
Aeson.encode (Value -> ByteString) -> Value -> ByteString
forall a b. (a -> b) -> a -> b
$ [Pair] -> Value
Aeson.object [(Key
"error", Text -> Value
Aeson.String Text
"Expected JSON body, but the request has a form content type. Make sure to set 'Content-Type: application/json' in the request header.")]
getAppConfig :: forall configParameter context. (?context :: context, ConfigProvider context, Typeable configParameter) => configParameter
getAppConfig :: forall configParameter context.
(?context::context, ConfigProvider context,
Typeable configParameter) =>
configParameter
getAppConfig = context
?context::context
?context.frameworkConfig.appConfig
TMap -> (TMap -> Maybe configParameter) -> Maybe configParameter
forall a b. a -> (a -> b) -> b
|> forall a. Typeable a => TMap -> Maybe a
TypeMap.lookup @configParameter
Maybe configParameter
-> (Maybe configParameter -> configParameter) -> configParameter
forall a b. a -> (a -> b) -> b
|> configParameter -> Maybe configParameter -> configParameter
forall a. a -> Maybe a -> a
fromMaybe (String -> configParameter
forall a. HasCallStack => String -> a
error (String
"Could not find " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> (TypeRep -> String
forall a. Show a => a -> String
show (Proxy configParameter -> TypeRep
forall {k} (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
Typeable.typeRep (forall t. Proxy t
forall {k} (t :: k). Proxy t
Typeable.Proxy @configParameter))) String -> String -> String
forall a. Semigroup a => a -> a -> a
<>String
" in config"))
{-# INLINE getAppConfig #-}