{-# LANGUAGE LambdaCase #-} module System.Win32.Shortcut.Error where import Control.Monad (join) import Control.Monad.Except (ExceptT (..)) import Control.Monad.Trans (MonadTrans, lift) import System.Win32.Shortcut.Internal -- | In @Win32 API@ 'HRESULT' is used to indicate error and warning -- conditions. 'HRESULTError' wraps 'HRESULT' to give it some meaning. data HRESULTError = E_ABORT -- ^ Operation aborted. | E_ACCESSDENIED -- ^ General access denied error. | E_FAIL -- ^ Unspecified failure. | E_HANDLE -- ^ Handle that is not valid. | E_INVALIDARG -- ^ One or more arguments are not valid. | E_NOINTERFACE -- ^ No such interface supported. | E_NOTIMPL -- ^ Not implemented. | E_OUTOFMEMORY -- ^ Failed to allocate necessary memory. | E_POINTER -- ^ Pointer that is not valid. | E_UNEXPECTED -- ^ Unexpected failure. | HRESULTUnknown HRESULT -- ^ Other, unknown 'HRESULT' value. deriving (Show) succeeded :: HRESULT -> Either HRESULTError () succeeded x | x == s_OK = Right () | x == e_ACCESSDENIED = Left E_ACCESSDENIED | x == e_FAIL = Left E_FAIL | x == e_HANDLE = Left E_HANDLE | x == e_INVALIDARG = Left E_INVALIDARG | x == e_NOINTERFACE = Left E_NOINTERFACE | x == e_NOTIMPL = Left E_NOTIMPL | x == e_OUTOFMEMORY = Left E_OUTOFMEMORY | x == e_POINTER = Left E_POINTER | x == e_UNEXPECTED = Left E_UNEXPECTED | otherwise = Left (HRESULTUnknown x) succeeded' :: (HRESULTError -> Either e ()) -> HRESULT -> Either e () succeeded' f = either f Right . succeeded withExcept' :: (MonadTrans t, Monad (t m), Monad m) => (HRESULTError -> Either e ()) -> t m (m HRESULT) -> ExceptT e (t m) () withExcept' f = ExceptT . fmap (succeeded' f) . join . fmap lift overLeft :: (e1 -> e2) -> Either e1 a -> Either e2 a overLeft f = either (Left . f) Right -- | Some error that comes from 'HRESULT'. data OrHRESULTError err = OtherError err -- ^ 'HRESULT' value was expected and fits @err@. | HRESULTError HRESULTError -- ^ 'HRESULT' did not fit @err@. deriving (Show) -- | @COM@ cannot create specified interface. data CoCreateInstanceError = CCI_REGDB_E_CLASSNOTREG -- ^ A specified class is not registered in the registration database. -- Also can indicate that the type of server you requested -- in the CLSCTX enumeration is not registered or the values -- for the server types in the registry are corrupt. | CCI_CLASS_E_NOAGGREGATION -- ^ This class cannot be created as part of an aggregate. | CCI_E_NOINTERFACE -- ^ The specified class does not implement the requested interface, -- or the controlling IUnknown does not expose the requested interface. | CCI_E_POINTER -- ^ The ppv parameter is NULL. | CCI_CO_E_NOTINITIALIZED -- ^ @COM@ library was not initialized. deriving (Show) toCoCreateInstanceError :: HRESULTError -> Either (OrHRESULTError CoCreateInstanceError) () toCoCreateInstanceError = Left . \case HRESULTUnknown x | x == -2147221164 -- 0x80040154 -> OtherError CCI_REGDB_E_CLASSNOTREG | x == -2147221232 -- 0x80040110 -> OtherError CCI_CLASS_E_NOAGGREGATION | x == -2147221008 -- 0x800401f0 -> OtherError CCI_CO_E_NOTINITIALIZED E_NOINTERFACE -> OtherError CCI_E_NOINTERFACE E_POINTER -> OtherError CCI_E_POINTER other -> HRESULTError other -- | @COM@ failed to initialize. data CoInitializeError = CI_RPC_E_CHANGED_MODE -- ^ A previous call to CoInitializeEx specified the concurrency model -- for this thread as multithread apartment (MTA). -- This could also indicate that a change from neutral-threaded -- apartment to single-threaded apartment has occurred. | CI_CO_E_NOTINITIALIZED -- ^ You need to initialize the @COM@ library on a thread -- before you call any of the library functions. -- Otherwise, the @COM@ function will return 'CI_CO_E_NOTINITIALIZED'. deriving (Show) toCoInitializeError :: HRESULTError -> Either (OrHRESULTError CoInitializeError) () toCoInitializeError = Left . \case HRESULTUnknown x | x == -2147417850 -- 0x80010106 -> OtherError CI_RPC_E_CHANGED_MODE | x == -2147221008 -- 0x800401F0 -> OtherError CI_CO_E_NOTINITIALIZED other -> HRESULTError other -- | @IPersistFile@ interface failed to save a file. data SaveError = SE_S_FALSE -- ^ The object was not successfully saved. deriving (Show) toSaveError :: HRESULTError -> Either (OrHRESULTError SaveError) () toSaveError = \case HRESULTUnknown x | x == s_FALSE -> Left (OtherError SE_S_FALSE) other -> Left (HRESULTError other) -- | @IPersistFile@ interface failed to load a file. data LoadError = LE_E_OUTOFMEMORY -- ^ The object could not be loaded due to a lack of memory. | LE_E_FAIL -- ^ The object could not be loaded for some reason -- other than a lack of memory. deriving (Show) toLoadError :: HRESULTError -> Either (OrHRESULTError LoadError) () toLoadError = Left . \case E_OUTOFMEMORY -> OtherError LE_E_OUTOFMEMORY E_FAIL -> OtherError LE_E_FAIL other -> HRESULTError other data PathError = Path_S_FALSE -- ^ The operation is successful but no path is retrieved. | PathTooLong -- ^ Path length >= 260 characters. deriving (Show) toPathError :: HRESULTError -> Either (OrHRESULTError PathError) () toPathError = \case HRESULTUnknown x | x == s_FALSE -> Left (OtherError Path_S_FALSE) other -> Left (HRESULTError other) data ArgumentsError = ArgumentsTooLong -- ^ Arguments length >= 32768 characters. deriving (Show) data WorkingDirectoryError = WorkingDirectoryTooLong deriving (Show) data DescriptionError = DescriptionTooLong deriving (Show) data IconLocationError = IconLocationTooLong deriving (Show) -- | Catch-all type for errors. data ShortcutError = InitializationError (OrHRESULTError CoInitializeError) | CreateIShellLinkInterfaceError (OrHRESULTError CoCreateInstanceError) | CreateIPersistFileInterfaceError (OrHRESULTError CoCreateInstanceError) | LoadError (OrHRESULTError LoadError) | SaveError (OrHRESULTError SaveError) | InvalidPath (OrHRESULTError PathError) | InvalidArguments (OrHRESULTError ArgumentsError) | InvalidWorkingDirectory (OrHRESULTError WorkingDirectoryError) | InvalidDescription (OrHRESULTError DescriptionError) | InvalidIconLocation (OrHRESULTError IconLocationError) | InvalidHotkey HRESULTError | InvalidShowCmd HRESULTError deriving (Show)