feat: add Config module for YAML config loading
This commit is contained in:
@@ -46,6 +46,7 @@ dependencies:
|
||||
- time
|
||||
- wai
|
||||
- warp
|
||||
- yaml
|
||||
|
||||
library:
|
||||
source-dirs: src
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user