Commit 58d9fcb0 authored by Alfredo Di Napoli's avatar Alfredo Di Napoli

Proper error handling for remote import and export handlers

parent 23a06d28
...@@ -35,6 +35,7 @@ module Gargantext.API.Errors.Types ( ...@@ -35,6 +35,7 @@ module Gargantext.API.Errors.Types (
, GraphQLError(..) , GraphQLError(..)
, ToFrontendErrorData(..) , ToFrontendErrorData(..)
, AccessPolicyErrorReason(..) , AccessPolicyErrorReason(..)
, HasBackendInternalError(..)
-- * Constructing frontend errors -- * Constructing frontend errors
, mkFrontendErrNoDiagnostic , mkFrontendErrNoDiagnostic
...@@ -67,6 +68,7 @@ import Gargantext.Prelude hiding (Location, WithStacktrace) ...@@ -67,6 +68,7 @@ import Gargantext.Prelude hiding (Location, WithStacktrace)
import Gargantext.Utils.Dict (Dict(..)) import Gargantext.Utils.Dict (Dict(..))
import Gargantext.Utils.Jobs.Monad qualified as Jobs import Gargantext.Utils.Jobs.Monad qualified as Jobs
import Servant (ServerError) import Servant (ServerError)
import Control.Lens.Prism (prism')
-- | A 'WithStacktrace' carries an error alongside its -- | A 'WithStacktrace' carries an error alongside its
-- 'CallStack', to be able to print the correct source location -- 'CallStack', to be able to print the correct source location
...@@ -121,6 +123,12 @@ data BackendInternalError ...@@ -121,6 +123,12 @@ data BackendInternalError
makePrisms ''BackendInternalError makePrisms ''BackendInternalError
class HasBackendInternalError e where
_BackendInternalError :: Prism' e BackendInternalError
instance HasBackendInternalError BackendInternalError where
_BackendInternalError = prism' identity Just
instance ToJSON BackendInternalError where instance ToJSON BackendInternalError where
toJSON (InternalJobError s) = toJSON (InternalJobError s) =
object [ ("status", toJSON ("IsFailure" :: Text)) object [ ("status", toJSON ("IsFailure" :: Text))
......
...@@ -17,11 +17,12 @@ module Gargantext.API.Prelude ...@@ -17,11 +17,12 @@ module Gargantext.API.Prelude
, HasServerError(..) , HasServerError(..)
, serverError ) where , serverError ) where
import Control.Exception.Safe qualified as Safe
import Control.Lens ((#)) import Control.Lens ((#))
import Control.Monad.Random (MonadRandom) import Control.Monad.Random (MonadRandom)
import Gargantext.API.Admin.Auth.Types (AuthenticationError) import Gargantext.API.Admin.Auth.Types (AuthenticationError)
import Gargantext.API.Errors.Class (HasAuthenticationError, _AuthenticationError) import Gargantext.API.Errors.Class (HasAuthenticationError, _AuthenticationError)
import Gargantext.API.Errors.Types (HasServerError(..), serverError) import Gargantext.API.Errors.Types (HasServerError(..), serverError, HasBackendInternalError)
import Gargantext.Core.Notifications.CentralExchange.Types (HasCentralExchangeNotification) import Gargantext.Core.Notifications.CentralExchange.Types (HasCentralExchangeNotification)
import Gargantext.Core.Config (HasConfig, HasManager) import Gargantext.Core.Config (HasConfig, HasManager)
import Gargantext.Core.Mail.Types (HasMail) import Gargantext.Core.Mail.Types (HasMail)
...@@ -54,6 +55,7 @@ type ErrC err = ...@@ -54,6 +55,7 @@ type ErrC err =
, HasValidationError err , HasValidationError err
, HasTreeError err , HasTreeError err
, HasServerError err , HasServerError err
, HasBackendInternalError err
, HasAuthenticationError err , HasAuthenticationError err
-- , ToJSON err -- TODO this is arguable -- , ToJSON err -- TODO this is arguable
, Exception err , Exception err
...@@ -63,6 +65,7 @@ type GargServerC env err m = ...@@ -63,6 +65,7 @@ type GargServerC env err m =
( HasNodeStory env err m ( HasNodeStory env err m
, HasMail env , HasMail env
, MonadRandom m , MonadRandom m
, Safe.MonadCatch m
, EnvC env , EnvC env
, ErrC err , ErrC err
, ToJSON err , ToJSON err
......
...@@ -38,7 +38,7 @@ clientRoutes = genericClient ...@@ -38,7 +38,7 @@ clientRoutes = genericClient
remoteImportClient :: Auth.Token remoteImportClient :: Auth.Token
-> C.ConduitT () Named.RemoteBinaryData IO () -> C.ConduitT () Named.RemoteBinaryData IO ()
-> ClientM (C.ConduitT () Named.RemoteBinaryData IO ()) -> ClientM ()
remoteImportClient (S.Token . TE.encodeUtf8 -> token) c = remoteImportClient (S.Token . TE.encodeUtf8 -> token) c =
clientRoutes & apiWithCustomErrorScheme clientRoutes & apiWithCustomErrorScheme
& ($ GES_new) & ($ GES_new)
......
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DerivingStrategies #-}
module Gargantext.API.Routes.Named.Remote ( module Gargantext.API.Routes.Named.Remote (
-- * Routes types -- * Routes types
RemoteAPI(..) RemoteAPI(..)
...@@ -8,6 +9,7 @@ module Gargantext.API.Routes.Named.Remote ( ...@@ -8,6 +9,7 @@ module Gargantext.API.Routes.Named.Remote (
, RemoteBinaryData(..) , RemoteBinaryData(..)
) where ) where
import Conduit qualified as C
import Data.Aeson as JSON import Data.Aeson as JSON
import Data.ByteString.Lazy qualified as BL import Data.ByteString.Lazy qualified as BL
import Data.ByteString qualified as BS import Data.ByteString qualified as BS
...@@ -20,7 +22,6 @@ import Prelude ...@@ -20,7 +22,6 @@ import Prelude
import Servant.API import Servant.API
import Servant.Client.Core.BaseUrl import Servant.Client.Core.BaseUrl
import Test.QuickCheck import Test.QuickCheck
import qualified Conduit as C
data RemoteAPI mode = RemoteAPI data RemoteAPI mode = RemoteAPI
...@@ -77,5 +78,5 @@ instance ToSchema RemoteBinaryData where ...@@ -77,5 +78,5 @@ instance ToSchema RemoteBinaryData where
data RemoteAPI' mode = RemoteAPI' data RemoteAPI' mode = RemoteAPI'
{ remoteExportEp :: mode :- "export" :> ReqBody '[JSON] RemoteExportRequest :> Post '[JSON] () { remoteExportEp :: mode :- "export" :> ReqBody '[JSON] RemoteExportRequest :> Post '[JSON] ()
, remoteImportEp :: mode :- "import" :> StreamBody NoFraming OctetStream (C.ConduitT () RemoteBinaryData IO ()) , remoteImportEp :: mode :- "import" :> StreamBody NoFraming OctetStream (C.ConduitT () RemoteBinaryData IO ())
:> StreamPost NoFraming OctetStream (C.ConduitT () RemoteBinaryData IO ()) :> Post '[JSON] ()
} deriving Generic } deriving Generic
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
module Gargantext.API.Server.Named.Remote ( module Gargantext.API.Server.Named.Remote (
remoteAPI remoteAPI
...@@ -9,16 +10,21 @@ module Gargantext.API.Server.Named.Remote ( ...@@ -9,16 +10,21 @@ module Gargantext.API.Server.Named.Remote (
import Codec.Serialise import Codec.Serialise
import Conduit import Conduit
import Control.Lens (view) import Control.Exception.Safe qualified as Safe
import Control.Exception (toException)
import Control.Lens (view, (#))
import Control.Monad.Except (throwError)
import Data.ByteString.Builder qualified as B import Data.ByteString.Builder qualified as B
import Data.ByteString.Char8 qualified as C8 import Data.ByteString.Char8 qualified as C8
import Data.ByteString.Lazy qualified as BL import Data.ByteString.Lazy qualified as BL
import Data.Conduit.Combinators qualified as C import Data.Conduit.Combinators qualified as C
import Data.Conduit.List qualified as CL import Data.Conduit.List qualified as CL
import Gargantext.API.Errors.Types
import Gargantext.API.Prelude (IsGargServer) import Gargantext.API.Prelude (IsGargServer)
import Gargantext.API.Routes.Client (remoteImportClient) import Gargantext.API.Routes.Client (remoteImportClient)
import Gargantext.API.Routes.Named.Remote qualified as Named import Gargantext.API.Routes.Named.Remote qualified as Named
import Gargantext.Core.Config import Gargantext.Core.Config
import Gargantext.Database.Prelude (IsDBCmd)
import Prelude import Prelude
import Servant.Client.Streaming (mkClientEnv, withClientM, ClientError) import Servant.Client.Streaming (mkClientEnv, withClientM, ClientError)
import Servant.Server.Generic (AsServerT) import Servant.Server.Generic (AsServerT)
...@@ -27,19 +33,21 @@ remoteAPI :: (MonadIO m, IsGargServer env err m) => Named.RemoteAPI (AsServerT m ...@@ -27,19 +33,21 @@ remoteAPI :: (MonadIO m, IsGargServer env err m) => Named.RemoteAPI (AsServerT m
remoteAPI = Named.RemoteAPI $ remoteAPI = Named.RemoteAPI $
Named.RemoteAPI' Named.RemoteAPI'
{ remoteExportEp = remoteExportHandler { remoteExportEp = remoteExportHandler
, remoteImportEp = pure , remoteImportEp = remoteImportHandler
} }
type ExpectedPayload = C8.ByteString -- FIXME(adn) type ExpectedPayload = C8.ByteString -- FIXME(adn)
remoteImportHandler :: (MonadIO m, Serialise a) => ConduitT () Named.RemoteBinaryData IO () -> m a remoteImportHandler :: (HasBackendInternalError err, IsDBCmd env err m, MonadIO m)
remoteImportHandler c = liftIO $ do => ConduitT () Named.RemoteBinaryData IO ()
chunks <- sourceToList $ c .| C.map (B.byteString . Named.getRemoteBinaryData) -> m ()
case deserialiseOrFail (B.toLazyByteString $ mconcat chunks) of remoteImportHandler c = do
Left err -> liftIO $ error $ "Deserialization error: " ++ show err chunks <- liftIO $ sourceToList $ c .| C.map (B.byteString . Named.getRemoteBinaryData)
Right value -> pure value case deserialiseOrFail @ExpectedPayload (B.toLazyByteString $ mconcat chunks) of
Left err -> throwError $ _BackendInternalError # InternalUnexpectedError (toException $ userError $ "Deserialization error: " ++ show err)
Right value -> liftIO $ putStrLn $ show $ value
remoteExportHandler :: ( MonadIO m remoteExportHandler :: ( MonadIO m, Safe.MonadCatch m
, IsGargServer err env m , IsGargServer err env m
) )
=> Named.RemoteExportRequest => Named.RemoteExportRequest
...@@ -48,13 +56,14 @@ remoteExportHandler Named.RemoteExportRequest{..} = do ...@@ -48,13 +56,14 @@ remoteExportHandler Named.RemoteExportRequest{..} = do
mgr <- view gargHttpManager mgr <- view gargHttpManager
-- FIXME(adn) eventually we want to be sending nodes here. -- FIXME(adn) eventually we want to be sending nodes here.
let node = C8.pack "hello world" let node = C8.pack "hello world"
result <- liftIO $ withClientM (remoteImportClient _rer_instance_auth (streamEncoder node)) (mkClientEnv mgr _rer_instance_url) streamDecode liftIO (withClientM (remoteImportClient _rer_instance_auth (streamEncoder node)) (mkClientEnv mgr _rer_instance_url) streamDecode)
liftIO $ putStrLn (show (result :: ExpectedPayload)) `Safe.catch` \(e :: BackendInternalError) -> throwError $ _BackendInternalError # e
streamEncoder :: Serialise a => a -> ConduitT () Named.RemoteBinaryData IO () streamEncoder :: (MonadIO m, Serialise a) => a -> ConduitT () Named.RemoteBinaryData m ()
streamEncoder = CL.sourceList . map Named.RemoteBinaryData . BL.toChunks . serialise streamEncoder = CL.sourceList . map Named.RemoteBinaryData . BL.toChunks . serialise
streamDecode :: Either ClientError (ConduitT () Named.RemoteBinaryData IO ()) -> IO ExpectedPayload -- | Returns a conduit which can be used to decode
streamDecode :: Either ClientError () -> IO ()
streamDecode = \case streamDecode = \case
Left err -> error $ show err -- FIXME(adn) How to deal with the error properly? Left err -> Safe.throwIO $ InternalUnexpectedError (toException $ userError $ show err)
Right c -> remoteImportHandler c Right _ -> pure ()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment