packages feed

sauron-0.0.0.0: src/Sauron/Top/Json.hs

{- |
Copyright: (c) 2022 Dmitrii Kovanikov
SPDX-License-Identifier: MPL-2.0
Maintainer: Dmitrii Kovanikov <kovanikov@gmail.com>

JSON parsing helpers and common utilities
-}

module Sauron.Top.Json
    ( Data (..)
    , Meta (..)
    , Page (..)
    ) where

import Data.Aeson (FromJSON (..), Result (..), Value (Object), fromJSON, withObject, (.:), (.:?))
import Relude.Extra.Type (typeName)


{- | Newtype wrapper for parsing JSON data inside the "data"
field. For example:

@
{ "data": ...
@
-}
newtype Data a = Data
    { unData :: a
    } deriving stock (Show)
      deriving newtype (Eq)

instance (FromJSON a, Typeable a) => FromJSON (Data a) where
    parseJSON = withObject ("Data " <> type_) $ \o -> do
        data_ <- o .: "data"
        pure $ Data data_
      where
        type_ :: String
        type_ = toString $ typeName @a

{- | Parses relevant parts of the "meta" object

@
  "meta": {
    "result_count": 5,
    "newest_id": "1565283709275803650",
    "oldest_id": "1565246649185849344",
    "next_token": "7140dibdnow9c7btw4232rrxi2153ga6h81clvyarutkk"
  }
@
-}
data Meta = Meta
    { metaResultCount :: Int
    , metaNextToken   :: Maybe Text
    } deriving stock (Show, Eq)

instance FromJSON Meta where
    parseJSON = withObject "Meta" $ \o -> do
        metaResultCount <- o .:  "result_count"
        metaNextToken   <- o .:? "next_token"
        pure Meta{..}

{- | A result of a pagination request
-}
data Page a = Page
    { pageData :: Data a
    , pageMeta :: Meta
    } deriving stock (Show, Eq)

instance (FromJSON a, Typeable a) => FromJSON (Page a) where
    parseJSON = withObject ("Page " <> type_) $ \o -> do
        pageData <- case fromJSON @(Data a) (Object o) of
            Error err -> fail err
            Success a -> pure a
        pageMeta <- o .: "meta"
        pure Page{..}
      where
        type_ :: String
        type_ = toString $ typeName @a