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

feat: make error handling configurable #592

Merged
merged 28 commits into from
Jan 8, 2022
Merged

feat: make error handling configurable #592

merged 28 commits into from
Jan 8, 2022

Conversation

routmoute
Copy link
Contributor

add Simple ErrorService possibility

See Issue #591

@crookse
Copy link
Member

crookse commented Dec 31, 2021

hey @routmoute ! i like the idea behind this, but the current implementation is part of Drash's "core" code as opposed to being a separate service that can be plugged in through the services config. what i mean by that is:

This Pull Request

const server = new Server({
  protocol: "http",
  hostname: "localhost",
  port: 3000,
  resources: [],
  error_service: new MyErrorService() // <--- should be in services config
});

Expected Implementation

const server = new Drash.Server({
  protocol: "http",
  hostname: "localhost",
  port: 3000,
  resources: [],
  services: [ new MyErrorService() ] // <--- see here
});

reason we don't introduce much code into Drash's core is because we don't know for sure that developers want "built-in" features. the way Drash is currently built is so that developers are free to create their own services as well as a custom error service that meets their needs. however, we do feel that Drash should have an error handling layer that can be used easily, which is what you're introducing in this pull request. also, thank you for writing the documentation! much appreciated :).

so, all this being said, could you refactor your error service so that it can be plugged into the services config? if you run into any issues, feel free to message back and we'll respond asap. another option is we can merge your pull request in a feature branch, refactor some code, make a pull request to that feature branch, and have you review it. either option is fine with us, but we'd love your thoughts. let us know! thanks again!

@routmoute
Copy link
Contributor Author

I think I see, don't create another class but add the runOnError function directly on the services. I will refactor that, it will be better, thank you.

Revert "ErrorService tests: add assert status"

This reverts commit 3bc90f3.

Revert "ErrorService: change function name"

This reverts commit 21fdacb.

Revert "add ErrorService tests"

This reverts commit eebd9d6.

Revert "add ErrorService feature"

This reverts commit 58237b3.
@crookse
Copy link
Member

crookse commented Dec 31, 2021

hey @routmoute, so i had a talk with the core team and basically we think drash should have a built-in error handler and not an "error service". so your approach was mostly correct. sorry for misguiding you. the end goal would be a Drash.ErrorHandler class or something similar. that class would be instantiated and executed in server.ts' getHandler() method inside the catch block.

@routmoute
Copy link
Contributor Author

routmoute commented Jan 1, 2022

Arf, but, I think the second approach is better because developers can work the errors differently if they are thrown on a particular method, path and then on the server.
On the first approach, we can only work at the server level.

@crookse
Copy link
Member

crookse commented Jan 1, 2022

true. maybe we stick with the error service for now and see how that goes and if it needs to be changed we can always do that later and make another release

@routmoute
Copy link
Contributor Author

routmoute commented Jan 1, 2022

Actualy, I've just add a function runOnError() in Drash.Service class, this function is called in server.ts getHandler() catch block.

@crookse
Copy link
Member

crookse commented Jan 2, 2022

