-
Notifications
You must be signed in to change notification settings - Fork 325
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more information to federation errors #1560
Changes from 6 commits
84cb50d
6a698b6
9ea32cf
a4a79e6
8e4a8dc
12eddaf
aa6e658
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
module Wire.API.Federation.Client where | ||
|
||
import Control.Monad.Except (ExceptT, MonadError (..), withExceptT) | ||
import Control.Monad.State (MonadState (..), StateT, evalStateT, gets) | ||
import Data.ByteString.Builder (toLazyByteString) | ||
import qualified Data.ByteString.Lazy as LBS | ||
import Data.Domain (Domain, domainText) | ||
|
@@ -41,11 +42,11 @@ data FederatorClientEnv = FederatorClientEnv | |
originDomain :: Domain | ||
} | ||
|
||
newtype FederatorClient (component :: Proto.Component) m a = FederatorClient {runFederatorClient :: ReaderT FederatorClientEnv m a} | ||
deriving newtype (Functor, Applicative, Monad, MonadReader FederatorClientEnv, MonadIO) | ||
newtype FederatorClient (component :: Proto.Component) m a = FederatorClient {runFederatorClient :: ReaderT FederatorClientEnv (StateT (Maybe ByteString) m) a} | ||
deriving newtype (Functor, Applicative, Monad, MonadReader FederatorClientEnv, MonadState (Maybe ByteString), MonadIO) | ||
|
||
runFederatorClientWith :: GrpcClient -> Domain -> Domain -> FederatorClient component m a -> m a | ||
runFederatorClientWith client targetDomain originDomain = flip runReaderT (FederatorClientEnv client targetDomain originDomain) . runFederatorClient | ||
runFederatorClientWith :: Monad m => GrpcClient -> Domain -> Domain -> FederatorClient component m a -> m a | ||
runFederatorClientWith client targetDomain originDomain = flip evalStateT Nothing . flip runReaderT (FederatorClientEnv client targetDomain originDomain) . runFederatorClient | ||
|
||
class KnownComponent (c :: Proto.Component) where | ||
componentVal :: Proto.Component | ||
|
@@ -58,26 +59,37 @@ instance KnownComponent 'Proto.Galley where | |
|
||
-- | expectedStatuses is ignored as we don't get any status from the federator, | ||
-- all responses have '200 OK' as their status. | ||
instance (Monad m, MonadError FederationClientError m, MonadIO m, KnownComponent component) => RunClient (FederatorClient component m) where | ||
instance (Monad m, MonadIO m, MonadError FederationClientFailure m, KnownComponent component) => RunClient (FederatorClient component m) where | ||
runRequestAcceptStatus _expectedStatuses req = do | ||
env <- ask | ||
let path = LBS.toStrict . toLazyByteString $ requestPath req | ||
domain = targetDomain env | ||
mkFailure = FederationClientFailure domain path | ||
failure :: MonadError FederationClientFailure n => FederationClientError -> n x | ||
failure = throwError . mkFailure | ||
rpcFailure = failure . FederationClientRPCError | ||
readBody = \case | ||
RequestBodyLBS lbs -> pure $ LBS.toStrict lbs | ||
RequestBodyBS bs -> pure bs | ||
RequestBodySource _ -> failure FederationClientStreamingUnsupported | ||
put (Just path) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, does this make an actual put request? Shouldn't this only happen a few lines below in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question, I should have explained what this is in a comment. It is not a PUT request, but simply saving the path in the state of the monad stack. The function The idea here is that Servant is going to call this function for each request, and call My solution was to change the monad to keep a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that makes more sense. Too many functions have the same name and without a qualified import it's not directly obvious. Thanks for the explanation and the comments! |
||
body <- readBody . maybe (RequestBodyBS "") fst $ requestBody req | ||
let call = | ||
Proto.ValidatedFederatedRequest | ||
(targetDomain env) | ||
domain | ||
( Proto.Request | ||
(componentVal @component) | ||
(LBS.toStrict . toLazyByteString $ requestPath req) | ||
path | ||
body | ||
(domainText (originDomain env)) | ||
) | ||
grpcResponse <- callRemote (grpcClient env) call | ||
case grpcResponse of | ||
GRpcTooMuchConcurrency _tmc -> rpcErr "too much concurrency" | ||
GRpcErrorCode code -> rpcErr $ "grpc error code: " <> T.pack (show code) | ||
GRpcErrorString msg -> rpcErr $ "grpc error: " <> T.pack msg | ||
GRpcClientError msg -> rpcErr $ "grpc client error: " <> T.pack (show msg) | ||
GRpcOk (Proto.OutwardResponseError err) -> throwError (FederationClientOutwardError err) | ||
GRpcTooMuchConcurrency _tmc -> rpcFailure "too much concurrency" | ||
GRpcErrorCode code -> rpcFailure $ "grpc error code: " <> T.pack (show code) | ||
GRpcErrorString msg -> rpcFailure $ "grpc error: " <> T.pack msg | ||
GRpcClientError msg -> rpcFailure $ "grpc client error: " <> T.pack (show msg) | ||
GRpcOk (Proto.OutwardResponseError err) -> failure (FederationClientOutwardError err) | ||
GRpcOk (Proto.OutwardResponseBody res) -> do | ||
pure $ | ||
Response | ||
|
@@ -89,23 +101,28 @@ instance (Monad m, MonadError FederationClientError m, MonadIO m, KnownComponent | |
responseHttpVersion = HTTP.http11, | ||
responseBody = LBS.fromStrict res | ||
} | ||
where | ||
rpcErr = throwError . FederationClientRPCError | ||
readBody = \case | ||
RequestBodyLBS lbs -> pure $ LBS.toStrict lbs | ||
RequestBodyBS bs -> pure bs | ||
RequestBodySource _ -> throwError FederationClientStreamingUnsupported | ||
throwClientError = throwError . FederationClientServantError | ||
|
||
instance (Monad m, MonadError FederationClientError m) => MonadError FederationClientError (FederatorClient c m) where | ||
|
||
throwClientError err = do | ||
dom <- asks targetDomain | ||
path <- gets (fromMaybe "") | ||
throwError (FederationClientFailure dom path (FederationClientServantError err)) | ||
|
||
instance (Monad m, MonadError FederationClientFailure m) => MonadError FederationClientFailure (FederatorClient c m) where | ||
throwError = FederatorClient . throwError | ||
catchError (FederatorClient action) f = FederatorClient $ catchError action (runFederatorClient . f) | ||
|
||
data FederationError | ||
= FederationUnavailable Text | ||
| FederationNotImplemented | ||
| FederationNotConfigured | ||
| FederationCallFailure FederationClientError | ||
| FederationCallFailure FederationClientFailure | ||
|
||
data FederationClientFailure = FederationClientFailure | ||
{ fedFailDomain :: Domain, | ||
fedFailPath :: ByteString, | ||
fedFailError :: FederationClientError | ||
} | ||
deriving (Show, Eq) | ||
|
||
data FederationClientError | ||
= FederationClientInvalidMethod HTTP.Method | ||
|
@@ -141,7 +158,7 @@ mkFederatorClient = do | |
executeFederated :: | ||
(MonadIO m, HasFederatorConfig m) => | ||
Domain -> | ||
FederatorClient component (ExceptT FederationClientError m) a -> | ||
FederatorClient component (ExceptT FederationClientFailure m) a -> | ||
ExceptT FederationError m a | ||
executeFederated targetDomain action = do | ||
federatorClient <- mkFederatorClient | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this domain the originating domain or the remote domain? Possibly
FederationErrorData
could be extended in the future to contain both.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the remote domain. I assumed the originating domain would not be needed because it would simply be the one where the instance is running. But yes, it can be added easily.