mmzk-env 0.2.1.1 → 0.3.0.0
raw patch · 6 files changed
+108/−66 lines, 6 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.Env.DefaultBool: DefaultBool :: Bool -> DefaultBool (def :: Bool)
- Data.Env.DefaultBool: boolDefault :: BoolDefault b => Proxy b -> Bool
- Data.Env.DefaultBool: class BoolDefault (b :: Bool)
- Data.Env.DefaultBool: instance Data.Env.DefaultBool.BoolDefault 'GHC.Types.False
- Data.Env.DefaultBool: instance Data.Env.DefaultBool.BoolDefault 'GHC.Types.True
- Data.Env.DefaultBool: instance Data.Env.DefaultBool.BoolDefault def => Data.Env.TypeParser.TypeParser (Data.Env.DefaultBool.DefaultBool def)
- Data.Env.DefaultBool: instance GHC.Classes.Eq (Data.Env.DefaultBool.DefaultBool def)
- Data.Env.DefaultBool: instance GHC.Classes.Ord (Data.Env.DefaultBool.DefaultBool def)
- Data.Env.DefaultBool: instance GHC.Show.Show (Data.Env.DefaultBool.DefaultBool def)
- Data.Env.DefaultBool: newtype DefaultBool (def :: Bool)
+ Data.Env.Witness.DefaultBool: data DefaultBool (b :: Bool) a
+ Data.Env.Witness.DefaultBool: instance Data.Env.Witness.DefaultBool.BoolVal 'GHC.Types.False
+ Data.Env.Witness.DefaultBool: instance Data.Env.Witness.DefaultBool.BoolVal 'GHC.Types.True
+ Data.Env.Witness.DefaultBool: instance Data.Env.Witness.DefaultBool.BoolVal b => Data.Env.TypeParserW.TypeParserW (Data.Env.Witness.DefaultBool.DefaultBool b GHC.Types.Bool) GHC.Types.Bool
Files
- CHANGELOG.md +8/−0
- mmzk-env.cabal +2/−2
- src/Data/Env/DefaultBool.hs +0/−43
- src/Data/Env/Witness/DefaultBool.hs +62/−0
- test/DefaultBoolSpec.hs +23/−13
- test/RecordParserWSpec.hs +13/−8
CHANGELOG.md view
@@ -1,6 +1,14 @@ # Revision history for mmzk-env +## 0.3.0.0 -- 2026-03-22++* Fixed `DefaultBool` to follow the proper witness pattern:+ * Changed from a newtype with `TypeParser` to a phantom type with `TypeParserW`.+ * Added second type parameter (consistent with `DefaultNum` and `DefaultString`) enabling use with the `Di` type alias.+* `DefaultBool` now accepts `True`, `False`, `true`, `false`, `T`, `F`, `t`, `f`, `1`, `0`.++ ## 0.2.1.1 -- 2026-03-22 * Added `DefaultBool` for `Bool` fields with a type-level default value.
mmzk-env.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: mmzk-env-version: 0.2.1.1+version: 0.3.0.0 synopsis: Read environment variables into a user-defined data type description:@@ -51,13 +51,13 @@ import: settings exposed-modules: Data.Env- Data.Env.DefaultBool Data.Env.EnumParser Data.Env.ExtractFields Data.Env.RecordParser Data.Env.RecordParserW Data.Env.TypeParser Data.Env.TypeParserW+ Data.Env.Witness.DefaultBool Data.Env.Witness.DefaultNum Data.Env.Witness.DefaultString build-depends:
− src/Data/Env/DefaultBool.hs
@@ -1,43 +0,0 @@--- |--- Module: Data.Env.DefaultBool--- Description: A helper type for Bool fields with a type-level default value.------ This module provides 'DefaultBool', a newtype wrapping 'Bool' whose--- 'TypeParser' instance returns a type-level default when the input is empty.------ Example usage:------ > data Config = Config--- > { debug :: DefaultBool 'False--- > , verbose :: DefaultBool 'True--- > } deriving (Show, Generic, EnvSchema)--- >--- > -- DEBUG unset → DefaultBool False--- > -- DEBUG=True → DefaultBool True--- > -- DEBUG=False → DefaultBool False-module Data.Env.DefaultBool where--import Data.Env.TypeParser (TypeParser(..))-import Data.Proxy (Proxy(..))---- | A 'Bool' field with a type-level default.------ When the environment variable is absent (empty string) the field takes the--- value @def@; otherwise the string is parsed as a regular 'Bool'.-newtype DefaultBool (def :: Bool) = DefaultBool Bool- deriving (Show, Eq, Ord)---- | Reflect a type-level 'Bool' to a value-level 'Bool'.-class BoolDefault (b :: Bool) where- boolDefault :: Proxy b -> Bool--instance BoolDefault 'True where- boolDefault _ = True--instance BoolDefault 'False where- boolDefault _ = False--instance BoolDefault def => TypeParser (DefaultBool def) where- parseType "" = Right (DefaultBool (boolDefault (Proxy :: Proxy def)))- parseType s = DefaultBool <$> parseType s- {-# INLINE parseType #-}
+ src/Data/Env/Witness/DefaultBool.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE MultiParamTypeClasses #-}++{- |+Witness type for parsing 'Bool' with a type-level default value.+-}+module Data.Env.Witness.DefaultBool (+ DefaultBool,+) where++import Data.Env.TypeParser+import Data.Env.TypeParserW+import Data.Proxy++-- | Witness type for parsing 'Bool' with a default value.+--+-- The type parameter @b@ is a type-level 'Bool' that specifies the default+-- value used when the environment variable is absent (empty string).+-- The type parameter @a@ is the parsed type (always 'Bool').+--+-- ==== __Examples__+--+-- >>> parseTypeW (Proxy @(DefaultBool 'False Bool)) ""+-- Right False+--+-- >>> parseTypeW (Proxy @(DefaultBool 'True Bool)) ""+-- Right True+--+-- >>> parseTypeW (Proxy @(DefaultBool 'False Bool)) "True"+-- Right True+--+-- >>> parseTypeW' (Proxy @(DefaultBool 'False Bool)) "invalid"+-- Nothing+data DefaultBool (b :: Bool) a++class BoolVal (b :: Bool) where+ boolVal :: Proxy b -> Bool++instance BoolVal 'True where+ boolVal _ = True++instance BoolVal 'False where+ boolVal _ = False++-- | Parse a 'Bool' with a default fallback.+--+-- When the input string is empty, returns the default value specified by the+-- type-level 'Bool' @b@. Otherwise, attempts to parse the string as 'Bool',+-- accepting @True@, @False@, @true@, @false@, @T@, @F@, @t@, @f@, @1@, @0@.+instance BoolVal b => TypeParserW (DefaultBool b Bool) Bool where+ parseTypeW :: Proxy (DefaultBool b Bool) -> String -> Either String Bool+ parseTypeW _ str = case str of+ "" -> Right (boolVal (Proxy :: Proxy b))+ "true" -> Right True+ "false" -> Right False+ "T" -> Right True+ "F" -> Right False+ "t" -> Right True+ "f" -> Right False+ "1" -> Right True+ "0" -> Right False+ _ -> parseType str+ {-# INLINE parseTypeW #-}
test/DefaultBoolSpec.hs view
@@ -1,23 +1,33 @@ module DefaultBoolSpec ( spec ) where import Data.Either ( isLeft )-import Data.Env.DefaultBool-import Data.Env.TypeParser+import Data.Env.TypeParserW+import Data.Env.Witness.DefaultBool+import Data.Proxy import Test.Hspec spec :: Spec spec = describe "DefaultBool" do- it "parses True" do- parseType @(DefaultBool 'False) "True" `shouldBe` Right (DefaultBool True)- parseType @(DefaultBool 'True) "True" `shouldBe` Right (DefaultBool True)- it "parses False" do- parseType @(DefaultBool 'False) "False" `shouldBe` Right (DefaultBool False)- parseType @(DefaultBool 'True) "False" `shouldBe` Right (DefaultBool False) it "defaults to False when empty" do- parseType @(DefaultBool 'False) "" `shouldBe` Right (DefaultBool False)+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "" `shouldBe` Right False it "defaults to True when empty" do- parseType @(DefaultBool 'True) "" `shouldBe` Right (DefaultBool True)+ parseTypeW (Proxy @(DefaultBool 'True Bool)) "" `shouldBe` Right True+ it "parses True / False" do+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "True" `shouldBe` Right True+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "False" `shouldBe` Right False+ it "parses true / false" do+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "true" `shouldBe` Right True+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "false" `shouldBe` Right False+ it "parses T / F" do+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "T" `shouldBe` Right True+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "F" `shouldBe` Right False+ it "parses t / f" do+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "t" `shouldBe` Right True+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "f" `shouldBe` Right False+ it "parses 1 / 0" do+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "1" `shouldBe` Right True+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "0" `shouldBe` Right False it "fails to parse other values" do- parseType @(DefaultBool 'False) "1" `shouldSatisfy` isLeft- parseType @(DefaultBool 'False) "true" `shouldSatisfy` isLeft- parseType @(DefaultBool 'False) "false" `shouldSatisfy` isLeft+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "yes" `shouldSatisfy` isLeft+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "no" `shouldSatisfy` isLeft+ parseTypeW (Proxy @(DefaultBool 'False Bool)) "invalid" `shouldSatisfy` isLeft
test/RecordParserWSpec.hs view
@@ -11,6 +11,7 @@ import Data.Map qualified as M import GHC.Generics import Test.Hspec+import Data.Env.Witness.DefaultBool (DefaultBool) data NameW @@ -23,9 +24,10 @@ data Config c = Config- { port :: Di (DefaultNum 5432) c Int- , env :: Di Solo c String- , name :: Column c NameW String+ { port :: Di (DefaultNum 5432) c Int+ , env :: Di Solo c String+ , name :: Column c NameW String+ , debug :: Di (DefaultBool 'False) c Bool } deriving (Generic) @@ -34,15 +36,18 @@ spec :: Spec spec = describe "parseRecordW for Config" do- it "uses default port 5432 when port not specified" do+ it "uses default port 5432 and default debug False when not specified" do let envMap = M.fromList [("env", "production"), ("name", "name")]- parseRecordW @(Config 'Dec) envMap `shouldBe` Right (Config 5432 "production" "name")- it "parses config with custom port" do- let envMap = M.fromList [("port", "8080"), ("env", "development"), ("name", "name")]- parseRecordW @(Config 'Dec) envMap `shouldBe` Right (Config 8080 "development" "name")+ parseRecordW @(Config 'Dec) envMap `shouldBe` Right (Config 5432 "production" "name" False)+ it "parses config with custom port and explicit debug" do+ let envMap = M.fromList [("port", "8080"), ("env", "development"), ("name", "name"), ("debug", "True")]+ parseRecordW @(Config 'Dec) envMap `shouldBe` Right (Config 8080 "development" "name" True) it "fails to parse invalid port value" do let envMap = M.fromList [("port", "not-a-number"), ("env", "test"), ("name", "name")] parseRecordW @(Config 'Dec) envMap `shouldSatisfy` isLeft it "fails to parse invalid name value" do let envMap = M.fromList [("env", "test"), ("name", "1234567890poiuytrewq")]+ parseRecordW @(Config 'Dec) envMap `shouldSatisfy` isLeft+ it "fails to parse invalid debug value" do+ let envMap = M.fromList [("env", "test"), ("name", "name"), ("debug", "yes")] parseRecordW @(Config 'Dec) envMap `shouldSatisfy` isLeft