batchd-0.1.1.0: src/Batchd/Common/Types.hs
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE RecordWildCards #-}
-- | This module contains data type declarations that are used both by batchd daemon and client.
module Batchd.Common.Types
(
-- * Data types
TemplateSyntax (..),
JobType (..), OnFailAction (..),
ParamType (..), ParamDesc (..),
JobStatus (..),
JobParamInfo, JobInfo (..),
UserInfo (..), Permission (..),
DbDriver (..), DaemonMode (..),
AuthMode (..), AuthMethod (..),
LogTarget (..), LogConfig (..),
WebClientConfig (..),
ManagerConfig (..), DispatcherConfig (..),
MetricsConfig (..),
StorageConfig (..),
GlobalConfig (..),
MetricKind (..),
ByStatus (..),
-- * Exceptions
UploadException (..), DownloadException (..), ExecException (..),
-- * Utility functions
lookupParam, getParamType,
parseUpdate, parseUpdateMaybe,
parseUpdateStar, parseStatus,
jsonOptions, authMethods,
-- * Some default settings
defaultManagerPort, zeroUtcTime,
defaultAuthMode, defaultLogConfig,
defaultLogFormat, defaultStaticSalt,
getAuthStaticSalt
) where
import GHC.Generics
import Control.Exception
import Control.Monad
import Data.Generics hiding (Generic)
import Data.Int
import Data.Char
import Data.String
import Data.Time
import Data.List (isPrefixOf)
import Database.Persist
import Database.Persist.TH
import qualified Data.Map as M
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import Data.Aeson as Aeson
import Data.Aeson.Types
import Data.Default
import qualified Data.Text.Format.Heavy as F
import qualified Data.Text.Format.Heavy.Parse.Braces as PF
import Data.Dates
import qualified System.Posix.Syslog as Syslog
import System.Log.Heavy
import System.Exit
import Batchd.Core.Common.Types
-- | Default manager port - 9681.
defaultManagerPort :: Int
defaultManagerPort = 9681
-- | Type of job parameter
data ParamType =
String
| Integer
| InputFile
| OutputFile
deriving (Eq, Show, Data, Typeable, Generic)
-- | Description of job parameter
data ParamDesc = ParamDesc {
piName :: TL.Text -- ^ Parameter name (identifier)
, piType :: ParamType -- ^ Parameter type
, piTitle :: TL.Text -- ^ Parameter title (to show in client)
, piDefault :: T.Text -- ^ Default value of the parameter
}
deriving (Eq, Show, Data, Typeable, Generic)
data TemplateSyntax = Shell | Python
deriving (Eq, Show, Data, Typeable, Generic)
instance FromJSON TemplateSyntax where
parseJSON = genericParseJSON defaultOptions
instance ToJSON TemplateSyntax where
toJSON = genericToJSON defaultOptions
-- | Job type description
data JobType = JobType {
jtName :: String -- ^ Job type name (identifier)
, jtTitle :: Maybe String -- ^ Job type title (to show in client)
, jtSyntax :: Maybe TemplateSyntax
, jtTemplate :: [T.Text] -- ^ Template of command line to execute
, jtOnFail :: OnFailAction -- ^ What to do if execution failed
, jtHostName :: Maybe String -- ^ Name of host where to execute jobs.
-- Nothing means use default host from queue or localhost.
, jtMaxJobs :: Maybe Int -- ^ Maximum count of jobs of this type which
-- can be executed in parallel on one host.
-- Can be overruled in host settings.
, jtParams :: [ParamDesc] -- ^ Set of job parameters
}
deriving (Eq, Show, Data, Typeable, Generic)
stripPrefix :: String -> String -> String
stripPrefix prefix str =
if prefix `isPrefixOf` str
then drop (length prefix) str
else str
camelCaseToUnderscore :: String -> String
camelCaseToUnderscore = go False
where
go _ [] = []
go False (x:xs) = toLower x : go True xs
go True (x:xs)
| isUpper x = '_' : toLower x : go True xs
| otherwise = x : go True xs
jsonOptions :: String -> Data.Aeson.Types.Options
jsonOptions prefix = defaultOptions {fieldLabelModifier = camelCaseToUnderscore . stripPrefix prefix}
instance FromJSON ParamType where
parseJSON = genericParseJSON $ defaultOptions {fieldLabelModifier = camelCaseToUnderscore}
instance ToJSON ParamType where
toJSON = genericToJSON $ defaultOptions {fieldLabelModifier = camelCaseToUnderscore}
instance FromJSON ParamDesc where
parseJSON (Object v) = do
name <- v .: "name"
tp <- v .: "type"
title <- v .:? "title" .!= name
dflt <- v .:? "default" .!= ""
return $ ParamDesc name tp title dflt
parseJSON invalid = typeMismatch "parameter description" invalid
instance ToJSON ParamDesc where
toJSON = genericToJSON (jsonOptions "pi")
repackLines :: [T.Text] -> [T.Text]
repackLines texts = concatMap T.lines texts
instance FromJSON JobType where
parseJSON (Object v) = do
name <- v .: "name"
title <- v .:? "title"
tmp <- v .: "template"
syntax <- v .:? "syntax"
template <- case tmp of
Aeson.String str -> return [str]
_ -> parseJSON tmp
on_fail <- v .:? "on_fail" .!= Continue
host_name <- v .:? "host_name"
max_jobs <- v .:? "max_jobs"
params <- v .: "params"
return $ JobType name title syntax (repackLines template) on_fail host_name max_jobs params
instance ToJSON JobType where
toJSON = genericToJSON (jsonOptions "jt")
-- | What to do if job execution fails
data OnFailAction =
Continue -- ^ Continue to the next job
| RetryNow Int -- ^ Leave the job in the queue and retry execution.
-- Not more than @n@ times.
| RetryLater Int -- ^ Put the job to the end of queue, to be executed later.
-- Not more than @n@ times.
deriving (Eq, Show, Read, Data, Typeable, Generic)
instance ToJSON OnFailAction where
toJSON Continue = Aeson.String "continue"
toJSON (RetryNow n) = object ["retry" .= object ["when" .= ("now" :: T.Text), "count" .= n]]
toJSON (RetryLater n) = object ["retry" .= object ["when" .= ("later" :: T.Text), "count" .= n]]
instance FromJSON OnFailAction where
parseJSON (Aeson.String "continue") = return Continue
parseJSON (Aeson.String "retry") = return (RetryNow 1)
parseJSON (Object v) = do
r <- v .: "retry"
case r of
Object retry -> do
nowStr <- retry .:? "when" .!= "now"
now <- case nowStr of
"now" -> return True
"later" -> return False
_ -> fail $ "Unknown retry type specification: " ++ nowStr
count <- retry .:? "count" .!= 1
if now
then return $ RetryNow count
else return $ RetryLater count
_ -> typeMismatch "retry" r
parseJSON invalid = typeMismatch "on fail" invalid
-- | Job execution status
data JobStatus =
New -- ^ Just created, waiting for polling process to peek it.
| Waiting -- ^ Waiting for free worker.
| Processing -- ^ Being processed by the worker.
| Done -- ^ Successfully executed.
| Failed -- ^ Execution failed.
| Postponed -- ^ Execution postponed. This status can be set only manually.
deriving (Eq, Ord, Show, Read, Data, Typeable, Generic)
instance ToJSON JobStatus
instance FromJSON JobStatus
newtype ByStatus a = ByStatus (M.Map JobStatus a)
deriving (Eq, Show, Data, Typeable, Generic)
instance ToJSON a => ToJSON (ByStatus a) where
toJSON (ByStatus m) = object $ map go $ M.assocs m
where
go (st,x) = (T.pack $ map toLower $ show st) .= toJSON x
instance FromJSON a => FromJSON (ByStatus a) where
parseJSON (Object v) = do
pairs <- forM (H.toList v) go
return $ ByStatus $ M.fromList pairs
where
go (key,val) = do
Just st <- parseStatus Nothing (fail "invalid status") (Just key)
cnt <- parseJSON val
return (st, cnt)
deriving instance Generic WeekDay
instance ToJSON WeekDay
instance FromJSON WeekDay
-- | Job parameter values
type JobParamInfo = Variables
-- | Job information. This data type
-- unites information from @Job@ and connected
-- @JobParam@.
data JobInfo = JobInfo {
jiId :: Int64,
jiQueue :: String,
jiType :: String,
jiSeq :: Int,
jiUserName :: String,
jiCreateTime :: UTCTime,
jiStartTime :: Maybe UTCTime,
jiStatus :: JobStatus,
jiTryCount :: Int,
jiHostName :: Maybe String,
jiNotes :: Maybe String,
jiResultTime :: Maybe UTCTime,
jiExitCode :: Maybe ExitCode,
jiStdout :: Maybe T.Text,
jiStderr :: Maybe T.Text,
jiParams :: JobParamInfo
}
deriving (Generic, Show)
instance ToJSON JobInfo where
toJSON = genericToJSON (jsonOptions "ji")
zeroUtcTime :: UTCTime
zeroUtcTime = UTCTime (ModifiedJulianDay 0) 0
instance FromJSON JobInfo where
parseJSON (Object v) =
JobInfo
<$> v .:? "id" .!= 0
<*> v .:? "queue" .!= ""
<*> v .: "type"
<*> v .:? "seq" .!= 0
<*> v .:? "user_name" .!= "<unknown>"
<*> v .:? "create_time" .!= zeroUtcTime
<*> v .:? "start_time"
<*> v .:? "status" .!= New
<*> v .:? "try_count" .!= 0
<*> v .:? "host_name"
<*> v .:? "notes"
<*> v .:? "result_time"
<*> v .:? "exit_code"
<*> v .:? "stdout"
<*> v .:? "stderr"
<*> v .:? "params" .!= M.empty
parseJSON invalid = typeMismatch "job" invalid
-- | User name and password
data UserInfo = UserInfo {
uiName :: String,
uiPassword :: String
} deriving (Generic, Show)
instance ToJSON UserInfo where
toJSON = genericToJSON (jsonOptions "ui")
instance FromJSON UserInfo where
parseJSON = genericParseJSON (jsonOptions "ui")
-- | Lookup for parameter description by name.
lookupParam :: TL.Text -- ^ Parameter name
-> [ParamDesc] -- ^ List of parameter descriptions
-> Maybe ParamDesc
lookupParam _ [] = Nothing
lookupParam name (p:ps)
| piName p == name = Just p
| otherwise = lookupParam name ps
-- | Lookup for parameter type by name
getParamType :: JobType -> TL.Text -> Maybe ParamType
getParamType jt name = piType `fmap` lookupParam name (jtParams jt)
-- | Supported database drivers
data DbDriver = Sqlite | PostgreSql
deriving (Eq, Show, Data, Typeable, Generic)
instance ToJSON DbDriver
instance FromJSON DbDriver
-- | Daemon execution mode
data DaemonMode =
Manager -- ^ Manager thread only
| Dispatcher -- ^ Dispatcher thread only
| Both -- ^ Both manager and dispatcher threads
deriving (Data, Typeable, Show, Read, Eq, Ord, Generic)
instance ToJSON DaemonMode
instance FromJSON DaemonMode
-- | Authentication methods
data AuthMode =
AuthDisabled -- ^ Authentication is disabled, all users to be authorized as superusers
| AuthConfig {
authBasicEnabled :: Bool -- ^ Is HTTP Basic authentication enabled
, authHeaderEnabled :: Bool -- ^ Is use of @X-Auth-User:@ HTTP header enabled
, authStaticSalt :: String -- ^ Static salt value
}
deriving (Data, Typeable, Show, Eq, Generic)
getAuthStaticSalt :: AuthMode -> String
getAuthStaticSalt AuthDisabled = defaultStaticSalt
getAuthStaticSalt auth = authStaticSalt auth
instance ToJSON AuthMode where
toJSON AuthDisabled = Aeson.String "disable"
toJSON (AuthConfig {..}) = object ["basic" .= authBasicEnabled, "header" .= authHeaderEnabled, "staticSalt" .= authStaticSalt]
instance FromJSON AuthMode where
parseJSON (Aeson.String "disable") = return AuthDisabled
parseJSON (Object v) =
AuthConfig
<$> v .:? "basic" .!= True
<*> v .:? "header" .!= False
<*> v .:? "static_salt" .!= defaultStaticSalt
-- | Default autentication mode: only HTTP basic auth is enabled.
defaultAuthMode :: AuthMode
defaultAuthMode = AuthConfig True False defaultStaticSalt
-- | Supported authentication methods
data AuthMethod = BasicAuth | HeaderAuth
deriving (Data, Typeable, Show, Read, Eq, Generic)
instance ToJSON AuthMethod where
toJSON BasicAuth = Aeson.String "basic"
toJSON HeaderAuth = Aeson.String "header"
instance FromJSON AuthMethod where
parseJSON (Aeson.String "basic") = return BasicAuth
parseJSON (Aeson.String "header") = return HeaderAuth
parseJSON x = typeMismatch "auth method" x
-- | Get enabled authentication methods from specified mode.
authMethods :: AuthMode -> [AuthMethod]
authMethods AuthDisabled = []
authMethods (AuthConfig {..}) =
(if authBasicEnabled then [BasicAuth] else []) ++
(if authHeaderEnabled then [HeaderAuth] else [])
-- | Supported logging targets
data LogTarget =
LogSyslog
| LogStdout
| LogStderr
| LogFile FilePath
deriving (Eq, Show, Data, Typeable, Generic)
instance ToJSON LogTarget where
toJSON LogSyslog = Aeson.String "syslog"
toJSON LogStdout = Aeson.String "stdout"
toJSON LogStderr = Aeson.String "stderr"
toJSON (LogFile path) = toJSON path
instance FromJSON LogTarget where
parseJSON (Aeson.String "syslog") = return LogSyslog
parseJSON (Aeson.String "stdout") = return LogStdout
parseJSON (Aeson.String "stderr") = return LogStderr
parseJSON (Aeson.String path) = return $ LogFile $ T.unpack path
parseJSON invalid = typeMismatch "log target" invalid
-- | Logging configuration
data LogConfig = LogConfig {
lcTarget :: LogTarget -- ^ Logging target. Default is syslog.
, lcFormat :: F.Format -- ^ Log message format.
, lcLevel :: Level -- ^ General filter
, lcFilter :: [(String, Level)] -- ^ Filter by source prefixes
}
deriving (Eq, Show, Typeable, Generic)
-- | Default logging config
defaultLogConfig :: LogConfig
defaultLogConfig = LogConfig LogSyslog defaultLogFormat info_level []
-- | Default log messages format:
-- @"{time} [{level:~l}] {source} ({fullcontext}): {message}\n"@
defaultLogFormat :: F.Format
defaultLogFormat = "{time} [{level:~l}] {source} ({fullcontext}): {message}\n"
instance ToJSON LogConfig where
toJSON = genericToJSON (jsonOptions "lc")
instance ToJSON F.Format where
toJSON fmt = toJSON (show fmt)
instance FromJSON LogConfig where
parseJSON (Object v) = LogConfig
<$> v .:? "target" .!= LogSyslog
<*> parseLogFormat (v .:? "format")
<*> v .:? "level" .!= info_level
<*> parseFilter (v .:? "filter" .!= M.empty)
where
parseFilter :: Parser (M.Map String Level) -> Parser [(String, Level)]
parseFilter = fmap M.assocs
parseLogFormat :: Parser (Maybe TL.Text) -> Parser F.Format
parseLogFormat p = do
mbString <- p
case mbString of
Nothing -> return defaultLogFormat
Just str -> case PF.parseFormat str of
Left err -> fail $ show err
Right fmt -> return fmt
-- | Global daemon configuration
data GlobalConfig = GlobalConfig {
dbcDaemonMode :: DaemonMode -- ^ Daemon execution mode
, dbcDriver :: DbDriver -- ^ Type of DB backend
, dbcConnectionString :: T.Text -- ^ DB connection string
, dbcConnectionPoolSize :: Int -- ^ Database connections pool size
, dbcLogging :: LogConfig -- ^ Logging configuration
, dbcManager :: ManagerConfig
, dbcDispatcher :: DispatcherConfig
, dbcDefTemplateSyntax :: TemplateSyntax
, dbcDefScriptsDirectory :: FilePath
, dbcMetrics :: MetricsConfig
, dbcStorage :: StorageConfig
, dbcVariables :: Variables
}
deriving (Eq, Show, Typeable, Generic)
instance Default GlobalConfig where
def = GlobalConfig {
dbcDaemonMode = Both
, dbcDriver = Sqlite
, dbcConnectionString = ":memory:"
, dbcConnectionPoolSize = 10
, dbcLogging = defaultLogConfig
, dbcManager = def
, dbcDispatcher = def
, dbcDefTemplateSyntax = Shell
, dbcDefScriptsDirectory = "./scripts"
, dbcMetrics = def
, dbcStorage = def
, dbcVariables = def
}
data MetricsConfig = MetricsConfig {
mcGcMetrics :: Bool -- ^ Whether to enable GC metrics (default true)
, mcHttpMetrics :: Bool -- ^ Whether to enable HTTP metrics for manager REST API (default true)
, mcStorePrefixOnly :: Maybe T.Text -- ^ Only store metrics, names of which starts with specified prefix.
, mcDumpTimeout :: Int -- ^ How often to dump metrics data to DB, in seconds (default - each 10 seconds)
}
deriving (Eq, Show, Typeable, Generic)
instance Default MetricsConfig where
def = MetricsConfig {
mcGcMetrics = True
, mcHttpMetrics = True
, mcStorePrefixOnly = Nothing
, mcDumpTimeout = 10
}
data WebClientConfig = WebClientConfig {
wcAllowedOrigin :: Maybe String -- ^ Allowed Origin for CORS
, wcPath :: String -- ^ Path to web client HTML\/JS\/CSS files
}
deriving (Eq, Show, Typeable, Generic)
instance Default WebClientConfig where
def = WebClientConfig Nothing ""
data ManagerConfig = ManagerConfig {
mcPort :: Int -- ^ Network port for manager to listen
, mcAuth :: AuthMode -- ^ Authentication configuration
, mcWebClient :: Maybe WebClientConfig
}
deriving (Eq, Show, Typeable, Generic)
instance Default ManagerConfig where
def = ManagerConfig {
mcPort = defaultManagerPort
, mcAuth = defaultAuthMode
, mcWebClient = Nothing
}
data DispatcherConfig = DispatcherConfig {
dcWorkers :: Int
, dcPollTimeout :: Int
}
deriving (Eq, Show, Typeable, Generic)
instance Default DispatcherConfig where
def = DispatcherConfig {
dcWorkers = 1
, dcPollTimeout = 10
}
data StorageConfig = StorageConfig {
scDoneJobs :: Int -- ^ How long to store executed jobs, in days.
, scMetricRecords :: Int -- ^ How long to store metric records, in days.
}
deriving (Eq, Show, Typeable, Generic)
instance Default StorageConfig where
def = StorageConfig {
scDoneJobs = 2
, scMetricRecords = 3
}
-- | Default static salt value.
defaultStaticSalt :: String
defaultStaticSalt = "1234567890abcdef"
instance ToJSON MetricsConfig where
toJSON = genericToJSON (jsonOptions "mc")
instance FromJSON MetricsConfig where
parseJSON (Object v) =
MetricsConfig
<$> v .:? "gc_metrics" .!= mcGcMetrics def
<*> v .:? "http_metrics" .!= mcHttpMetrics def
<*> v .:? "store_prefix_only" .!= mcStorePrefixOnly def
<*> v .:? "dump_timeout" .!= mcDumpTimeout def
parseJSON invalid = typeMismatch "metrics configuration" invalid
instance ToJSON WebClientConfig where
toJSON = genericToJSON (jsonOptions "wc")
instance FromJSON WebClientConfig where
parseJSON (Object v) =
WebClientConfig
<$> v .:? "allowed_origin"
<*> v .: "path"
parseJSON invalid = typeMismatch "web client configuration" invalid
instance ToJSON ManagerConfig where
toJSON = genericToJSON (jsonOptions "mc")
instance FromJSON ManagerConfig where
parseJSON (Object v) =
ManagerConfig
<$> v .:? "port" .!= defaultManagerPort
<*> v .:? "auth" .!= defaultAuthMode
<*> v .:? "web_client"
parseJSON invalid = typeMismatch "manager configuration" invalid
instance ToJSON DispatcherConfig where
toJSON = genericToJSON (jsonOptions "dc")
instance FromJSON DispatcherConfig where
parseJSON (Object v) =
DispatcherConfig
<$> v .:? "workers" .!= dcWorkers def
<*> v .:? "poll_timeout" .!= dcPollTimeout def
parseJSON invalid = typeMismatch "dispatcher configuration" invalid
instance ToJSON StorageConfig where
toJSON = genericToJSON (jsonOptions "sc")
instance FromJSON StorageConfig where
parseJSON (Object v) =
StorageConfig
<$> v .:? "done_jobs" .!= scDoneJobs def
<*> v .:? "metric_records" .!= scMetricRecords def
parseJSON invalid = typeMismatch "storage configuration" invalid
instance ToJSON GlobalConfig where
toJSON = genericToJSON (jsonOptions "dbc")
instance FromJSON GlobalConfig where
parseJSON (Object v) =
GlobalConfig
<$> v .:? "daemon" .!= dbcDaemonMode def
<*> v .:? "driver" .!= dbcDriver def
<*> v .:? "connection_string" .!= dbcConnectionString def
<*> v .:? "connection_pool_size" .!= dbcConnectionPoolSize def
<*> v .:? "logging" .!= defaultLogConfig
<*> v .:? "manager" .!= def
<*> v .:? "dispatcher" .!= def
<*> v .:? "default_template_syntax" .!= Shell
<*> v .:? "default_scripts_directory" .!= "./scripts"
<*> v .:? "metrics" .!= def
<*> v .:? "storage" .!= def
<*> v .:? "variables" .!= def
parseJSON invalid = typeMismatch "global configuration" invalid
data MetricKind =
Counter
| Gauge
| Label
| Distribution
deriving (Eq, Ord, Show, Read)
deriving instance Data Syslog.Priority
deriving instance Typeable Syslog.Priority
deriving instance Data Level
deriving instance Typeable Level
parseStatus :: (Eq s, IsString s, Monad m) => Maybe JobStatus -> m (Maybe JobStatus) -> Maybe s -> m (Maybe JobStatus)
parseStatus dflt _ Nothing = return dflt
parseStatus _ _ (Just "all") = return Nothing
parseStatus _ _ (Just "new") = return $ Just New
parseStatus _ _ (Just "waiting") = return $ Just Waiting
parseStatus _ _ (Just "processing") = return $ Just Processing
parseStatus _ _ (Just "done") = return $ Just Done
parseStatus _ _ (Just "failed") = return $ Just Failed
parseStatus _ _ (Just "postponed") = return $ Just Postponed
parseStatus _ handle (Just _) = handle
instance ToJSON ExitCode where
toJSON ExitSuccess = Number (fromIntegral 0)
toJSON (ExitFailure n) = Number (fromIntegral n)
instance FromJSON ExitCode where
parseJSON (Number 0) = return ExitSuccess
parseJSON (Number n) = return $ ExitFailure $ round n
parseJSON x = typeMismatch "exit code" x
data ExecException = ExecException SomeException
deriving (Typeable)
instance Exception ExecException
instance Show ExecException where
show (ExecException e) = "Exception during command execution: " ++ show e
data UploadException = UploadException FilePath SomeException
deriving (Typeable)
instance Exception UploadException
instance Show UploadException where
show (UploadException path e) = "Exception uploading file `" ++ path ++ "': " ++ show e
data DownloadException = DownloadException FilePath SomeException
deriving (Typeable)
instance Exception DownloadException
instance Show DownloadException where
show (DownloadException path e) = "Exception downloading file `" ++ path ++ "': " ++ show e
derivePersistField "WeekDay"
derivePersistField "JobStatus"
derivePersistField "ExitCode"
derivePersistField "DaemonMode"
derivePersistField "MetricKind"
-- | Supported user permissions.
data Permission =
SuperUser
| CreateJobs
| ViewJobs
| ManageJobs
| ViewQueues
| ManageQueues
| ViewSchedules
| ManageSchedules
deriving (Eq, Show, Read, Data, Typeable, Generic)
derivePersistField "Permission"
instance FromJSON Permission where
parseJSON = genericParseJSON $ defaultOptions {fieldLabelModifier = camelCaseToUnderscore}
instance ToJSON Permission where
toJSON = genericToJSON $ defaultOptions {fieldLabelModifier = camelCaseToUnderscore}
parseUpdate :: (PersistField t, FromJSON t) => EntityField v t -> T.Text -> Value -> Parser (Maybe (Update v))
parseUpdate field label (Object v) = do
mbValue <- v .:? label
let upd = case mbValue of
Nothing -> Nothing
Just value -> Just (field =. value)
return upd
parseUpdateMaybe :: (PersistField t, FromJSON t) => EntityField v (Maybe t) -> T.Text -> Value -> Parser (Maybe (Update v))
parseUpdateMaybe field label (Object v) = do
if label `H.member` v
then do
mbValue <- v .:? label
return $ Just $ field =. mbValue
else return Nothing
parseUpdateStar :: (PersistField t, FromJSON t, IsString t, Eq t)
=> EntityField v (Maybe t) -> T.Text -> Value -> Parser (Maybe (Update v))
parseUpdateStar field label (Object v) = do
mbValue <- v .:? label
let upd = case mbValue of
Nothing -> Nothing
Just "*" -> Just (field =. Nothing)
Just value -> Just (field =. Just value)
return upd