module Aftok.Api.Json where import Prelude import Control.Monad.Error.Class (throwError) import Control.Monad.Except.Trans (ExceptT, except, withExceptT, runExceptT) import Control.Monad.Trans.Class (lift) import Data.Argonaut.Core (Json) import Data.Argonaut.Decode (class DecodeJson, decodeJson, JsonDecodeError(..), (.:)) import Data.BigInt (fromNumber) as BigInt import Data.DateTime (DateTime) import Data.DateTime.Instant (Instant, fromDateTime) import Data.Functor.Compose (Compose(..)) import Data.Either (Either(..), note) import Data.Foldable (class Foldable, foldr, foldl, foldMap) import Data.JSDate as JD import Data.Maybe (Maybe(..)) import Data.Newtype (class Newtype, unwrap, over) import Data.Traversable (class Traversable, traverse) import Effect (Effect) import Effect.Aff (Aff) import Foreign.Object (Object) import Affjax as AJAX import Affjax (Response, printError) import Affjax.StatusCode (StatusCode(..)) import Aftok.Api.Types (APIError(..)) import Aftok.Zcash (Zatoshi(..)) newtype JsonCompose f g a = JsonCompose (Compose f g a) derive instance jsonComposeNewtype :: Newtype (JsonCompose f g a) _ instance jsonComposeFunctor :: (Functor f, Functor g) => Functor (JsonCompose f g) where map f = over JsonCompose (map f) instance jsonComposeFoldable :: (Foldable f, Foldable g) => Foldable (JsonCompose f g) where foldr f b = foldr f b <<< unwrap foldl f b = foldl f b <<< unwrap foldMap f = foldMap f <<< unwrap instance jsonComposeTraversable :: (Traversable f, Traversable g) => Traversable (JsonCompose f g) where traverse f = map JsonCompose <<< traverse f <<< unwrap sequence = traverse identity instance jsonComposeDecodeJson :: (DecodeJson (f (g a))) => DecodeJson (JsonCompose f g a) where decodeJson json = JsonCompose <<< Compose <$> decodeJson json decompose :: forall f g a. JsonCompose f g a -> f (g a) decompose (JsonCompose (Compose fga)) = fga parseJsonDate :: Json -> ExceptT JsonDecodeError Effect DateTime parseJsonDate json = do str <- except $ decodeJson json (withExceptT TypeMismatch $ parseDate str) parseDate :: String -> ExceptT String Effect DateTime parseDate str = do jsDate <- lift $ JD.parse str except $ note ("Unable to convert date " <> show jsDate <> " to a valid DateTime value.") (JD.toDateTime jsDate) type Decode a = Json -> Either JsonDecodeError a parseResponse :: forall a. Decode a -> Either AJAX.Error (Response Json) -> Aff (Either APIError a) parseResponse decode response = runExceptT $ case response of Left err -> throwError $ Error { status: Nothing, message: printError err } Right r -> case r.status of StatusCode 403 -> throwError $ Forbidden StatusCode 200 -> withExceptT ParseFailure <<< except $ decode r.body other -> throwError $ Error { status: Just other, message: r.statusText } decodeDatedJson :: forall t. Traversable t => Decode (t String) -> Json -> ExceptT JsonDecodeError Effect (t DateTime) decodeDatedJson decode json = do decoded <- except $ decode json (withExceptT TypeMismatch $ traverse parseDate decoded) parseDatedResponse :: forall t. Traversable t => Decode (t String) -> Either AJAX.Error (Response Json) -> ExceptT APIError Effect (t Instant) parseDatedResponse decode = case _ of Left err -> throwError $ Error { status: Nothing, message: printError err } Right r -> case r.status of StatusCode 403 -> throwError $ Forbidden StatusCode 200 -> withExceptT ParseFailure $ map fromDateTime <$> decodeDatedJson decode r.body other -> throwError $ Error { status: Just other, message: r.statusText } parseDatedResponseMay :: forall t. Traversable t => Decode (t String) -> Either AJAX.Error (Response Json) -> ExceptT APIError Effect (Maybe (t Instant)) parseDatedResponseMay decode = case _ of Left err -> throwError $ Error { status: Nothing, message: printError err } Right r -> case r.status of StatusCode 403 -> throwError $ Forbidden StatusCode 404 -> pure Nothing StatusCode 200 -> map Just <<< withExceptT ParseFailure <<< map (map fromDateTime) $ decodeDatedJson decode r.body other -> throwError $ Error { status: Just other, message: r.statusText } parseZatoshi :: Object Json -> Either JsonDecodeError Zatoshi parseZatoshi obj = map Zatoshi $ (note (TypeMismatch "Failed to decode as Zatoshi") <<< BigInt.fromNumber) =<< (obj .: "zatoshi")