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

Editoast error management #237

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open

Editoast error management #237

wants to merge 27 commits into from

Conversation

leovalais
Copy link
Contributor

Settling on this topic is necessary in order to go forward with the split in crates.

@leovalais leovalais marked this pull request as ready for review September 18, 2024 09:27
Copy link
Contributor

@woshilapin woshilapin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a first round, hope you like it. I'm really worried by the complexity of this macro and if there would be some other solution.

Signed-off-by: Leo Valais <[email protected]>
Signed-off-by: Leo Valais <[email protected]>
@leovalais
Copy link
Contributor Author

Thanks for all the corrections! I'll get back to this document when I have some time, hopefully in the next iteration :)

@leovalais
Copy link
Contributor Author

I've added a bit of content, especially an implementation plan and details about how to forward Core errors.

Signed-off-by: Leo Valais <[email protected]>
@leovalais
Copy link
Contributor Author

Thanks for the corrections!

Copy link
Contributor

@woshilapin woshilapin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice introducing some new sections about "Issues of the old system" or "Implementation plan".

Signed-off-by: Leo Valais <[email protected]>
@leovalais
Copy link
Contributor Author

This looks mergeable as it is to me.

@Khoyo: we discussed about Core errors with @flomonster yesterday and settled on wrapping the errors, but not parsing them. Lmk if it's ok.

