Reputation: 6793
I have written my medium-sized Haskell app with hard-coded config variables (like Google OAuth ClientId & ClientSecret). Now that I'm prepping the app for a production deployment, I need to move all these config variable out of the source, to either: (a) environment variables, or (b) a plain-text config file.
Here's what the code, currently, look likes:
googleClientId :: T.Text
googleClientId = "redacted"
googleClientSecret :: T.Text
googleClientSecret = "redacted"
generateOAuthUserCode :: IO (OAuthCodeResponse)
generateOAuthUserCode = do
r <- asJSON =<< post "https://accounts.google.com/o/oauth2/device/code" ["client_id" := googleClientId, "scope" := ("email profile" :: T.Text)]
return $ r ^. responseBody
What's the fastest/easiest way to get googleClientId
and googleClientSecret
from an environment variable (or config file)? I tried the following:
googleClientId :: T.Text
googleClientId = undefined
googleClientSecret :: T.Text
googleClientSecret = undefined
main :: IO()
main = do
googleClientId <- getEnv "GOOGLE_CLIENT_ID"
googleClientSecret <- getENV "GOOGLE_CLIENT_SECRET"
-- Start the main app, which internally will call generateOAuthUserCode at some point.
The expectation was that the global googleClientId
and googleClientSecret
will be re-bound, but my editor immediately started showing a warning that "the binding shadows an existing binding", indicating that Haskell is creating a new binding, instead of changing the existing one.
So, two questions here:
Edit: What about the following approach?
what about the following approach?
outerFunc :: String -> String -> IO ()
outerFunc googleClientId googleClientSecret = do
-- more code comes here
where
generateOAuthUserCode :: IO (OAuthCodeResponse)
generateOAuthUserCode = do
r <- asJSON =<< post "https://accounts.google.com/o/oauth2/device/code" ["client_id" := googleClientId, "scope" := ("email profile" :: T.Text)]
return $ r ^. responseBody
-- more functions depending upon the config variables
Upvotes: 2
Views: 483
Reputation: 3455
I assumed that you rely on global variables like googleClientId
often in your codebase.
You might want to try and at least estimate the cost of the Reader
approach before you go the "technical debt" route. Global variables are a bad practice anyways, @Carsten has proposed an alternative refactoring. But since you are asking for pragmatic help...
Question 1: The fastest/easiest way is to use the frowned-upon unsafePerformIO
. Like this:
googleClientId = unsafePerformIO $ getEnv "GOOGLE_CLIENT_ID"
main = putStrLn googleClientId
This basically lets you ignore the safety of IO
and puts the desired value into a global variable as if it was a plain string. Please note that getEnv
crashes if the environment variable does not exist.
Question 2: One cannot "update" variables in Haskell. If one creates another variable with the same name in a nested scope, that binding will shadow the outer one in the inner scope, leaving the outer binding intact. That is somewhat confusing, hence the warning.
Upvotes: 4