hey @routmoute , so i pulled down your branch locally and tested it -- keeping developer user experience and maintainability in mind -- and during my testing, i realized using an "error service" will get us back to what we had in v1 (and we don't want to go back to what we had in v1).

in v1, the error handling got really messy and was hard to maintain because we had to run middleware during the error handling process. every time we came back to the code, we forgot how it worked and there were edge cases with it that we kept finding. in your branch, you basically have what we had in v1 (https://github.com/routmoute/drash/blob/errorService/src/http/server.ts#L354-L385). your code isn't wrong. it's exactly what's needed to have an "error service" in drash, but it's also something that we removed when going from v1 to v2.

@Guergeiro suggested that there be an error handling layer in drash and that it be a built-in feature that can be extended by developers and i disagreed because i thought having an "error service" would be best suited for configurability. after testing your code locally, i feel that @Guergeiro is correct and we should not use an "error service," but instead have an error handling layer that can be extended and configured by developers (if they choose to do so).

@crookse
Copy link
Member

crookse commented Jan 2, 2022

feel free to hop in discord to discuss btw. we're pretty active in it and respond faster there. here's the link in case you're interested: https://discord.gg/DmjhWJt5

Revert "refactoring serviceError"

This reverts commit 2ea504c.

Revert "oops: clean"

This reverts commit ab734e6.

Revert "add  basic ErrorsToJsonService with test"

This reverts commit 16f603a.

Revert "rename service_error to service_runOnError tests"

This reverts commit fcd6765.

Revert "add runBeforeResource() and runAfterResource() errors tests"

This reverts commit 7a2fb22.
@routmoute
Copy link
Contributor Author

I will retry something

@crookse crookse linked an issue Jan 2, 2022 that may be closed by this pull request
3 tasks
@crookse crookse mentioned this pull request Jan 2, 2022
@routmoute
Copy link
Contributor Author

I've created an ExceptionLayer class can be exetended and declared on server 'exception' var.

what do you think about that ?


if (exception) {
try {
await exception.catch(e, originalRequest, response);
Copy link
Member

Choose a reason for hiding this comment

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

this isn't documented in the codebase, but we try to instantiate things during compile time so that

  1. the user doesn't have to do it
  2. saves the server from having to build a new object for every request

could you make it so that the exception doesn't have to be instantiated by the user? that's the only feedback i have. id like to get thoughts on the naming of the class from @ebebbington and @Guergeiro

as far as implementation, LGTM. thanks @routmoute !

Copy link
Member

Choose a reason for hiding this comment

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

You mean the new Drash.ExceptionLayer().catch(...)?

Copy link
Member

Choose a reason for hiding this comment

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

yea, but we instantiate it once and reuse it just like we do with resources

Copy link
Member

Choose a reason for hiding this comment

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

I agree with it. Let's avoid generating garbage for the garbage collector to collect 👍🏿

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, last commit instantiate ExceptionLayer on server.ts

src/http/server.ts Outdated Show resolved Hide resolved
@@ -141,6 +141,7 @@ export interface IServerOptions {
protocol: "http" | "https";
resources: typeof Resource[];
services?: Service[];
exception?: ExceptionLayer;
Copy link
Member

Choose a reason for hiding this comment

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

This should be the IExceptionLayer. With that, I can create my own exception layer that does not extend the Drash layer, but rather completely overrides it.

A simple test with this would be a nice touch 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in last commit, I use typeof ExceptionLayer (like 'typeof Resource').

I added a test, I don't know if it's that you want.

Copy link
Member

Choose a reason for hiding this comment

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

@Guergeiro , i tested using IExceptionLayer as the expected type in the TS playground. seems to require new MyExceptionClass() to be used. see playground here.

Copy link
Member

Choose a reason for hiding this comment

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

Yes. It's supposed to. Here's a link with the same code, but the correct options signature.

Copy link
Member

Choose a reason for hiding this comment

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

ahh constructor. ok

Copy link
Member

@ebebbington ebebbington left a comment

Choose a reason for hiding this comment

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

Also in regards to #592 (comment), i think eric means, instantiate the exception class in the server constructor:

#exception: CustomException|null = null
constructor(...) {
  if (options.exception) {
    this.#exception = new options.exception()
  }
}
...

const exception = this.#exception
return function () {
  exception.catch(...)
}

But that wouldn't be a problem if the catch method was static anyways

src/http/server.ts Outdated Show resolved Hide resolved
src/http/server.ts Outdated Show resolved Hide resolved
@crookse crookse changed the title add ErrorService feat: make error handling configurable Jan 4, 2022
@crookse crookse added the Type: Minor Merging this pull request results in a minor version increment label Jan 4, 2022
Copy link
Member

@Guergeiro Guergeiro left a comment

Choose a reason for hiding this comment

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

In general, it's good for me. Logic wise.

This is some stuff I would prefer to simplify stuff. Basically, if no option for an ErrorHandler is passed, just automatically assign a default one. Then we can be assured it exists, otherwise it would fail in server setup.

Comment on lines 124 to 126
if (options.error_handler) {
this.#error_handler = new options.error_handler();
}
Copy link
Member

Choose a reason for hiding this comment

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

No need for condition. Always apply the default if none exist.

this.#error_handler = new (options.error_handler() || Drash.ErrorHandler);

Copy link
Member

@@ -173,6 +182,9 @@ export class Server {
#getHandler(): (r: Request, connInfo: ConnInfo) => Promise<Response> {
const resources = this.#resources;
const serverServices = this.#options.services ?? [];
const errorHandler = this.#error_handler;
const defaultErrorHandler = new Drash.ErrorHandler();
Copy link
Member

Choose a reason for hiding this comment

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

No need for this.

Comment on lines 343 to 347
if (errorHandler) {
try {
await errorHandler.catch(e, originalRequest, response);
} catch (e) {
await defaultErrorHandler.catch(e, originalRequest, response);
}
} else {
await defaultErrorHandler.catch(e, originalRequest, response);
}
return new Response(e.stack, {
status: e.code,
headers: response.headers,
});

return new Response(response.body, response);
Copy link
Member

Choose a reason for hiding this comment

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

try {
  // stuff
} catch (e) {
  await this.#error_handler.catch(e, originalRequest, response);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we use defaultErrorHandler when this.#error_handler.catch() or errorHandler.catch() (it's the same const errorHandler = this.#error_handler;) thrown an error

src/http/error_handler.ts Outdated Show resolved Hide resolved
}
response.json({ error: error.message });
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

why error is Error here and is HttpError in default ErrorHandler ?
if is simple Error thrown, this ErrorHandler return 200 ?

Copy link
Contributor Author

@routmoute routmoute Jan 6, 2022

Choose a reason for hiding this comment

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

I think use Error everywhere and set status to 500 if not an HttpError, see e805f2a and bcb8f18, it's better for me.

the answer to the second question is yes, it's bad. fixed on this commits

Copy link
Member

Choose a reason for hiding this comment

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

So there's two approaches here:

  1. Catch on every error. This means the server will never stop working, regardless of the error. If an error is thrown just send 500.
  2. Catch only HttpError. This can make the server crash if, for instance, a database errors and the developer doesn't worry about surrounding external calls with try...catch.

My opinion is that option 1 would be the most interesting. Ideally the server never goes down. If an error occurs that was not expected, and Internal Server Error Exception shall be sent to the user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same opinion, that's exactly what I did.

Copy link
Member

@Guergeiro Guergeiro left a comment

Choose a reason for hiding this comment

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

I approve the architecture of this PR. Good job for me.

@crookse crookse merged commit fd1fffc into drashland:main Jan 8, 2022
@crookse
Copy link
Member

crookse commented Jan 8, 2022

thank you @routmoute :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Type: Minor Merging this pull request results in a minor version increment
Development

Successfully merging this pull request may close these issues.

ErrorService to handle HttpErrors / introduce custom error handling
4 participants