module IHP.EnvVar
( env
, envOrDefault
, envOrNothing
, hasEnvVar
, EnvVarReader (..)
)
where

import IHP.Prelude
import Data.String.Interpolate.IsString (i)
import qualified System.Posix.Env.ByteString as Posix
import Network.Socket (PortNumber)
import IHP.Mail.Types
import IHP.Environment

-- | Returns a env variable. The raw string
-- value is parsed before returning it. So the return value type depends on what
-- you expect (e.g. can be Text, Int some custom type).
--
-- When the parameter is missing or cannot be parsed, an error is raised and
-- the app startup is aborted. Use 'envOrDefault' when you want to get a
-- default value instead of an error, or 'paramOrNothing' to get @Nothing@
-- when the env variable is missing.
--
-- You can define a custom env variable parser by defining a 'EnvVarReader' instance.
--
-- __Example:__ Accessing a env var PORT.
--
-- Let's say an env var PORT is set to 1337
--
-- > export PORT=1337
--
-- We can read @PORT@ like this:
--
-- > port <- env @Int "PORT"
--
-- __Example:__ Missing env vars
--
-- Let's say the @PORT@ env var is not defined. In that case we'll get an
-- error when trying to access it:
--
-- >>> port <- env @Int "PORT"
-- "Env var 'PORT' not set, but it's required for the app to run"
--
env :: forall result monad. (MonadIO monad) => EnvVarReader result => ByteString -> monad result
env :: forall result (monad :: * -> *).
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad result
env ByteString
name = ByteString -> result -> monad result
forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> result -> monad result
envOrDefault ByteString
name (Text -> result
forall a. Text -> a
error [i|Env var '#{name}' not set, but it's required for the app to run|])
{-# INLINE env #-}

envOrDefault :: (MonadIO monad) => EnvVarReader result => ByteString -> result -> monad result
envOrDefault :: forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> result -> monad result
envOrDefault ByteString
name result
defaultValue = result -> Maybe result -> result
forall a. a -> Maybe a -> a
fromMaybe result
defaultValue (Maybe result -> result) -> monad (Maybe result) -> monad result
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ByteString -> monad (Maybe result)
forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad (Maybe result)
envOrNothing ByteString
name
{-# INLINE envOrDefault #-}

envOrNothing :: (MonadIO monad) => EnvVarReader result => ByteString -> monad (Maybe result)
envOrNothing :: forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad (Maybe result)
envOrNothing ByteString
name = IO (Maybe result) -> monad (Maybe result)
forall a. IO a -> monad a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe result) -> monad (Maybe result))
-> IO (Maybe result) -> monad (Maybe result)
forall a b. (a -> b) -> a -> b
$ (ByteString -> result) -> Maybe ByteString -> Maybe result
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ByteString -> result
parseString (Maybe ByteString -> Maybe result)
-> IO (Maybe ByteString) -> IO (Maybe result)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ByteString -> IO (Maybe ByteString)
Posix.getEnv ByteString
name
    where
        parseString :: ByteString -> result
parseString ByteString
string = case ByteString -> Either Text result
forall valueType.
EnvVarReader valueType =>
ByteString -> Either Text valueType
envStringToValue ByteString
string of
            Left Text
errorMessage -> Text -> result
forall a. Text -> a
error [i|Env var '#{name}' is invalid: #{errorMessage}|]
            Right result
value -> result
value
{-# INLINE envOrNothing #-}

hasEnvVar :: (MonadIO monad) => ByteString -> monad Bool
hasEnvVar :: forall (monad :: * -> *). MonadIO monad => ByteString -> monad Bool
hasEnvVar ByteString
name = IO Bool -> monad Bool
forall a. IO a -> monad a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO do
    Maybe ByteString
value :: Maybe ByteString <- ByteString -> IO (Maybe ByteString)
forall (monad :: * -> *) result.
(MonadIO monad, EnvVarReader result) =>
ByteString -> monad (Maybe result)
envOrNothing ByteString
name
    Bool -> IO Bool
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe ByteString -> Bool
forall a. Maybe a -> Bool
isJust Maybe ByteString
value)
{-# INLINE hasEnvVar #-}

class EnvVarReader valueType where
    envStringToValue :: ByteString -> Either Text valueType

instance EnvVarReader Environment where
    envStringToValue :: ByteString -> Either Text Environment
envStringToValue ByteString
"Production"  = Environment -> Either Text Environment
forall a b. b -> Either a b
Right Environment
Production
    envStringToValue ByteString
"Development" = Environment -> Either Text Environment
forall a b. b -> Either a b
Right Environment
Development
    envStringToValue ByteString
otherwise     = Text -> Either Text Environment
forall a b. a -> Either a b
Left Text
"Should be set to 'Development' or 'Production'"

instance EnvVarReader Int where
    envStringToValue :: ByteString -> Either Text Int
envStringToValue ByteString
string = case Text -> Maybe Int
textToInt (ByteString -> Text
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
string) of
        Just Int
integer -> Int -> Either Text Int
forall a b. b -> Either a b
Right Int
integer
        Maybe Int
Nothing -> Text -> Either Text Int
forall a b. a -> Either a b
Left [i|Expected integer, got #{string}|]

instance EnvVarReader Text where
    envStringToValue :: ByteString -> Either Text Text
envStringToValue ByteString
string = Text -> Either Text Text
forall a b. b -> Either a b
Right (ByteString -> Text
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
string)

instance EnvVarReader String where
    envStringToValue :: ByteString -> Either Text String
envStringToValue ByteString
string = String -> Either Text String
forall a b. b -> Either a b
Right (ByteString -> String
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
string)

instance EnvVarReader ByteString where
    envStringToValue :: ByteString -> Either Text ByteString
envStringToValue ByteString
string = ByteString -> Either Text ByteString
forall a b. b -> Either a b
Right ByteString
string

instance EnvVarReader Bool where
    envStringToValue :: ByteString -> Either Text Bool
envStringToValue ByteString
"1"       = Bool -> Either Text Bool
forall a b. b -> Either a b
Right Bool
True
    envStringToValue ByteString
"0"       = Bool -> Either Text Bool
forall a b. b -> Either a b
Right Bool
False
    envStringToValue ByteString
otherwise = Text -> Either Text Bool
forall a b. a -> Either a b
Left Text
"Should be set to '1' or '0'"

-- | Allow reading the env var of an SMTP port number.
instance EnvVarReader PortNumber where
    envStringToValue :: ByteString -> Either Text PortNumber
envStringToValue ByteString
string = case Text -> Maybe Int
textToInt (ByteString -> Text
forall a b. ConvertibleStrings a b => a -> b
cs ByteString
string) of
        Just Int
integer -> PortNumber -> Either Text PortNumber
forall a b. b -> Either a b
Right (PortNumber -> Either Text PortNumber)
-> PortNumber -> Either Text PortNumber
forall a b. (a -> b) -> a -> b
$ Int -> PortNumber
convertIntToPortNumber Int
integer
        Maybe Int
Nothing -> Text -> Either Text PortNumber
forall a b. a -> Either a b
Left [i|Expected integer to be used as a Port number, got #{string}|]

convertIntToPortNumber :: Int -> PortNumber
convertIntToPortNumber :: Int -> PortNumber
convertIntToPortNumber Int
int = Int -> PortNumber
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
int :: Int) :: PortNumber

-- | Allow reading the env var of an SMTP encryption method.
instance EnvVarReader SMTPEncryption where
    envStringToValue :: ByteString -> Either Text SMTPEncryption
envStringToValue ByteString
"Unencrypted" = SMTPEncryption -> Either Text SMTPEncryption
forall a b. b -> Either a b
Right SMTPEncryption
Unencrypted
    envStringToValue ByteString
"TLS"  = SMTPEncryption -> Either Text SMTPEncryption
forall a b. b -> Either a b
Right SMTPEncryption
TLS
    envStringToValue ByteString
"STARTTLS"  = SMTPEncryption -> Either Text SMTPEncryption
forall a b. b -> Either a b
Right SMTPEncryption
STARTTLS
    envStringToValue ByteString
otherwise = Text -> Either Text SMTPEncryption
forall a b. a -> Either a b
Left [i|Expected 'Unencrypted', 'TLS' or 'STARTTLS', got #{otherwise}|]