{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnicodeSyntax #-}
module Configuration.Utils.Validation
( ConfigValidation
, validateHttpOrHttpsUrl
, validateHttpUrl
, validateHttpsUrl
, validateUri
, validateAbsoluteUri
, validateAbsoluteUriFragment
, validateIPv4
, validateIPv6
, validatePort
, validateNonEmpty
, validateLength
, validateMinLength
, validateMaxLength
, validateMinMaxLength
, validateFilePath
, validateFile
, validateFileReadable
, validateFileWritable
, validateExecutable
, validateDirectory
, validateConfigFile
, validateFalse
, validateTrue
, validateBool
, validateNonNegative
, validatePositive
, validateNonPositive
, validateNegative
, validateNonNull
, validateLess
, validateLessEq
, validateGreater
, validateGreaterEq
, validateRange
) where
import Configuration.Utils.Internal
import Control.Monad.Error.Class
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Writer.Class
import qualified Data.Foldable as F
import Data.Monoid.Unicode
import qualified Data.Text as T
import Network.URI
import Prelude.Unicode
import System.Directory
type ConfigValidation a f = ∀ m . (MonadIO m, Functor m, Applicative m, MonadError T.Text m, MonadWriter (f T.Text) m) ⇒ a → m ()
validateHttpOrHttpsUrl
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateHttpOrHttpsUrl configName uri =
case parseURI uri of
Nothing → throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
Just u → unless (uriScheme u ≡ "http:" || uriScheme u ≡ "https:") ∘ throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not an HTTP or HTTPS URL"
validateHttpUrl
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateHttpUrl configName uri =
case parseURI uri of
Nothing → throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
Just u → unless (uriScheme u ≡ "http:") ∘ throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not an HTTP URL"
validateHttpsUrl
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateHttpsUrl configName uri =
case parseURI uri of
Nothing → throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
Just u → unless (uriScheme u ≡ "https:") ∘ throwError $
"the value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not an HTTPS URL"
validateUri
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateUri configName uri =
unless (isURIReference uri) ∘ throwError $
"The value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
validateAbsoluteUri
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateAbsoluteUri configName uri =
unless (isAbsoluteURI uri) ∘ throwError $
"The value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
validateAbsoluteUriFragment
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateAbsoluteUriFragment configName uri =
unless (isURI uri) ∘ throwError $
"The value " ⊕ T.pack uri ⊕ " for " ⊕ configName ⊕ " is not a valid URI"
validateIPv4
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateIPv4 configName ipv4 =
unless (isIPv4address ipv4) ∘ throwError $
"The value " ⊕ T.pack ipv4 ⊕ " for " ⊕ configName ⊕ " is not a valid IPv4 address"
validateIPv6
∷ MonadError T.Text m
⇒ T.Text
→ String
→ m ()
validateIPv6 configName ipv6 =
unless (isIPv6address ipv6) ∘ throwError $
"The value " ⊕ T.pack ipv6 ⊕ " for " ⊕ configName ⊕ " is not a valid IPv6 address"
validatePort
∷ (MonadError T.Text m, Integral n, Show n)
⇒ T.Text
→ n
→ m ()
validatePort configName p =
unless (p > 1 && p < 65535) ∘ throwError $
"port value " ⊕ T.pack (show p) ⊕ " for " ⊕ configName ⊕ " is not valid port number"
validateNonEmpty
∷ (MonadError T.Text m, Eq a, Monoid a)
⇒ T.Text
→ a
→ m ()
validateNonEmpty configName x =
when (x ≡ mempty) ∘ throwError $
"value for " ⊕ configName ⊕ " must not be empty"
validateLength
∷ (MonadError T.Text m, F.Foldable f)
⇒ T.Text
→ Int
→ f a
→ m ()
validateLength configName len x =
unless (length (F.toList x) ≡ len) ∘ throwError $
"value for " ⊕ configName ⊕ " must be of length exactly " ⊕ sshow len
validateMaxLength
∷ (MonadError T.Text m, F.Foldable f)
⇒ T.Text
→ Int
→ f a
→ m ()
validateMaxLength configName u x =
unless (length (F.toList x) ≤ u) ∘ throwError $
"value for " ⊕ configName ⊕ " must be of length at most " ⊕ sshow u
validateMinLength
∷ (MonadError T.Text m, F.Foldable f)
⇒ T.Text
→ Int
→ f a
→ m ()
validateMinLength configName l x =
unless (length (F.toList x) ≥ l) ∘ throwError $
"value for " ⊕ configName ⊕ " must be of length at least " ⊕ sshow l
validateMinMaxLength
∷ (MonadError T.Text m, F.Foldable f)
⇒ T.Text
→ Int
→ Int
→ f a
→ m ()
validateMinMaxLength configName l u x =
unless (len ≥ l && len ≤ u) ∘ throwError $
"the length of the value for " ⊕ configName ⊕
" must be at least " ⊕ sshow l ⊕ " and at most " ⊕ sshow u
where
len = length $ F.toList x
validateFilePath
∷ MonadError T.Text m
⇒ T.Text
→ FilePath
→ m ()
validateFilePath configName file =
when (null file) ∘ throwError $
"file path for " ⊕ configName ⊕ " must not be empty"
validateFile
∷ (MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateFile configName file = do
exists ← liftIO $ doesFileExist file
unless exists ∘ throwError $
"the file " ⊕ T.pack file ⊕ " for " ⊕ configName ⊕ " does not exist"
validateFileReadable
∷ (MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateFileReadable configName file = do
validateFile configName file
liftIO (getPermissions file) >>= \x → unless (readable x) ∘ throwError $
"the file " ⊕ T.pack file ⊕ " for " ⊕ configName ⊕ " is not readable"
validateFileWritable
∷ (MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateFileWritable configName file = do
validateFile configName file
liftIO (getPermissions file) >>= \x → unless (writable x) ∘ throwError $
"the file " ⊕ T.pack file ⊕ " for " ⊕ configName ⊕ " is not writable"
validateFileExecutable
∷ (MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateFileExecutable configName file = do
validateFile configName file
liftIO (getPermissions file) >>= \x → unless (executable x) ∘ throwError $
"the file " ⊕ T.pack file ⊕ " for " ⊕ configName ⊕ " is not excutable"
validateDirectory
∷ (MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateDirectory configName dir = do
exists ← liftIO $ doesDirectoryExist dir
unless exists ∘ throwError $
"the directory " ⊕ T.pack dir ⊕ " for " ⊕ configName ⊕ " does not exist"
validateExecutable
∷ (Functor m, MonadError T.Text m, MonadIO m)
⇒ T.Text
→ FilePath
→ m ()
validateExecutable configName file = do
execFile ← (file <$ validateFile configName file) `catchError` \_ ->
liftIO (findExecutable file) >>= \case
Nothing → throwError $
"the executable " ⊕ T.pack file ⊕ " for " ⊕ configName ⊕ " could not be found in the system;"
⊕ " you may check your SearchPath and PATH variable settings"
Just f → return f
validateFileExecutable configName execFile
validateConfigFile
∷ (MonadIO m, MonadError T.Text m)
⇒ String
→ m ()
validateConfigFile filepath =
validateFileReadable "config-file" filepath
#ifdef REMOTE_CONFIGS
`catchError` \_ ->
validateHttpOrHttpsUrl "config-file" filepath
#endif
validateFalse
∷ (MonadError T.Text m)
⇒ T.Text
→ Bool
→ m ()
validateFalse configName = validateBool configName False
validateTrue
∷ (MonadError T.Text m)
⇒ T.Text
→ Bool
→ m ()
validateTrue configName = validateBool configName True
validateBool
∷ (MonadError T.Text m)
⇒ T.Text
→ Bool
→ Bool
→ m ()
validateBool configName expected x = unless (x ≡ expected) ∘ throwError $
"expected " ⊕ configName ⊕ " to be " ⊕ sshow expected ⊕ ", but was " ⊕ sshow x
validateNonNegative
∷ (MonadError T.Text m, Ord a, Num a)
⇒ T.Text
→ a
→ m ()
validateNonNegative configName x =
when (x < 0) ∘ throwError $
"value for " ⊕ configName ⊕ " must not be negative"
validatePositive
∷ (MonadError T.Text m, Ord a, Num a)
⇒ T.Text
→ a
→ m ()
validatePositive configName x =
when (x ≤ 0) ∘ throwError $
"value for " ⊕ configName ⊕ " must be positive"
validateNonPositive
∷ (MonadError T.Text m, Ord a, Num a)
⇒ T.Text
→ a
→ m ()
validateNonPositive configName x =
when (x > 0) ∘ throwError $
"value for " ⊕ configName ⊕ " must not be positive"
validateNegative
∷ (MonadError T.Text m, Ord a, Num a)
⇒ T.Text
→ a
→ m ()
validateNegative configName x =
when (x ≥ 0) ∘ throwError $
"value for " ⊕ configName ⊕ " must be negative"
validateNonNull
∷ (MonadError T.Text m, Eq a, Num a)
⇒ T.Text
→ a
→ m ()
validateNonNull configName x = when (x ≡ 0) ∘ throwError $
"value for " ⊕ configName ⊕ " must not be zero"
validateLess
∷ (MonadError T.Text m, Ord a, Show a)
⇒ T.Text
→ a
→ a
→ m ()
validateLess configName upper x = unless (x < upper) ∘ throwError $
"value for " ⊕ configName ⊕ " must be strictly less than " ⊕ sshow upper ⊕ ", but was " ⊕ sshow x
validateLessEq
∷ (MonadError T.Text m, Ord a, Show a)
⇒ T.Text
→ a
→ a
→ m ()
validateLessEq configName upper x = unless (x ≤ upper) ∘ throwError $
"value for " ⊕ configName ⊕ " must be less or equal than " ⊕ sshow upper ⊕ ", but was " ⊕ sshow x
validateGreater
∷ (MonadError T.Text m, Ord a, Show a)
⇒ T.Text
→ a
→ a
→ m ()
validateGreater configName lower x = unless (x > lower) ∘ throwError $
"value for " ⊕ configName ⊕ " must be strictly greater than " ⊕ sshow lower ⊕ ", but was " ⊕ sshow x
validateGreaterEq
∷ (MonadError T.Text m, Ord a, Show a)
⇒ T.Text
→ a
→ a
→ m ()
validateGreaterEq configName lower x = unless (x ≥ lower) ∘ throwError $
"value for " ⊕ configName ⊕ " must be greater or equal than " ⊕ sshow lower ⊕ ", but was " ⊕ sshow x
validateRange
∷ (MonadError T.Text m, Ord a, Show a)
⇒ T.Text
→ (a, a)
→ a
→ m ()
validateRange configName (lower,upper) x = unless (x ≥ lower ∧ x ≤ upper) ∘ throwError $
"value for " ⊕ configName ⊕ " must be within the range of (" ⊕ sshow lower ⊕ ", " ⊕ sshow upper ⊕ "), but was " ⊕ sshow x