graphql-engine/server/src-lib/Hasura/Prelude.hs
Rakesh Emmadi dc31b835e1
allow reusing Postgres scalars in custom types & actions (close #4125) (#4333)
* allow re-using Postgres scalars in custom types, close #4125

* add pytest tests

* update CHANGELOG.md

* add a doc pointer for reusable postgres scalars

* document the code, improve the CHANGELOG entry

As suggested by @lexi-lambda

* a bit more source code documentation, use WriterT to collect reused scalars

* Apply suggestions from code review

Co-Authored-By: Marion Schleifer <marion@hasura.io>

* improve doc for Postgres scalars in custom graphql types

* Add some more references to Note; fix Haddock syntax

Also a few very minor tweaks:
  * Use HashSet instead of [] more pervasively
  * Export execWriterT from Hasura.Prelude
  * Use pattern guards in multi-way if
  * Tweak a few names/comments

* Pull buildActions out of buildAndCollectInfo, use buildInfoMap

* Tweak wording in documentation

* incorporate changes in console code

* account Postgres scalars for action input arguments

-> Avoid unnecessary 'throw500' in making action schema

* Review changes

Co-authored-by: Marion Schleifer <marion@hasura.io>
Co-authored-by: Alexis King <lexi.lambda@gmail.com>
Co-authored-by: Vamshi Surabhi <0x777@users.noreply.github.com>
Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>
2020-04-15 17:33:13 +05:30

149 lines
6.1 KiB
Haskell

{-# OPTIONS_GHC -fno-warn-orphans #-}
module Hasura.Prelude
( module M
, alphaNumerics
, onNothing
, onJust
, onLeft
, choice
, afold
, bsToTxt
, txtToBs
, spanMaybeM
, findWithIndex
, mapFromL
-- * Measuring and working with moments and durations
, withElapsedTime
, startTimer
, module Data.Time.Clock.Units
) where
import Control.Applicative as M (Alternative (..))
import Control.Arrow as M (first, second, (&&&), (***), (<<<), (>>>))
import Control.DeepSeq as M (NFData, deepseq, force)
import Control.Monad as M (void, when)
import Control.Monad.Base as M
import Control.Monad.Except as M
import Control.Monad.Fail as M (MonadFail)
import Control.Monad.Identity as M
import Control.Monad.Reader as M
import Control.Monad.State.Strict as M
import Control.Monad.Writer.Strict as M (MonadWriter (..), WriterT (..),
execWriterT, runWriterT)
import Data.Align as M (Align (align, alignWith))
import Data.Align.Key as M (AlignWithKey (..))
import Data.Bool as M (bool)
import Data.Data as M (Data (..))
import Data.Either as M (lefts, partitionEithers, rights)
import Data.Foldable as M (asum, foldrM, for_, toList, traverse_)
import Data.Function as M (on, (&))
import Data.Functor as M (($>), (<&>))
import Data.Hashable as M (Hashable)
import Data.HashMap.Strict as M (HashMap)
import Data.HashSet as M (HashSet)
import Data.List as M (find, findIndex, foldl', group,
intercalate, intersect, lookup, sort,
sortBy, sortOn, union, unionBy, (\\))
import Data.List.NonEmpty as M (NonEmpty (..))
import Data.Maybe as M (catMaybes, fromMaybe, isJust, isNothing,
listToMaybe, mapMaybe, maybeToList)
import Data.Monoid as M (getAlt)
import Data.Ord as M (comparing)
import Data.Semigroup as M (Semigroup (..))
import Data.Sequence as M (Seq)
import Data.String as M (IsString)
import Data.Text as M (Text)
import Data.These as M (These (..), fromThese, mergeThese,
mergeTheseWith, these)
import Data.Time.Clock.Units
import Data.Traversable as M (for)
import Data.Word as M (Word64)
import GHC.Generics as M (Generic)
import Prelude as M hiding (fail, init, lookup)
import Test.QuickCheck.Arbitrary.Generic as M
import Text.Read as M (readEither, readMaybe)
import qualified Data.ByteString as B
import qualified Data.HashMap.Strict as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TE
import qualified GHC.Clock as Clock
import qualified Test.QuickCheck as QC
alphaNumerics :: String
alphaNumerics = ['a'..'z'] ++ ['A'..'Z'] ++ "0123456789 "
instance Arbitrary Text where
arbitrary = T.pack <$> QC.listOf (QC.elements alphaNumerics)
onNothing :: (Monad m) => Maybe a -> m a -> m a
onNothing m act = maybe act return m
onJust :: (Monad m) => Maybe a -> (a -> m ()) -> m ()
onJust m action = maybe (return ()) action m
onLeft :: (Monad m) => Either e a -> (e -> m a) -> m a
onLeft e f = either f return e
choice :: (Alternative f) => [f a] -> f a
choice = asum
afold :: (Foldable t, Alternative f) => t a -> f a
afold = getAlt . foldMap pure
bsToTxt :: B.ByteString -> Text
bsToTxt = TE.decodeUtf8With TE.lenientDecode
txtToBs :: Text -> B.ByteString
txtToBs = TE.encodeUtf8
-- Like 'span', but monadic and with a function that produces 'Maybe' instead of 'Bool'
spanMaybeM
:: (Foldable f, Monad m)
=> (a -> m (Maybe b)) -> f a -> m ([b], [a])
spanMaybeM f = go . toList
where
go [] = pure ([], [])
go l@(x:xs) = f x >>= \case
Just y -> first (y:) <$> go xs
Nothing -> pure ([], l)
findWithIndex :: (a -> Bool) -> [a] -> Maybe (a, Int)
findWithIndex p l = do
v <- find p l
i <- findIndex p l
pure (v, i)
-- TODO: Move to Data.HashMap.Strict.Extended; rename to fromListWith?
mapFromL :: (Eq k, Hashable k) => (a -> k) -> [a] -> Map.HashMap k a
mapFromL f = Map.fromList . map (\v -> (f v, v))
-- | Time an IO action, returning the time with microsecond precision. The
-- result of the input action will be evaluated to WHNF.
--
-- The result 'DiffTime' is guarenteed to be >= 0.
withElapsedTime :: MonadIO m=> m a -> m (DiffTime, a)
withElapsedTime ma = do
bef <- liftIO Clock.getMonotonicTimeNSec
!a <- ma
aft <- liftIO Clock.getMonotonicTimeNSec
let !dur = nanoseconds $ fromIntegral (aft - bef)
return (dur, a)
-- | Start timing and return an action to return the elapsed time since 'startTimer' was called.
--
-- @
-- timer <- startTimer
-- someStuffToTime
-- elapsed <- timer
-- moreStuff
-- elapsedBoth <- timer
-- @
startTimer :: (MonadIO m, MonadIO n)=> m (n DiffTime)
startTimer = do
!bef <- liftIO Clock.getMonotonicTimeNSec
return $ do
aft <- liftIO Clock.getMonotonicTimeNSec
return $ nanoseconds $ fromIntegral (aft - bef)