module Data.Aeson.Extended
  ( FromJSONKeyValue (..),
    ToJSONKeyValue (..),
    FromJSONWithContext (..),
    mapWithJSONPath,
    encodeToStrictText,
    (.=?),

    -- * Re-exports
    module Data.Aeson,
  )
where

-------------------------------------------------------------------------------

import Data.Aeson
import Data.Aeson.Text (encodeToTextBuilder)
import Data.Aeson.Types (JSONPathElement (..), Parser)
import Data.Functor.Const (getConst)
import Data.Text.Lazy (toStrict)
import Data.Text.Lazy.Builder (toLazyText)
import Hasura.Prelude

-------------------------------------------------------------------------------

class ToJSONKeyValue a where
  toJSONKeyValue :: a -> (Key, Value)

class FromJSONKeyValue a where
  parseJSONKeyValue :: (Key, Value) -> Parser a

instance ToJSONKeyValue Void where
  toJSONKeyValue = absurd

instance (ToJSONKeyValue a) => ToJSONKeyValue (Const a b) where
  toJSONKeyValue = toJSONKeyValue . getConst

-- | Similar to 'FromJSON', except the parser can also source data with which
-- to construct 'a' from a context 'ctx'.
--
-- This can be useful if the 'a' value contains some data that is not from the
-- current piece of JSON (the 'Value'). For example, some data from higher
-- up in the overall JSON graph, or from some system context.
class FromJSONWithContext ctx a | a -> ctx where
  parseJSONWithContext :: ctx -> Value -> Parser a

-------------------------------------------------------------------------------

-- | An optional key-value pair for encoding a JSON object.
--
-- @
-- object $ ["foo" .= 0] <> catMaybes [ "bar" .=? Nothing, "baz" .=? 2 ]
-- @
(.=?) :: (ToJSON v, KeyValue e kv) => Key -> Maybe v -> Maybe kv
(.=?) k = fmap (k .=)
{-# INLINE (.=?) #-}

infixr 8 .=?

-- | Map a 'Parser' over a list, keeping the JSONPath context
mapWithJSONPath :: (a -> Parser b) -> [a] -> Parser [b]
mapWithJSONPath parser xs =
  traverse (\(idx, item) -> parser item <?> Index idx) $ zip [0 ..] xs

encodeToStrictText :: (ToJSON a) => a -> Text
encodeToStrictText = toStrict . toLazyText . encodeToTextBuilder
