Improving my error handling using custom error types

I want to improve my error handling. If I understand correctly a part of the philosophy of Rust is that you have to write a bit more code then in another language because you need to check for errors instead of spamming .unwrap() everywhere, which is more work but finally results in a solid code.

So I want to make use of that.

Because of this instead of simply returning Result<(), String> I want to start using custom error types.

I have a application consisting of different parts:

  • A basic HTTP server*.
  • A module communicating with Exchange API's (Binance, Kraken).
  • Routes related to my HTTP Server acting as custom API endpoints to my own application.
  • A module interpreting Python scripts containing trading algorithms using PyO3.

*I think I won't be using the Error trait for 'intended errors'. E.g if the user requests /this-page-that-not-exists the server will still return Ok() containing a 404 page. This is okay?

How should I approach this? Currently I am thinking about making the following enum in my main.rs:

// Custom Error
#[derive(Debug)]
enum ApplicationError {
    ExchangeAPIError(String),
    ServerError(String),
    ParseError(String),
    AlgorithmError(String),
}

impl std::error::Error for ApplicationError {}

impl std::fmt::Display for ApplicationError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ApplicationError::ExchangeAPIError(error_msg) => write!(f, "[Error] ExchangeAPI: {}", error_msg),
            ApplicationError::ServerError(error_msg) => write!(f, "[Error] Server: {}", error_msg),
            ApplicationError::ParseError(error_msg) => write!(f, "[Error] ParseError: {}", error_msg),
            ApplicationError::AlgorithmError(error_msg) => write!(f, "[Error] AlgorithmError: {}", error_msg),
        }
    }
}

My functions can then just return Result<(), ApplicationError>.

Is this a good approach or should I rather make different Enums? e.g:

#[derive(Debug)]
enum ServerError {
    StreamError(String),
    ParseError(String),
    //...
    // List of all the things that could go wrong in the server.
}
#[derive(Debug)]
enum ExchangeAPIError {
    APIError(String),
    ParseError(String),
    //...
    // Lust of all the things that could go wrong in the api.
}

And I would also like to return the file in which the error occurred. Is that possible? Should I make a extra parameter in my enums and manually write in which file it occurred when I call the error?

Other things to consider?

Edit:
I am currently also implementing error types from serde_json etc like this:

impl std::error::Error for ApplicationError {}
impl From<sha1::digest::InvalidLength> for ApplicationError {
    fn from(err: sha1::digest::InvalidLength) -> Self {
        ApplicationError::ExchangeAPIError(err.to_string())
    }
}

impl From<serde_json::Error> for ApplicationError {
    fn from(err: serde_json::Error) -> Self {
        ApplicationError::ParseError(err.to_string())
    }
}

So I can use ? in my code.

Edit2:
When implementing the custom error-types from differemt crates I noticed each module has his own error type:

impl From<sha1::digest::InvalidLength> for ApplicationError
impl From<serde_json::Error> for ApplicationError 
impl From<reqwest::Error> for ApplicationError
// ...

So I am guessing the correct approach would not be to make just one error type for my whole application. But make different error types for each part in my app?

But on the other hand: Only using one error type makes it easier for cases where one part of my app calls a function from another part.