| Safe Haskell | None |
|---|---|
| Language | Haskell2010 |
Monad.Rail
Description
Railway-Oriented error handling for Haskell applications.
Monad.Rail is a Haskell library implementing Railway-Oriented Programming (ROP), a functional approach to error handling that makes both success and failure paths explicit and composable.
What is Railway-Oriented Programming?
Railway-Oriented Programming uses a railway analogy: your program has two tracks, one for success and one for failure. Operations can move between tracks, and once on the failure track, you stay there until the end.
This library implements ROP through:
RailT- A monad transformer for railway computationsthrowError- Moving to the failure track(<!>)- Combining validations while collecting all errors- Error accumulation - Multiple errors are gathered, not just the first one
Quick Start
Implement HasErrorInfo with errorPublicMessage — the only required method.
Derive Data to get an automatic error code from the constructor name:
>>>{-# LANGUAGE DeriveDataTypeable #-}>>>>>>data UserError = NameEmpty | EmailInvalid>>>deriving (Show, Data)>>>>>>instance HasErrorInfo UserError where>>>errorPublicMessage NameEmpty = "Name cannot be empty">>>errorPublicMessage EmailInvalid = "Email format is invalid"
Override individual methods when you need custom codes or errorDetails:
>>>instance HasErrorInfo UserError where>>>errorPublicMessage NameEmpty = "Name cannot be empty">>>errorPublicMessage EmailInvalid = "Email format is invalid">>>>>>errorCode NameEmpty = "UserNameEmpty">>>errorCode EmailInvalid = "UserEmailInvalid"
Use in your railway:
>>>validateUser :: Rail ()>>>validateUser = do>>>checkName <!> checkEmail>>>saveToDatabase>>>liftIO $ putStrLn "User created!"
Run and handle the result:
>>>main :: IO ()>>>main = do>>>result <- runRail validateUser>>>case result of>>>Right () -> putStrLn "Success!">>>Left errors -> print errors
Error Accumulation
The <!> operator is key to ROP. It runs both validations regardless of failure:
>>>validate :: Rail ()>>>validate = do>>>checkName <!> checkEmail <!> checkAge>>>-- If any checks fail, ALL errors are reported together
Core Concepts
Rail- The monad for your computationsFailure- Contains accumulated errorsSomeError- Wrapper for any error typeHasErrorInfo- Typeclass for custom errors<!>- Error accumulation operator
Logging and Monitoring
Each error carries two separate records:
PublicErrorInfo- Safe for end users: message, code, and details. Assembled viapublicErrorInfo; serialized to JSON in API responses. Null fields are omitted.InternalErrorInfo- Sensitive diagnostics: severity, internal message, exception, and call stack. Assembled viainternalErrorInfo; implementsToJSONfor structured log output but is never included in public API responses. Null fields are omitted.
The Failure type implements ToJSON, so errors serialize automatically:
>>>import Data.Aeson (encode)>>>result <- runRail myRail>>>case result of>>>Left errors -> BS.putStrLn $ encode errors>>>Right _ -> pure ()
Synopsis
- newtype RailT e (m :: Type -> Type) a = RailT {}
- type Rail a = RailT Failure IO a
- newtype Failure = Failure {}
- runRailT :: Monad m => RailT e m a -> m (Either e a)
- runRail :: Rail a -> IO (Either Failure a)
- throwError :: forall e (m :: Type -> Type) a. (HasErrorInfo e, Show e, Typeable e, Monad m) => e -> RailT Failure m a
- throwUnhandledException :: forall (m :: Type -> Type) a. (HasCallStack, Monad m) => SomeException -> RailT Failure m a
- throwUnhandledExceptionWithCode :: forall (m :: Type -> Type) a. (HasCallStack, Monad m) => Text -> SomeException -> RailT Failure m a
- tryRail :: HasCallStack => IO a -> Rail a
- tryRailWithCode :: HasCallStack => (SomeException -> Text) -> IO a -> Rail a
- tryRailWithError :: (HasCallStack, HasErrorInfo e) => (SomeException -> e) -> IO a -> Rail a
- (<!>) :: forall (m :: Type -> Type). Monad m => RailT Failure m () -> RailT Failure m () -> RailT Failure m ()
- data ErrorSeverity
- data SomeErrorDetails = (ToJSON a, Show a, Typeable a) => SomeErrorDetails a
- data PublicErrorInfo = PublicErrorInfo {}
- data InternalErrorInfo = InternalErrorInfo {}
- class HasErrorInfo e where
- errorPublicMessage :: e -> Text
- errorCode :: e -> Text
- errorDetails :: e -> Maybe SomeErrorDetails
- errorSeverity :: e -> ErrorSeverity
- errorInternalMessage :: e -> Maybe Text
- errorException :: e -> Maybe SomeException
- errorCallStack :: e -> Maybe CallStack
- publicErrorInfo :: HasErrorInfo e => e -> PublicErrorInfo
- internalErrorInfo :: HasErrorInfo e => e -> InternalErrorInfo
- data SomeError = (HasErrorInfo e, Show e, Typeable e) => SomeError e
- data UnhandledException = UnhandledException {}
Core Types
newtype RailT e (m :: Type -> Type) a Source #
The Railway-Oriented monad transformer.
RailT wraps ExceptT to provide composable error handling with support for
error accumulation. It is parameterized over:
e- The error type (typicallyFailure)m- The underlying monad (oftenIO)a- The type of successful values
The transformer implements Functor, Applicative, Monad, and MonadIO,
allowing it to be used seamlessly with do-notation and other monadic operations.
Examples
Create a computation that can fail:
>>>computation :: RailT Failure IO String>>>computation = do>>>liftIO $ putStrLn "Starting...">>>pure "result"
Use with Rail for a more concise type signature:
>>>computation :: Rail String>>>computation = do>>>liftIO $ putStrLn "Starting...">>>pure "result"
type Rail a = RailT Failure IO a Source #
A convenient type alias for Railway computations in the IO monad.
This is the most common way to use RailT. It fixes the error type to Failure
and the base monad to IO, providing a simple interface for building IO-based
applications with Railway-Oriented error handling.
Rail a is equivalent to RailT Failure IO a
Example
>>>myApp :: Rail ()>>>myApp = do>>>validateInput>>>processData>>>liftIO $ putStrLn "Complete!"
Represents a collection of one or more application errors accumulated during a Railway computation.
This type is used as the error type in RailT computations. It guarantees that
at least one error is always present (using NonEmpty), which is essential for
the Railway-Oriented programming model where a failure state must contain error information.
Multiple errors are accumulated when using the <!> operator for validations,
allowing you to collect all validation errors before reporting failure.
Combining Errors
Use the Semigroup instance to combine multiple Failure values:
>>>let err1 = Failure (SomeError e1 :| [])>>>let err2 = Failure (SomeError e2 :| [])>>>let combined = err1 <> err2 -- Contains both errors
Constructors
| Failure | |
Running Railways
runRailT :: Monad m => RailT e m a -> m (Either e a) Source #
Runs a RailT computation in any base monad m, returning m (Either e a).
This is the general form of runRail. Use it when your base monad is not IO —
for example, when RailT is stacked on top of StateT, ReaderT, or any other
transformer.
Example: custom monad stack
>>>import Control.Monad.State (StateT, runStateT)>>>>>>type AppRail a = RailT Failure (StateT AppState IO) a>>>>>>runAppRail :: AppState -> AppRail a -> IO (Either Failure a, AppState)>>>runAppRail initialState = runStateT . runRailT
runRail :: Rail a -> IO (Either Failure a) Source #
Runs a Rail computation and returns the result or accumulated errors.
This function executes the railway computation and returns the final result wrapped
in Either. On success, you get Right value. On failure, you get Left errors
containing all accumulated SomeError values.
The returned Failure contains at least one error (guaranteed by NonEmpty),
making it safe to handle errors without null checks.
Example
>>>result <- runRail myComputation>>>case result of>>>Right value -> putStrLn $ "Success: " ++ show value>>>Left errors -> do>>>putStrLn "Errors occurred:">>>mapM_ print (getErrors errors)
JSON Serialization
The Failure type implements ToJSON, so you can easily serialize errors:
>>>import Data.Aeson (encode)>>>result <- runRail myComputation>>>case result of>>>Left errors -> putStrLn $ BS.unpack $ encode errors>>>Right _ -> pure ()
Throwing Errors
throwError :: forall e (m :: Type -> Type) a. (HasErrorInfo e, Show e, Typeable e, Monad m) => e -> RailT Failure m a Source #
Throws an application error in the Railway.
This function wraps the error in SomeError and then in a Failure container,
immediately failing the computation with that error. Subsequent operations in the
do-block will not be executed.
Any error type with HasErrorInfo, Show, and Typeable constraints can be
thrown directly — no need to wrap it in SomeError manually.
Use this function when you encounter an error condition that should stop execution.
For validations where you want to collect multiple errors before failing, use the
<!> operator instead.
Example
>>>checkAge :: Int -> Rail ()>>>checkAge age = do>>>when (age < 0) $ throwError AgeNegative>>>pure ()
Error Accumulation
When multiple errors occur (e.g., with <!>), Failure will contain all of them.
You can then handle them together or individually:
>>>result <- runRail (checkName <!> checkEmail)>>>case result of>>>Left errors -> mapM_ print (getErrors errors)>>>Right () -> putStrLn "All valid!"
throwUnhandledException :: forall (m :: Type -> Type) a. (HasCallStack, Monad m) => SomeException -> RailT Failure m a Source #
Throw an unhandled IO exception as a Railway error using the default code "UnhandledException".
This is a convenience wrapper around throwError for the common pattern of
catching an IO exception and re-throwing it as an UnhandledException. It
captures the call stack automatically, so you do not need to pass it manually.
The call stack is captured at the call site of throwUnhandledException, not at the
definition of any wrapper around it (provided the wrapper also carries
HasCallStack).
Use throwUnhandledExceptionWithCode when you need a domain-specific error code.
Example
>>>import qualified Control.Exception as E>>>>>>safeQuery :: Rail Row>>>safeQuery = do>>>result <- liftIO $ E.try runQuery>>>case result of>>>Right row -> pure row>>>Left ex -> throwUnhandledException ex
throwUnhandledExceptionWithCode :: forall (m :: Type -> Type) a. (HasCallStack, Monad m) => Text -> SomeException -> RailT Failure m a Source #
Like throwUnhandledException, but with a domain-specific error code.
The call stack is captured at the call site, provided the wrapper also carries
HasCallStack.
Example
>>>import qualified Control.Exception as E>>>>>>safeQuery :: Rail Row>>>safeQuery = do>>>result <- liftIO $ E.try runQuery>>>case result of>>>Right row -> pure row>>>Left ex -> throwUnhandledExceptionWithCode "DbQueryFailed" ex
Exception handling
tryRail :: HasCallStack => IO a -> Rail a Source #
Safely execute an IO action that may throw exceptions, converting any exception into a Railway error.
This is a convenience wrapper around tryRailWithCode that uses the default
error code "UnhandledException". Use tryRailWithCode for a custom code with the same
generic message, or tryRailWithError to also customise the public message.
Example
>>>readConfig :: FilePath -> Rail String>>>readConfig path = tryRail (readFile path)>>>>>>pipeline :: FilePath -> Rail ()>>>pipeline filePath = do>>>content <- tryRail (readFile filePath)>>>validateName content <!> validateEmail content>>>saveToDb content
tryRailWithCode :: HasCallStack => (SomeException -> Text) -> IO a -> Rail a Source #
Like tryRail, but with a custom error code derived from the exception.
Wraps an IO action that may throw, converting any exception into a Railway
error whose code is produced by applying the given function to the caught
exception. The public message remains the generic default
("An unexpected error occurred"). Use tryRailWithError when you also
need a domain-specific public message.
Pass a constant function () when the code is fixed, or
inspect the exception to return different codes:const "MyCode"
>>>tryDb :: HasCallStack => IO a -> Rail a>>>tryDb = tryRailWithCode (const "DbError")>>>>>>tryHttp :: HasCallStack => IO a -> Rail a>>>tryHttp = tryRailWithCode $ \ex ->>>>if "timeout" `T.isInfixOf` T.pack (Ex.displayException ex)>>>then "HttpTimeout">>>else "HttpError"
Note: if you partially apply this function, add HasCallStack to the
wrapper's own signature so the call stack is captured at each call site
rather than frozen at the definition of the wrapper.
tryRailWithError :: (HasCallStack, HasErrorInfo e) => (SomeException -> e) -> IO a -> Rail a Source #
Like tryRailWithCode, but derives the error code and public message from
a HasErrorInfo value built from the caught exception.
The error-building function receives the SomeException that was thrown,
allowing the resulting error value to carry information extracted from the
exception itself. The HasErrorInfo instance then supplies errorCode as the
error code and errorPublicMessage as the public message.
Example
>>>{-# LANGUAGE DeriveDataTypeable #-}>>>>>>data DbError = QueryFailed Text | ConnectionLost>>>deriving (Show, Data)>>>>>>instance HasErrorInfo DbError where>>>errorPublicMessage (QueryFailed _) = "A database query failed">>>errorPublicMessage ConnectionLost = "Lost connection to the database">>>>>>safeQuery :: Rail [Row]>>>safeQuery = tryRailWithError (\_ -> ConnectionLost) runQuery>>>>>>-- Inspect the exception to choose the right constructor:>>>safeQuery' :: Rail [Row]>>>safeQuery' = tryRailWithError (QueryFailed . T.pack . Ex.displayException) runQuery
Note: add HasCallStack to any wrapper so the call stack is
captured at each call site rather than frozen at the wrapper's definition.
Operators
(<!>) :: forall (m :: Type -> Type). Monad m => RailT Failure m () -> RailT Failure m () -> RailT Failure m () infixl 5 Source #
Accumulates errors from two Railway validations.
This operator runs both validations regardless of whether the first one fails, collecting all errors before failing. This is the key operator for implementing Railway-Oriented Programming in Haskell.
The operator is left-associative ('infixl') with precedence 5, meaning:
v1 <!> v2 <!> v3
is interpreted as:
(v1 <!> v2) <!> v3
Example: Multiple Validations
Collect all validation errors at once:
>>>validateUser :: Rail ()>>>validateUser = do>>>validateName <!> validateEmail <!> validateAge>>>saveToDatabase>>>liftIO $ putStrLn "User created successfully!"
If all validations pass, execution continues to saveToDatabase.
If any validation fails, all errors are accumulated and the computation stops.
Behavior
The behavior depends on the results of both validations:
- Both succeed (Right, Right) → Continue execution
- First fails, second succeeds (Left, Right) → Stop with first error
- First succeeds, second fails (Right, Left) → Stop with second error
- Both fail (Left, Left) → Stop with combined errors (using
<>)
This allows Railway-Oriented Programming where you can express both paths (success and failure) explicitly in your code.
Use Cases
The <!> operator is ideal for:
- Form validation (check multiple fields, report all errors)
- Configuration validation (validate all settings, report all problems)
- Data pipeline validation (check all constraints, fail with all violations)
- Any scenario where you want "fail-fast" with "report-all-errors"
Error types
data ErrorSeverity Source #
Represents the severity level of an application error.
Severity levels are used to categorize errors by their importance and urgency. This information is useful for logging, monitoring, and error handling strategies.
Constructors
| Error | Indicates a standard error that occurred during the execution of a Railway. Standard errors are recoverable and do not require immediate attention. |
| Critical | Indicates a critical error that occurred during the execution of a Railway. Critical errors may require immediate attention and indicate serious problems that could affect system stability. |
Instances
data SomeErrorDetails Source #
An existential wrapper that can hold any value with ToJSON, Show, and Typeable
constraints.
This allows errorDetails and PublicErrorInfo to carry structured detail values
of any type, deferring JSON serialization until needed while preserving the ability
to recover the concrete type via cast.
Example:
>>>SomeErrorDetails ("usr_123" :: Text)>>>SomeErrorDetails (42 :: Int)>>>SomeErrorDetails (object ["field" .= ("email" :: Text)])
Constructors
| (ToJSON a, Show a, Typeable a) => SomeErrorDetails a |
Instances
| ToJSON SomeErrorDetails Source # | |
Defined in Monad.Rail.Error Methods toJSON :: SomeErrorDetails -> Value # toEncoding :: SomeErrorDetails -> Encoding # toJSONList :: [SomeErrorDetails] -> Value # toEncodingList :: [SomeErrorDetails] -> Encoding # omitField :: SomeErrorDetails -> Bool # | |
| Show SomeErrorDetails Source # | |
Defined in Monad.Rail.Error Methods showsPrec :: Int -> SomeErrorDetails -> ShowS # show :: SomeErrorDetails -> String # showList :: [SomeErrorDetails] -> ShowS # | |
data PublicErrorInfo Source #
Contains the public-facing information about an application error.
All fields in this record are safe to expose to end users and will be included in JSON serialization. This record is the only part of an error that flows into API responses.
See InternalErrorInfo for the complementary record holding sensitive diagnostic data.
Constructors
| PublicErrorInfo | |
Fields
| |
Instances
| ToJSON PublicErrorInfo Source # | |
Defined in Monad.Rail.Error Methods toJSON :: PublicErrorInfo -> Value # toEncoding :: PublicErrorInfo -> Encoding # toJSONList :: [PublicErrorInfo] -> Value # toEncodingList :: [PublicErrorInfo] -> Encoding # omitField :: PublicErrorInfo -> Bool # | |
| Show PublicErrorInfo Source # | |
Defined in Monad.Rail.Error Methods showsPrec :: Int -> PublicErrorInfo -> ShowS # show :: PublicErrorInfo -> String # showList :: [PublicErrorInfo] -> ShowS # | |
data InternalErrorInfo Source #
Contains internal diagnostic information about an application error.
This record implements ToJSON so it can be serialized for server-side logging
and monitoring. However, SomeError's ToJSON instance delegates only to
PublicErrorInfo, so none of these fields ever appear in public API responses.
See PublicErrorInfo for the complementary record holding user-facing data.
Constructors
| InternalErrorInfo | |
Fields
| |
Instances
| ToJSON InternalErrorInfo Source # | |
Defined in Monad.Rail.Error Methods toJSON :: InternalErrorInfo -> Value # toEncoding :: InternalErrorInfo -> Encoding # toJSONList :: [InternalErrorInfo] -> Value # toEncodingList :: [InternalErrorInfo] -> Encoding # omitField :: InternalErrorInfo -> Bool # | |
| Show InternalErrorInfo Source # | |
Defined in Monad.Rail.Error Methods showsPrec :: Int -> InternalErrorInfo -> ShowS # show :: InternalErrorInfo -> String # showList :: [InternalErrorInfo] -> ShowS # | |
class HasErrorInfo e where Source #
A type class for converting custom error types into serializable error information.
Implement errorPublicMessage — the only required method — to integrate any error type
with the Railway error system. All other methods have defaults and can be overridden
individually as needed.
Use publicErrorInfo and internalErrorInfo to assemble the corresponding records
from an instance.
Simple errors: implement errorPublicMessage only
Derive Data and implement errorPublicMessage. The errorCode default derives
the error code from the constructor name via toConstr:
>>>{-# LANGUAGE DeriveDataTypeable #-}>>>>>>data UserError = NameEmpty | EmailInvalid>>>deriving (Show, Data)>>>>>>instance HasErrorInfo UserError where>>>errorPublicMessage NameEmpty = "Name cannot be empty">>>errorPublicMessage EmailInvalid = "Email format is invalid">>>-- publicErrorInfo NameEmpty>>>-- = PublicErrorInfo { publicMessage = "Name cannot be empty">>>-- , code = "NameEmpty">>>-- , details = Nothing }
Full control: override any field method
Override individual methods when you need custom codes, details, or internal context. Methods you do not override keep their defaults:
>>>instance HasErrorInfo UserError where>>>errorPublicMessage NameEmpty = "Name cannot be empty">>>errorPublicMessage EmailInvalid = "Email format is invalid">>>>>>errorCode NameEmpty = "UserNameEmpty">>>errorCode EmailInvalid = "UserEmailInvalid">>>>>>errorSeverity _ = Critical>>>errorInternalMessage NameEmpty = Just "name field was empty string after trimming"
Note on errorCallStack and callStack
The assembled InternalErrorInfo record has a field named callStack. If both
InternalErrorInfo and Stack are imported unqualified, the name callStack
may be ambiguous. Qualify callStack to avoid ambiguity.
Minimal complete definition
Methods
errorPublicMessage :: e -> Text Source #
A human-readable message safe to display to end users. This is the only required method.
errorCode :: e -> Text Source #
A machine-readable error code. Defaults to the constructor name via toConstr.
Override when you need a code that differs from the constructor name.
Example: errorCode NameEmpty = "UserNameEmpty"
errorDetails :: e -> Maybe SomeErrorDetails Source #
Optional details safe to share with callers. Defaults to Nothing.
Wrap your value with SomeErrorDetails to store it:
>>>errorDetails MyError = Just (SomeErrorDetails (object ["field" .= ("email" :: Text)]))
errorSeverity :: e -> ErrorSeverity Source #
Severity level of the error. Defaults to Error.
errorInternalMessage :: e -> Maybe Text Source #
An optional technical message for logs, safe to contain sensitive details.
Defaults to Nothing.
errorException :: e -> Maybe SomeException Source #
An optional underlying runtime exception. Defaults to Nothing.
errorCallStack :: e -> Maybe CallStack Source #
The Haskell call stack at the point the error was constructed. Defaults to Nothing.
Populate by adding HasCallStack to the function that builds the error
and passing Just GHC.Stack.callStack.
Instances
| HasErrorInfo SomeError Source # | |
Defined in Monad.Rail.Error Methods errorPublicMessage :: SomeError -> Text Source # errorCode :: SomeError -> Text Source # errorDetails :: SomeError -> Maybe SomeErrorDetails Source # errorSeverity :: SomeError -> ErrorSeverity Source # errorInternalMessage :: SomeError -> Maybe Text Source # | |
| HasErrorInfo UnhandledException Source # | |
Defined in Monad.Rail.Error Methods errorPublicMessage :: UnhandledException -> Text Source # errorCode :: UnhandledException -> Text Source # errorDetails :: UnhandledException -> Maybe SomeErrorDetails Source # errorSeverity :: UnhandledException -> ErrorSeverity Source # errorInternalMessage :: UnhandledException -> Maybe Text Source # errorException :: UnhandledException -> Maybe SomeException Source # errorCallStack :: UnhandledException -> Maybe CallStack Source # | |
publicErrorInfo :: HasErrorInfo e => e -> PublicErrorInfo Source #
Assembles a PublicErrorInfo from a HasErrorInfo instance.
This is the canonical way to obtain the public-facing error record for logging or serialization. The result contains only data safe to expose to end users.
internalErrorInfo :: HasErrorInfo e => e -> InternalErrorInfo Source #
Assembles an InternalErrorInfo from a HasErrorInfo instance.
This is the canonical way to obtain the internal diagnostic record for server-side logging and monitoring. Never include this in API responses.
A wrapper type that can hold any application error implementing HasErrorInfo.
This existential type allows you to combine errors of different types in the same
Railway computation. It uses existential quantification to hide the concrete error type
while preserving the ability to extract error information via the HasErrorInfo
interface. The Typeable constraint allows recovery of the original error type
via cast when needed.
This is particularly useful when you have multiple error sources (e.g., validation errors, database errors, network errors) and want to combine them in a single computation.
JSON serialization
SomeError's ToJSON instance serializes only the PublicErrorInfo fields.
InternalErrorInfo is intentionally excluded so that sensitive diagnostic data
(internal messages, call stacks, exceptions) is never accidentally exposed in API responses.
Use internalErrorInfo directly if you need to serialize that data for server-side logging.
Example
>>>data UserError = NameEmpty>>>data DatabaseError = ConnectionFailed>>>>>>instance HasErrorInfo UserError where { ... }>>>instance HasErrorInfo DatabaseError where { ... }>>>>>>validate :: Rail ()>>>validate = do>>>throwError NameEmpty -- User error>>>throwError ConnectionFailed -- Database error
Constructors
| (HasErrorInfo e, Show e, Typeable e) => SomeError e |
Instances
| ToJSON SomeError Source # | |
| Show SomeError Source # | |
| HasErrorInfo SomeError Source # | |
Defined in Monad.Rail.Error Methods errorPublicMessage :: SomeError -> Text Source # errorCode :: SomeError -> Text Source # errorDetails :: SomeError -> Maybe SomeErrorDetails Source # errorSeverity :: SomeError -> ErrorSeverity Source # errorInternalMessage :: SomeError -> Maybe Text Source # | |
data UnhandledException Source #
Wrapper for unhandled exceptions that can be used as an error type.
This type captures a SomeException thrown in IO and makes it
compatible with the Railway error system via its HasErrorInfo instance.
It is the error type produced by tryRail when an IO action throws.
The publicMessage of the assembled PublicErrorInfo is intentionally generic so
that internal details are never accidentally exposed to end users. The original
exception is stored in the exception field of InternalErrorInfo for logging
and debugging.
unhandledCode lets you assign a domain-specific error code when you catch
exceptions manually, rather than relying on the default "UnhandledException":
>>>import qualified Control.Exception as E>>>>>>safeQuery :: Rail Row>>>safeQuery = do>>>result <- liftIO $ E.try runQuery>>>case result of>>>Right row -> pure row>>>Left ex -> throwUnhandledExceptionWithCode "DbQueryFailed" ex
Or use throwUnhandledException when the default code is sufficient:
>>>safeQuery :: Rail Row>>>safeQuery = do>>>result <- liftIO $ E.try runQuery>>>case result of>>>Right row -> pure row>>>Left ex -> throwUnhandledException ex
When using tryRail, the code defaults to "UnhandledException" and the
call stack is captured automatically at the call site.
Constructors
| UnhandledException | |
Fields
| |
Instances
| Show UnhandledException Source # | |
Defined in Monad.Rail.Error Methods showsPrec :: Int -> UnhandledException -> ShowS # show :: UnhandledException -> String # showList :: [UnhandledException] -> ShowS # | |
| HasErrorInfo UnhandledException Source # | |
Defined in Monad.Rail.Error Methods errorPublicMessage :: UnhandledException -> Text Source # errorCode :: UnhandledException -> Text Source # errorDetails :: UnhandledException -> Maybe SomeErrorDetails Source # errorSeverity :: UnhandledException -> ErrorSeverity Source # errorInternalMessage :: UnhandledException -> Maybe Text Source # errorException :: UnhandledException -> Maybe SomeException Source # errorCallStack :: UnhandledException -> Maybe CallStack Source # | |