module Configuration.Dotenv.Scheme ( checkConfig , loadSafeFile , runSchemaChecker ) where import Control.Monad import Control.Monad.IO.Class (MonadIO(..)) import Data.Yaml (decodeFileEither, prettyPrintParseException) import Text.Megaparsec import System.Directory (doesFileExist) import Configuration.Dotenv (loadFile) import Configuration.Dotenv.Types (Config(..)) import Configuration.Dotenv.Scheme.Helpers import Configuration.Dotenv.Scheme.Parser import Configuration.Dotenv.Scheme.Types -- | @loadSafeFile@ parses the /.scheme.yml/ file and will perform the type checking -- of the environment variables in the /.env/ file. loadSafeFile :: MonadIO m => FilePath -> Config -> m [(String, String)] loadSafeFile schemaFile config = do envs <- loadFile config liftIO (readScheme schemaFile >>= checkConfig envs) return envs readScheme :: FilePath -> IO [Env] readScheme schemeFile = do eitherEnvConf <- decodeFileEither schemeFile case eitherEnvConf of Right envConfs -> return envConfs Left errorYaml -> error (prettyPrintParseException errorYaml) checkConfig :: [(String, String)] -> [Env] -> IO () checkConfig envvars envsWithType = let prettyParsedErrors = unlines . fmap parseErrorTextPretty envsTypeAndValue = joinEnvs envsWithType envvars valuesAndTypes = matchValueAndType envsTypeAndValue dotenvsMissing = filter required (missingDotenvs envsWithType envsTypeAndValue) schemeEnvsMissing = missingSchemeEnvs envvars envsTypeAndValue in do unless (null dotenvsMissing) (error $ "The following envs: " ++ showMissingDotenvs dotenvsMissing ++ " must be in the dotenvs") unless (null schemeEnvsMissing) (error $ "The following envs: " ++ showMissingSchemeEnvs schemeEnvsMissing ++ " must be in your scheme.yml") case parseEnvsWithScheme valuesAndTypes of Left errors -> error (prettyParsedErrors errors) _ -> return () runSchemaChecker :: FilePath -> Config -> IO () runSchemaChecker schemeFile config = do exists <- doesFileExist schemeFile when exists (void $ loadSafeFile schemeFile config)