@Khoyo @woshilapin: do I drop the "Rejected Ideas" section or not? (Personally I don't care.)

A final round of review would be much appreciated 🙏

Signed-off-by: Leo Valais <[email protected]>
Copy link
Contributor

@woshilapin woshilapin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, made a few more comments, I believe most of them are not fundamental so should not move things too much.

And for your questions, I'd prefer to keep "Rejected ideas", it's a good thing to keep these around so we avoid to reinvent things we might have explore in the past.

- Create a proc-macro `derive(ViewError)` which interfaces with `derive(thiserror::Error)`.
- The `context` is empty by default but can be provided by the `impl ViewError`. The macro is also able to take context providers.
- `ViewError`'s `#[source]`, `#[from]`, `source` and `backtrace` fields are never serialized, unless explicitly provided. This shouldn't be the case as it exposes editoast internals at the API level.
- `derive(ViewError)` generates the `impl utoipa::ToSchema for T`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all our errors implements EditoastError, can't we not do it with something like impl<T: EditoastError> utoipa::ToSchema for T once and for all (and avoid to do that in the macro, and therefore, avoid also a lot of code generation?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there's a misunderstanding or just a typo here. All errors returned by endpoints implement trait ViewError (trait EditoastError is bound to disappear).

The problem is that impl ViewError errors are not serialized in the response. They are converted into what is currently a struct InternalError which has its own serialization. So we can't just #[derive(ToSchema)] on a trait ViewError since the generated schema is inferred from the "would-be" serialization of the type.

Instead, we want MyViewError::schema() to return the schema of the error, but following the structure of InternalError!

For example:

#[derive(ToSchema)]
struct MyViewError { cause: String }

MyViewError::schema()

shouldn't be:

type MyViewError = { cause: string };

but instead:

type MyViewError = {
	status: 500,
	error_type: 'editoast:MyViewError',
	context: { cause: string },
	message: string
}

We can't just impl<T: EditoastError> utoipa::ToSchema for T: the function schema() would build its result based on which information? (We don't necessarily have to generate the whole ToSchema, but we do need to generate enough information at compile-time to build the schema. Rust doesn't have introspection.)

I realize all that is worth a section on this document to avoid confusion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-off-by: Leo Valais <[email protected]>
Signed-off-by: Leo Valais <[email protected]>
Signed-off-by: Leo Valais <[email protected]>
@@ -45,7 +45,7 @@ weight: 80
- Create a proc-macro `derive(ViewError)` which interfaces with `derive(thiserror::Error)`.
- The `context` is empty by default but can be provided by the `impl ViewError`. The macro is also able to take context providers.
- `ViewError`'s `#[source]`, `#[from]`, `source` and `backtrace` fields are never serialized, unless explicitly provided. This shouldn't be the case as it exposes editoast internals at the API level.
- `derive(ViewError)` generates the `impl utoipa::ToSchema for T`.
- `impl utoipa::IntoResponses for T` (may be generated or inferred)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Isn't it this?

Suggested change
- `impl utoipa::IntoResponses for T` (may be generated or inferred)
- `impl<T: ViewError> utoipa::IntoResponses for T` (may be generated or inferred)

}
```

making `serde` basically useless for our errors. On top of that, since by design the derive macros of `utoipa` (`ToSchema`, `IntoResponse` especially) interpret the type structure like `serde` would do, we can't rely on them either. Therefore we need a custom derive-macro to convey the structural information of the type at runtime.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we still want serde, just not the automatic derive.

Suggested change
making `serde` basically useless for our errors. On top of that, since by design the derive macros of `utoipa` (`ToSchema`, `IntoResponse` especially) interpret the type structure like `serde` would do, we can't rely on them either. Therefore we need a custom derive-macro to convey the structural information of the type at runtime.
making `derive(serde::Serialize)` basically useless for our errors. ...

Here are some ideas for not codegen the serialize implementation.


Another solution would be to shift our error definition paradigm and orient ourselves to a system without code generation (probably using a combination of traits and builders). This would imply to rewrite all our errors and their handling, which is costly 🤑🫰. We'd also have to get rid of the convenience of `thiserror`, a huge loss in terms of ergonomics. And that would break the consistency with the other sub-crates of editoast.

The macro doesn't even have to be overly complex. The `trait ViewError` could be responsible of translating the static type definition into an associated constant, which would be used to compute data produced at runtime. (Ie. `impl axum::IntoResponse for T: ViewError` and `impl utoipa::IntoResponses for T: ViewError`.) This would reduce the amount of generated code, at the expense of more complex data manaipulation at runtime.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:

Suggested change
The macro doesn't even have to be overly complex. The `trait ViewError` could be responsible of translating the static type definition into an associated constant, which would be used to compute data produced at runtime. (Ie. `impl axum::IntoResponse for T: ViewError` and `impl utoipa::IntoResponses for T: ViewError`.) This would reduce the amount of generated code, at the expense of more complex data manaipulation at runtime.
The macro doesn't even have to be overly complex. The `trait ViewError` could be responsible of translating the static type definition into an associated constant, which would be used to compute data produced at runtime. (Ie. `impl axum::IntoResponse for T: ViewError` and `impl utoipa::IntoResponses for T: ViewError`.) This would reduce the amount of generated code, at the expense of more complex data manipulation at runtime.


The Core service is a bit special as it already returns errors with the common OSRD format. Since editoast doesn't really need to parse and recover from Core errors[^2], we don't need an exhaustive list of them. We still need to differentiate them from other editoast errors (let's not start tossing `InternalError` around again…) and to provide a key for the frontend to translate them.

[^2]: Core errors will likely never be recoverable from either editoast or the frontend. For the latter, such errors are likely to be displayed as a generic "Internal error" message. So no translation is needed. For these reasons, we don't need to them in the OpenAPI. However, if in the future, we want editoast to actually parse Core errors, ensuring a proper mapping will still be possible.
Copy link
Contributor

@SergeCroise SergeCroise Jan 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[^2]: Core errors will likely never be recoverable from either editoast or the frontend. For the latter, such errors are likely to be displayed as a generic "Internal error" message. So no translation is needed. For these reasons, we don't need to them in the OpenAPI. However, if in the future, we want editoast to actually parse Core errors, ensuring a proper mapping will still be possible.
[^2]: Core errors will likely never be recoverable from either editoast or the frontend. For the latter, such errors are likely to be displayed as a generic "Internal error" message. So no translation is needed. For these reasons, we don't need to pass them in the OpenAPI. However, if in the future, we want editoast to actually parse Core errors, ensuring a proper mapping will still be possible.

(j'avoue que ne n'ai pas trop compris la phrase)

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

Successfully merging this pull request may close these issues.

4 participants