packages feed

tricorder-0.6.0.0: src/Tricorder/CLI/App/Test/Json/Responses.hs

module Tricorder.CLI.App.Test.Json.Responses
    ( Response (..)
    , MessageWithLoc (..)
    , SuitesResponse (..)
    )
where

import Atelier.Types.JSON.QuietSnake (QuietSnake (..))
import Data.Aeson
    ( FromJSON
    , Options (..)
    , SumEncoding (..)
    , ToJSON (..)
    , defaultOptions
    , genericToEncoding
    , genericToJSON
    , object
    , pairs
    , (.=)
    )
import Text.Casing (quietSnake)

import Tricorder.FieldMask.Projection qualified as FieldMask (Projection)


data Response a
    = Error MessageWithLoc
    | Pending MessageWithLoc
    | Success a
    deriving stock (Eq, Functor, Generic, Show)


instance Applicative Response where
    pure = Success
    Error message <*> _ = Error message
    _ <*> Error message = Error message
    Pending message <*> _ = Pending message
    _ <*> Pending message = Pending message
    Success f <*> Success a = Success $ f a


instance Monad Response where
    Error message >>= _ = Error message
    Pending message >>= _ = Pending message
    Success a >>= f = f a


instance (ToJSON a) => ToJSON (Response a) where
    toJSON = genericToJSON responseOptions
    toEncoding = genericToEncoding responseOptions


data MessageWithLoc = MessageWithLoc
    { location :: Text
    , message :: Text
    }
    deriving stock (Eq, Generic, Show)
    deriving (FromJSON, ToJSON) via QuietSnake MessageWithLoc


responseOptions :: Options
responseOptions =
    defaultOptions
        { constructorTagModifier = quietSnake
        , sumEncoding = ObjectWithSingleField
        }


-- | Test suites, cut down to the requested fields.
newtype SuitesResponse = SuitesResponse
    { suites :: [FieldMask.Projection]
    }
    deriving stock (Eq, Show)


-- | Written by hand so 'toEncoding' keeps the field order of the projection.
instance ToJSON SuitesResponse where
    toJSON response = object ["suites" .= response.suites]
    toEncoding response = pairs ("suites" .= response.suites)