ihp-1.4.0: IHP/EnvVar.hs
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 name = envOrDefault name (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 name defaultValue = fromMaybe defaultValue <$> envOrNothing name
{-# INLINE envOrDefault #-}
envOrNothing :: (MonadIO monad) => EnvVarReader result => ByteString -> monad (Maybe result)
envOrNothing name = liftIO $ fmap parseString <$> Posix.getEnv name
where
parseString string = case envStringToValue string of
Left errorMessage -> error [i|Env var '#{name}' is invalid: #{errorMessage}|]
Right value -> value
{-# INLINE envOrNothing #-}
hasEnvVar :: (MonadIO monad) => ByteString -> monad Bool
hasEnvVar name = liftIO do
value :: Maybe ByteString <- envOrNothing name
pure (isJust value)
{-# INLINE hasEnvVar #-}
class EnvVarReader valueType where
envStringToValue :: ByteString -> Either Text valueType
instance EnvVarReader Environment where
envStringToValue "Production" = Right Production
envStringToValue "Development" = Right Development
envStringToValue otherwise = Left "Should be set to 'Development' or 'Production'"
instance EnvVarReader Int where
envStringToValue string = case textToInt (cs string) of
Just integer -> Right integer
Nothing -> Left [i|Expected integer, got #{string}|]
instance EnvVarReader Text where
envStringToValue string = Right (cs string)
instance EnvVarReader String where
envStringToValue string = Right (cs string)
instance EnvVarReader ByteString where
envStringToValue string = Right string
instance EnvVarReader Bool where
envStringToValue "1" = Right True
envStringToValue "0" = Right False
envStringToValue otherwise = Left "Should be set to '1' or '0'"
-- | Allow reading the env var of an SMTP port number.
instance EnvVarReader PortNumber where
envStringToValue string = case textToInt (cs string) of
Just integer -> Right $ convertIntToPortNumber integer
Nothing -> Left [i|Expected integer to be used as a Port number, got #{string}|]
convertIntToPortNumber :: Int -> PortNumber
convertIntToPortNumber int = fromIntegral (int :: Int) :: PortNumber
-- | Allow reading the env var of an SMTP encryption method.
instance EnvVarReader SMTPEncryption where
envStringToValue "Unencrypted" = Right Unencrypted
envStringToValue "TLS" = Right TLS
envStringToValue "STARTTLS" = Right STARTTLS
envStringToValue otherwise = Left [i|Expected 'Unencrypted', 'TLS' or 'STARTTLS', got #{otherwise}|]