Skip to content

Commit

Permalink
Support returning multiple errors when parsing env (#3)
Browse files Browse the repository at this point in the history
* Support returning multiple errors when parsing env

* rewrite to remove use of Tuple

* Use semigroup to join errors

* revert renaming in examples

---------

Co-authored-by: Nick Saunders <nick@saunde.rs>
  • Loading branch information
johncowie and nsaunders authored Feb 9, 2023
1 parent 889b3eb commit 24438d4
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 19 deletions.
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)

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

0 comments on commit 24438d4

Please sign in to comment.