content: {-# LANGUAGE OverloadedStrings #-}
  
  module Eventful.Store.DynamoDB.DynamoJSON
    ( valueToAttributeValue
    , attributeValueToValue
    ) where
  
  import Control.Lens
  import Data.Aeson
  import qualified Data.HashMap.Strict as HM
  import qualified Data.Text as T
  import qualified Data.Vector as V
  import Network.AWS.DynamoDB hiding (Null)
  
  -- | Note, currently we can't properly disambiguate between an empty Object or
  -- an empty Array because of amazonka's 'AttributeValue' lenses. We use a very
  -- nasty kludge where we replace empty Objects with a string with a very
  -- special value. Follow this issue to see when we can remove the kludge:
  -- https://github.com/brendanhay/amazonka/issues/282.
  valueToAttributeValue :: Value -> AttributeValue
  valueToAttributeValue (String v) = attributeValue & avS ?~ v
  valueToAttributeValue (Number v) = attributeValue & avN ?~ T.pack (show v)
  valueToAttributeValue (Bool v) = attributeValue & avBOOL ?~ v
  valueToAttributeValue (Array vs) = attributeValue & avL .~ fmap valueToAttributeValue (V.toList vs)
  valueToAttributeValue (Object v)
    | HM.null v = attributeValue & avS ?~ "__eventful-dynamodb-empty-object__"
    | otherwise = attributeValue & avM .~ fmap valueToAttributeValue v
  valueToAttributeValue Null = attributeValue & avNULL ?~ True
  
  attributeValueToValue :: AttributeValue -> Value
  attributeValueToValue av
    | Just "__eventful-dynamodb-empty-object__" <- av ^. avS = Object HM.empty
    | Just v <- av ^. avS = String v
    | Just v <- av ^. avN = Number $ read $ T.unpack v
    | Just v <- av ^. avBOOL = Bool v
    | Just _ <- av ^. avNULL = Null
    | v <- av ^. avM, not (HM.null v) = Object $ fmap attributeValueToValue v
    | vs <- av ^. avL = Array $ V.fromList $ fmap attributeValueToValue vs
    | otherwise = Null
  