feat: add Config module for YAML config loading

This commit is contained in:
2026-05-21 10:10:59 -04:00
parent 5abf5490b4
commit 1d5459ea1e
3 changed files with 78 additions and 0 deletions
+1
View File
@@ -46,6 +46,7 @@ dependencies:
- time
- wai
- warp
- yaml
library:
source-dirs: src
+5
View File
@@ -18,6 +18,7 @@ library
exposed-modules:
Data.CookLang
Roux
Roux.Config
Roux.CooklangPrint
Roux.Html
Roux.NYTimes
@@ -58,6 +59,7 @@ library
, time
, wai
, warp
, yaml
default-language: Haskell2010
executable check-recipes
@@ -95,6 +97,7 @@ executable check-recipes
, time
, wai
, warp
, yaml
default-language: Haskell2010
executable roux-server
@@ -133,6 +136,7 @@ executable roux-server
, unix
, wai
, warp
, yaml
default-language: Haskell2010
test-suite roux-server-test
@@ -179,4 +183,5 @@ test-suite roux-server-test
, time
, wai
, warp
, yaml
default-language: Haskell2010
+72
View File
@@ -0,0 +1,72 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{- | Configuration for Roux, loaded from a YAML config file.
Default location: ./roux-config.yaml in the working directory.
Overridable via --config-file CLI flag.
-}
module Roux.Config (
RouxConfig (..),
AnthropicConfig (..),
loadConfig,
) where
import Data.Text (Text)
import qualified Data.Yaml as Y
import qualified Fleece.Aeson as Fleece
import qualified Fleece.Aeson.Decoder as FleeceD
import Fleece.Core (constructor, object, optionalNullable, required, text, (#+))
import qualified Fleece.Core as FC
import System.Directory (doesFileExist)
-- | Top-level config.
data RouxConfig = RouxConfig
{ rcAnthropic :: Maybe AnthropicConfig
}
deriving stock (Eq, Show)
-- | Anthropic API configuration.
data AnthropicConfig = AnthropicConfig
{ acApiKey :: !Text
, acBaseUrl :: !(Maybe Text) -- default: https://api.anthropic.com
, acModel :: !(Maybe Text) -- default: claude-sonnet-4-20250514
}
deriving stock (Eq, Show)
-- | Fleece schema for AnthropicConfig.
anthropicConfigSchema :: FC.Schema Fleece.Decoder AnthropicConfig
anthropicConfigSchema =
object $
constructor AnthropicConfig
#+ required "api_key" acApiKey text
#+ optionalNullable FC.EmitNull "base_url" acBaseUrl text
#+ optionalNullable FC.EmitNull "model" acModel text
-- | Fleece schema for RouxConfig.
rouxConfigSchema :: FC.Schema Fleece.Decoder RouxConfig
rouxConfigSchema =
object $
constructor RouxConfig
#+ optionalNullable FC.EmitNull "anthropic" rcAnthropic anthropicConfigSchema
{- | Load config from a YAML file. Returns config with nothing set if the file
doesn't exist (LLM features will be unavailable but the server still runs).
Exits with an error if the file exists but is malformed.
-}
loadConfig :: FilePath -> IO RouxConfig
loadConfig path = do
exists <- doesFileExist path
if not exists
then pure (RouxConfig Nothing)
else do
result <- Y.decodeFileEither path
case result of
Left parseErr ->
fail $ "Failed to parse " <> path <> ": " <> show parseErr
Right val ->
case FleeceD.fromValue rouxConfigSchema val of
Right cfg -> pure cfg
Left decodeErr ->
fail $
"Invalid config format in " <> path <> ": " <> show decodeErr