packages feed

effectful-config-file-secretspec (empty) → 0.1.0.0

raw patch · 5 files changed

+219/−0 lines, 5 filesdep +basedep +containersdep +effectful

Dependencies added: base, containers, effectful, effectful-config-file, effectful-config-file-secretspec, filepath, hspec, lens, secretspec, temporary, text

Files

+ README.md view
@@ -0,0 +1,27 @@+# effectful-config-file-secretspec++SecretSpec-backed Secrets effect handler for effectful-config-file.++## Installing libsecretspec++The `secretspec` Haskell package links against the libsecretspec C library,+found through `pkg-config`. Build it from the+[secretspec](https://github.com/cachix/secretspec) tag matching the+`secretspec` extra-dep in `stack.yaml`.++Requirements: [rustup](https://rustup.rs), `pkg-config` and+[cargo-c](https://github.com/lu-zero/cargo-c) (`cargo install cargo-c`).++```bash+git clone --depth 1 --branch v0.21.1 https://github.com/cachix/secretspec.git+cd secretspec+cargo cinstall -p libsecretspec --manifest-path Cargo.toml \+  --library-type staticlib \+  --prefix "$HOME/.libsecretspec" \+  --bindir lib \+  --libdir lib \+  --pkgconfigdir lib/pkgconfig+export PKG_CONFIG_PATH="$HOME/.libsecretspec/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"+```++Check it with `pkg-config --libs libsecretspec`, then build with `stack build`.
+ effectful-config-file-secretspec.cabal view
@@ -0,0 +1,66 @@+cabal-version: 2.2++-- This file has been generated from package.yaml by hpack version 0.39.6.+--+-- see: https://github.com/sol/hpack++name:           effectful-config-file-secretspec+version:        0.1.0.0+synopsis:       SecretSpec backend for effectful-config-file+description:    SecretSpec-backed Secrets effect handler for effectful-config-file+category:       Configuration+author:         drlkf+maintainer:     drlkf@drlkf.net+copyright:      2026 drlkf+license:        BSD-3-Clause+build-type:     Simple+extra-source-files:+    README.md++library+  exposed-modules:+      Effectful.Config.Secrets.SecretSpec+  other-modules:+      Paths_effectful_config_file_secretspec+  autogen-modules:+      Paths_effectful_config_file_secretspec+  hs-source-dirs:+      src+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+  build-tool-depends:+      hspec-discover:hspec-discover+  build-depends:+      base >=4.7 && <5+    , containers >=0.2 && <1+    , effectful >=2.6 && <3+    , effectful-config-file >=0.1 && <2+    , secretspec >=0.21 && <1+    , text >=2.1 && <3+  default-language: Haskell2010++test-suite effectful-config-file-secretspec-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Effectful.Config.Secrets.SecretSpecSpec+      Paths_effectful_config_file_secretspec+  autogen-modules:+      Paths_effectful_config_file_secretspec+  hs-source-dirs:+      test+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+  build-tool-depends:+      hspec-discover:hspec-discover+  build-depends:+      base >=4.7 && <5+    , containers >=0.2 && <1+    , effectful >=2.6 && <3+    , effectful-config-file >=0.1 && <2+    , effectful-config-file-secretspec+    , filepath+    , hspec+    , lens+    , secretspec >=0.21 && <1+    , temporary+    , text >=2.1 && <3+  default-language: Haskell2010
+ src/Effectful/Config/Secrets/SecretSpec.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TypeOperators #-}++module Effectful.Config.Secrets.SecretSpec (+  runSecretSpec,+) where++import qualified Data.Map.Strict as Map+import Effectful (Eff, IOE, liftIO, type (:>))+import Effectful.Config.Secrets (Secrets (..), SecretsError (..))+import Effectful.Dispatch.Dynamic (interpret)+import Effectful.Error.Static (Error, throwError)+import Effectful.Exception (bracket)+import SecretSpec (Builder, builder, close, get, load, resolvedSecrets)++-- | @since 0.1.0.0+runSecretSpec+  :: IOE :> es+  => Error SecretsError :> es+  => (Builder -> Builder)+  -> Eff (Secrets : es) a+  -> Eff es a+runSecretSpec f act =+  bracket (liftIO (load (f builder))) (liftIO . close) $ \r ->+    interpret+      ( \_ -> \case+          GetSecret k ->+            maybe+              (throwError (SecretNotFound k))+              pure+              (Map.lookup k (resolvedSecrets r) >>= get)+      )+      act
+ test/Effectful/Config/Secrets/SecretSpecSpec.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}++module Effectful.Config.Secrets.SecretSpecSpec (+  spec,+) where++import Control.Concurrent.MVar (newMVar)+import Control.Lens (makeLenses)+import Data.Config (ConfigValue (..))+import qualified Data.Text as T (pack)+import Effectful (runEff)+import Effectful.Config (getConfigValue)+import Effectful.Config.Log (runLog)+import Effectful.Config.Secrets (SecretsError (..), getSecret)+import Effectful.Config.Secrets.SecretSpec (runSecretSpec)+import Effectful.Error.Static (runErrorNoCallStack)+import Effectful.Reader.Static (runReader)+import SecretSpec (Builder, withPath, withProvider, withReason)+import System.FilePath ((</>))+import System.IO.Temp (withSystemTempDirectory)+import Test.Hspec (Spec, describe, it, shouldBe)++newtype SecretConfig = SecretConfig+  { _secret_val :: ConfigValue String+  }+  deriving (Eq, Show)++makeLenses ''SecretConfig++silentLog :: a -> b -> c -> d -> IO ()+silentLog _ _ _ _ = pure ()++withManifest :: (FilePath -> (Builder -> Builder) -> IO a) -> IO a+withManifest act = withSystemTempDirectory "secretspec" $ \dir -> do+  writeFile (dir </> ".env") "MY_SECRET=hunter2\n"+  writeFile+    (dir </> "secretspec.toml")+    ( unlines+        [ "[project]"+        , "name = \"test\""+        , "revision = \"1.0\""+        , ""+        , "[profiles.default]"+        , "MY_SECRET = { description = \"test\", required = true }"+        ]+    )+  act+    dir+    ( withPath (T.pack (dir </> "secretspec.toml"))+        . withProvider (T.pack ("dotenv://" <> (dir </> ".env")))+        . withReason "test"+    )++spec :: Spec+spec = do+  describe "runSecretSpec" $ do+    it "resolves a declared secret" $ withManifest $ \_ cfg -> do+      result <-+        runEff+          . runErrorNoCallStack @SecretsError+          . runSecretSpec cfg+          $ getSecret "MY_SECRET"++      result `shouldBe` Right "hunter2"++    it "errors on an undeclared secret" $ withManifest $ \_ cfg -> do+      result <-+        runEff+          . runErrorNoCallStack @SecretsError+          . runSecretSpec cfg+          $ getSecret "NOPE"++      result `shouldBe` Left (SecretNotFound "NOPE")++  describe "getConfigValue" $ do+    it "resolves a Secret config value via secretspec" $ withManifest $ \_ cfg -> do+      p <- newMVar (SecretConfig (Secret "MY_SECRET"))+      result <-+        runEff+          . runErrorNoCallStack @SecretsError+          . runSecretSpec cfg+          . runLog silentLog+          . runReader p+          $ getConfigValue secret_val++      result `shouldBe` Right "hunter2"
+ test/Spec.hs view
@@ -0,0 +1,2 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}+{-# OPTIONS_GHC -Wno-missing-export-lists #-}