{-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} module Ide.Plugin.Error ( -- * Plugin Error Handling API PluginError(..), toErrorCode, toPriority, handleMaybe, handleMaybeM, getNormalizedFilePathE, ) where import Control.Monad.Extra (maybeM) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Except (ExceptT (..), throwE) import qualified Data.Text as T import Ide.Logger import Language.LSP.Protocol.Types -- ---------------------------------------------------------------------------- -- Plugin Error wrapping -- ---------------------------------------------------------------------------- -- |Each PluginError corresponds to either a specific ResponseError we want to -- return or a specific way we want to log the error. If the currently present -- ones are insufficient for the needs of your plugin, please feel free to add -- a new one. -- -- Currently the PluginErrors we provide can be broken up into several groups. -- First is PluginInternalError, which is the most serious of the errors, and -- also the "default" error that is used for things such as uncaught exceptions. -- Then we have PluginInvalidParams, which along with PluginInternalError map -- to a corresponding ResponseError. -- -- Next we have PluginRuleFailed and PluginInvalidUserState, with the only -- difference being PluginRuleFailed is specific to Shake rules and -- PluginInvalidUserState can be used for everything else. Both of these are -- "non-errors", and happen whenever the user's code is in a state where the -- plugin is unable to provide a answer to the users request. PluginStaleResolve -- is similar to the above two Error types, but is specific to resolve plugins, -- and is used only when the data provided by the resolve request is stale, -- preventing the proper resolution of it. -- -- Finally we have the outlier, PluginRequestRefused, where we allow a handler -- to preform "pluginEnabled" checks inside the handler, and reject the request -- after viewing it. The behavior of only one handler passing `pluginEnabled` -- and then returning PluginRequestRefused should be the same as if no plugins -- passed the `pluginEnabled` stage. data PluginError = -- |PluginInternalError should be used if an error has occurred. This -- should only rarely be returned. As it's logged with Error, it will be -- shown by the client to the user via `showWindow`. All uncaught exceptions -- will be caught and converted to this error. -- -- This error will be be converted into an InternalError response code. It -- will be logged with Error and takes the highest precedence (1) in being -- returned as a response to the client. PluginInternalError T.Text -- |PluginInvalidParams should be used if the parameters of the request are -- invalid. This error means that there is a bug in the client's code -- (otherwise they wouldn't be sending you requests with invalid -- parameters). -- -- This error will be will be converted into a InvalidParams response code. -- It will be logged with Warning and takes medium precedence (2) in being -- returned as a response to the client. | PluginInvalidParams T.Text -- |PluginInvalidUserState should be thrown when a function that your plugin -- depends on fails. This should only be used when the function fails -- because the user's code is in an invalid state. -- -- This error takes the name of the function that failed. Prefer to catch -- this error as close to the source as possible. -- -- This error will be logged with Debug, and will be converted into a -- RequestFailed response. It takes a low precedence (3) in being returned -- as a response to the client. | PluginInvalidUserState T.Text -- |PluginRequestRefused allows your handler to inspect a request before -- rejecting it. In effect it allows your plugin to act make a secondary -- `pluginEnabled` decision after receiving the request. This should only be -- used if the decision to accept the request can not be made in -- `pluginEnabled`. -- -- This error will be with Debug. If it's the only response to a request, -- HLS will respond as if no plugins passed the `pluginEnabled` stage. | PluginRequestRefused T.Text -- |PluginRuleFailed should be thrown when a Rule your response depends on -- fails. -- -- This error takes the name of the Rule that failed. -- -- This error will be logged with Debug, and will be converted into a -- RequestFailed response code. It takes a low precedence (3) in being -- returned as a response to the client. | PluginRuleFailed T.Text -- |PluginStaleResolve should be thrown when your resolve request is -- provided with data it can no longer resolve. -- -- This error will be logged with Debug, and will be converted into a -- ContentModified response. It takes a low precedence (3) in being returned -- as a response to the client. | PluginStaleResolve instance Pretty PluginError where pretty :: forall ann. PluginError -> Doc ann pretty = \case PluginInternalError Text msg -> Doc ann "Internal Error:" forall ann. Doc ann -> Doc ann -> Doc ann <+> forall a ann. Pretty a => a -> Doc ann pretty Text msg PluginError PluginStaleResolve -> Doc ann "Stale Resolve" PluginRuleFailed Text rule -> Doc ann "Rule Failed:" forall ann. Doc ann -> Doc ann -> Doc ann <+> forall a ann. Pretty a => a -> Doc ann pretty Text rule PluginInvalidParams Text text -> Doc ann "Invalid Params:" forall ann. Doc ann -> Doc ann -> Doc ann <+> forall a ann. Pretty a => a -> Doc ann pretty Text text PluginInvalidUserState Text text -> Doc ann "Invalid User State:" forall ann. Doc ann -> Doc ann -> Doc ann <+> forall a ann. Pretty a => a -> Doc ann pretty Text text PluginRequestRefused Text msg -> Doc ann "Request Refused: " forall ann. Doc ann -> Doc ann -> Doc ann <+> forall a ann. Pretty a => a -> Doc ann pretty Text msg -- |Converts to ErrorCode used in LSP ResponseErrors toErrorCode :: PluginError -> (LSPErrorCodes |? ErrorCodes) toErrorCode :: PluginError -> LSPErrorCodes |? ErrorCodes toErrorCode (PluginInternalError Text _) = forall a b. b -> a |? b InR ErrorCodes ErrorCodes_InternalError toErrorCode (PluginInvalidParams Text _) = forall a b. b -> a |? b InR ErrorCodes ErrorCodes_InvalidParams toErrorCode (PluginInvalidUserState Text _) = forall a b. a -> a |? b InL LSPErrorCodes LSPErrorCodes_RequestFailed -- PluginRequestRefused should never be a argument to `toResponseError`, as -- it should be dealt with in `extensiblePlugins`, but this is here to make -- this function complete toErrorCode (PluginRequestRefused Text _) = forall a b. b -> a |? b InR ErrorCodes ErrorCodes_MethodNotFound toErrorCode (PluginRuleFailed Text _) = forall a b. a -> a |? b InL LSPErrorCodes LSPErrorCodes_RequestFailed toErrorCode PluginError PluginStaleResolve = forall a b. a -> a |? b InL LSPErrorCodes LSPErrorCodes_ContentModified -- |Converts to a logging priority. In addition to being used by the logger, -- `combineResponses` currently uses this to choose which response to return, -- so care should be taken in changing it. toPriority :: PluginError -> Priority toPriority :: PluginError -> Priority toPriority (PluginInternalError Text _) = Priority Error toPriority (PluginInvalidParams Text _) = Priority Warning toPriority (PluginInvalidUserState Text _) = Priority Debug toPriority (PluginRequestRefused Text _) = Priority Debug toPriority (PluginRuleFailed Text _) = Priority Debug toPriority PluginError PluginStaleResolve = Priority Debug handleMaybe :: Monad m => e -> Maybe b -> ExceptT e m b handleMaybe :: forall (m :: * -> *) e b. Monad m => e -> Maybe b -> ExceptT e m b handleMaybe e msg = forall b a. b -> (a -> b) -> Maybe a -> b maybe (forall (m :: * -> *) e a. Monad m => e -> ExceptT e m a throwE e msg) forall (m :: * -> *) a. Monad m => a -> m a return handleMaybeM :: Monad m => e -> m (Maybe b) -> ExceptT e m b handleMaybeM :: forall (m :: * -> *) e b. Monad m => e -> m (Maybe b) -> ExceptT e m b handleMaybeM e msg m (Maybe b) act = forall (m :: * -> *) b a. Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b maybeM (forall (m :: * -> *) e a. Monad m => e -> ExceptT e m a throwE e msg) forall (m :: * -> *) a. Monad m => a -> m a return forall a b. (a -> b) -> a -> b $ forall (t :: (* -> *) -> * -> *) (m :: * -> *) a. (MonadTrans t, Monad m) => m a -> t m a lift m (Maybe b) act getNormalizedFilePathE :: Monad m => Uri -> ExceptT PluginError m NormalizedFilePath getNormalizedFilePathE :: forall (m :: * -> *). Monad m => Uri -> ExceptT PluginError m NormalizedFilePath getNormalizedFilePathE Uri uri = forall (m :: * -> *) e b. Monad m => e -> Maybe b -> ExceptT e m b handleMaybe (Text -> PluginError PluginInvalidParams (String -> Text T.pack forall a b. (a -> b) -> a -> b $ String "uriToNormalizedFile failed. Uri:" forall a. Semigroup a => a -> a -> a <> forall a. Show a => a -> String show Uri uri)) forall a b. (a -> b) -> a -> b $ NormalizedUri -> Maybe NormalizedFilePath uriToNormalizedFilePath forall a b. (a -> b) -> a -> b $ Uri -> NormalizedUri toNormalizedUri Uri uri