packages feed

spacecookie-1.1.0.0: server/Network/Spacecookie/Config.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP               #-}
module Network.Spacecookie.Config
  ( Config (..)
  , LogConfig (..)
  ) where

import Control.Monad (mzero, join)
import Control.Applicative ((<|>))
import Data.Aeson
import Data.Aeson.Types (Parser ())
import Data.ByteString (ByteString ())
import qualified Data.ByteString.UTF8 as UTF8
import Data.Text (toLower, Text ())
import Network.Gopher (GopherLogLevel (..))

data Config
  = Config
  { serverName    :: ByteString
  , listenAddr    :: Maybe ByteString
  , serverPort    :: Integer
  , runUserName   :: Maybe String
  , rootDirectory :: FilePath
  , logConfig     :: LogConfig
  }

-- We only use string literals with 'maybePath', so we can just switch between
-- Key and Text, since both have an IsString instance for OverloadedStrings.
#if MIN_VERSION_aeson(2,0,0)
maybePath :: FromJSON a => [Key] -> Object -> Parser (Maybe a)
#else
maybePath :: FromJSON a => [Text] -> Object -> Parser (Maybe a)
#endif
maybePath []     _ = fail "got empty path"
maybePath [x]    v = v .:? x
maybePath (x:xs) v = v .:? x >>= fmap join . traverse (maybePath xs)

instance FromJSON Config where
  parseJSON (Object v) = Config
    <$> v .: "hostname"
    <*> maybePath [ "listen", "addr" ] v
    <*> parseListenPort v .!= 70
    <*> v .:? "user"
    <*> v .: "root"
    <*> v .:? "log" .!= defaultLogConfig
  parseJSON _ = mzero

-- Use '(<|>)' over the 'Maybe's in the parser rather
-- to only fallback on 'Nothing' and not on @empty@.
-- This way a parse error in listen → port doesn't get
-- promoted to just 'Nothing'.
parseListenPort :: Object -> Parser (Maybe Integer)
parseListenPort v = (<|>)
  <$> maybePath [ "listen", "port" ] v
  <*> (v .:? "port")

data LogConfig
  = LogConfig
  { logEnable   :: Bool
  , logHideIps  :: Bool
  , logHideTime :: Bool
  , logLevel    :: GopherLogLevel
  }

defaultLogConfig :: LogConfig
defaultLogConfig = LogConfig True True False GopherLogLevelInfo

instance FromJSON LogConfig where
  parseJSON (Object v) = LogConfig
    <$> v .:?  "enable"   .!= logEnable defaultLogConfig
    <*> v .:? "hide-ips"  .!= logHideIps defaultLogConfig
    <*> v .:? "hide-time" .!= logHideTime defaultLogConfig
    <*> v .:? "level"     .!= logLevel defaultLogConfig
  parseJSON _ = mzero

-- auxiliary instances for types that have no default instance
instance FromJSON GopherLogLevel where
  parseJSON (String s) =
    case toLower s of
      "info"  -> pure GopherLogLevelInfo
      "warn" -> pure GopherLogLevelWarn
      "error" -> pure GopherLogLevelError
      _ -> mzero
  parseJSON _ = mzero

instance FromJSON ByteString where
  parseJSON s@(String _) = UTF8.fromString <$> parseJSON s
  parseJSON _ = mzero