Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 27 additions & 19 deletions src/TypedEnv.purs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ module TypedEnv

import Prelude

import Data.Either (Either, note)
import Data.Either (Either(..), note)
import Data.Generic.Rep (class Generic)
import Data.Int (fromString) as Int
import Data.Maybe (Maybe(..))
import Data.Number (fromString) as Number
import Data.Show.Generic (genericShow)
import Data.String.CodeUnits (uncons) as String
import Data.String.Common (toLower, joinWith)
import Data.Symbol (class IsSymbol, reflectSymbol)
import Data.String.Common (toLower)
import Data.Symbol (class IsSymbol, reflectSymbol)
import Foreign.Object (Object, lookup)
Expand All @@ -43,22 +45,28 @@ fromEnv
fromEnv = readEnv

-- | An error that can occur while reading an environment variable
data EnvError = EnvLookupError String | EnvParseError String
data EnvError = EnvLookupError String | EnvParseError String | EnvErrors (Array EnvError)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion, @srghma. I'm going to merge this change as-is but will follow up with your recommendation.


derive instance eqEnvError :: Eq EnvError

derive instance genericEnvError :: Generic EnvError _

instance semigroupEnvError :: Semigroup EnvError where
append (EnvErrors aErrors) (EnvErrors bErrors) = EnvErrors $ aErrors <> bErrors
append (EnvErrors errors) err = EnvErrors $ errors <> [err]
append err (EnvErrors errors) = EnvErrors $ [err] <> errors
append errA errB = EnvErrors [errA, errB]

instance showEnvError :: Show EnvError where
show = genericShow
show (EnvErrors errors) = joinWith "\n" $ map genericShow errors
show err = genericShow err

-- | Gets the error message for a given `EnvError` value.
envErrorMessage :: EnvError -> String
envErrorMessage = case _ of
EnvLookupError var -> "The required variable \"" <> var <>
"\" was not specified."
EnvParseError var -> "The variable \"" <> var <>
"\" was formatted incorrectly."
EnvLookupError var -> "The required variable \"" <> var <> "\" was not specified."
EnvParseError var -> "The variable \"" <> var <> "\" was formatted incorrectly."
EnvErrors errors -> joinWith "\n" $ map envErrorMessage errors

-- | Parses a `String` value to the specified type.
class ParseValue ty where
Expand Down Expand Up @@ -135,16 +143,16 @@ instance readEnvFieldsCons ::
, Row.Lacks name rt
, Row.Cons name ty rt r
, ReadValue ty
) =>
ReadEnvFields (Cons name ty elt) (Cons name ty rlt) r where
readEnvFields _ _ env = Record.insert nameP <$> value <*> tail
where
nameP = Proxy :: _ name
name = reflectSymbol (Proxy :: _ name)
value = readValue name env
tail = readEnvFields (Proxy :: _ elt) (Proxy :: _ rlt) env

instance readEnvFieldsNil ::
TypeEquals {} (Record row) =>
ReadEnvFields Nil Nil row where
) => ReadEnvFields (Cons name (Variable varName ty) elt) (Cons name ty rlt) r where
readEnvFields _ _ env = insert value tail
where
nameP = Proxy :: _ name
varName = reflectSymbol (Proxy :: _ varName)
value = readValue varName env
tail = readEnvFields (Proxy :: _ elt) (Proxy :: _ rlt) env

insert (Left valueErr) (Left tailErrs) = Left $ valueErr <> tailErrs
insert valE tailE = Record.insert nameP <$> valE <*> tailE

instance readEnvFieldsNil :: TypeEquals {} (Record row) => ReadEnvFields Nil Nil row where
readEnvFields _ _ _ = pure $ to {}
7 changes: 7 additions & 0 deletions test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ main = launchAff_ $ runSpec [ consoleReporter ] $
actual = fromEnv (Proxy :: _ ("DEBUG" :: Boolean)) env
actual `shouldEqual` expected

it "indicates when parsing multiple values has failed" do
let
env = FO.fromHomogeneous { "A": "err"}
expected = Left (EnvErrors [EnvParseError "A", EnvLookupError "B"])
actual = fromEnv (RProxy :: RProxy (a :: Int <: "A", b :: String <: "B")) env
actual `shouldEqual` expected

it "parses boolean values" do
traverse_
( \({ given, expected }) ->
Expand Down