Skip to content
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

[rfc] [draft] Tailcall Error Handling #2138

Closed
tusharmath opened this issue Jun 8, 2024 · 0 comments
Closed

[rfc] [draft] Tailcall Error Handling #2138

tusharmath opened this issue Jun 8, 2024 · 0 comments

Comments

@tusharmath
Copy link
Contributor

In the Tailcall application, errors can arise during both the validation phase and at runtime. These errors need to be displayed in various formats: on the command-line interface (CLI) and as a GraphQL response. The following RFC provides detailed guidance on how to architect and design our error management system.

Error Display

There are several ways we want to display errors to the user:

  1. On the Console

    For this purpose, we should implement a trait for errors:

    trait DisplayConsole {
        fn format(&self, fmt: impl Writer);
    }

    If the error implements this trait, it is console-friendly and can feature highlighted or colored text.

  2. In the errors Field of a GraphQL Response

    This is already supported by the trait async_graphql::ErrorExtensions. Any error that implements this trait will support richer error messages.

  3. In an External Telemetry System (To Be Considered)

    We could implement a separate trait for this:

    trait DisplayTelemetry {
        fn format(&self, fmt: impl Writer);
    }

Untyped Error

We can define a general-purpose container for errors as follows:

struct Error {
    message: String,
    description: Option<String>,
    suggestion: Option<String>,
    trace: Vec<String>,
    cause: Vec<Error>
}

The issue with this implementation is that it represents unchecked exceptions, which cannot be processed later. For example, if it's an HTTP error and we wish to retry, the error information would be entangled within the text, making further processing difficult.

The advantage of a general-purpose container is that we can easily implement the above display traits once and not worry about reimplementing them for every type of error.

Typed Errors

Ideally, each module should expose its own errors, allowing us to scope the errors to specific modules. For example, all HTTP errors would reside in http::error.rs, and all blueprint errors in blueprint::error.rs. This design ensures reusability, as we can issue the same error multiple times across our code without losing details, thanks to compiler enforcement.

However, there is a disadvantage. When a module uses multiple other modules, the errors it produces could be from any of them. This leaves us with two options: either perform a conversion from one type to another or create a wrapper type for the error.

Creating a wrapper is safer and more "correct," but it can quickly become unmanageable. Additionally, when refactoring code and changing modules, we would need to restructure the errors, which can be a significant challenge.

Error Monolith

An alternative is to create a large enum of errors that can be used throughout our application. This approach avoids the need for conversions but may result in multiple duplicate errors. To address this, we can group errors not by module but by logical separation.

Tooling to Simplify Error Management

There are tools like derive_more that can facilitate error transformation. Additionally, the ? operator in Rust automatically converts errors using into, easing the error handling process.

By considering these approaches, we can effectively manage errors in the Tailcall application, ensuring clarity and reusability while minimizing the challenges associated with error handling.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant