Closed brandur closed 4 days ago
@bgentry This isn't done, but I wanted to make sure you're okay with the broad design before going any further.
This is basically how I've set up other API projects in Go. There's definitely a little more abstraction compared to the raw handlers, but IMO has huge benefits for writing much more succinct implementations and testability. It's also potentially introspectable in case we ever want to build an OpenAPI spec for anything like that (in that it's possible to use reflect to iterate over the endpoint/request/response structs and extract information about them).
@bgentry Okay, great! Let me add a little more testing and docs for the core APU infrastructure and then I'll kick it back to you.
I might do the rest of the endpoint conversion in a different PR since with the tests especially, it'll be a big diff unto itself.
@bgentry Okay, added docs and tests for all the core infrastructure. I tried to write a few more API endpoint tests too, but found I needed the changes in https://github.com/riverqueue/river/pull/402 for anything queue-related, so going to push those for now. Mind taking another look?
Thanks for the review!
Put in a lightweight API framework for River UI's Go code to make writing endpoints more succinct and better enable testing. Endpoints are defined as a type that embeds a struct declaring their request and response types along with metadata that declares their path and success status code:
The request/response types know to unmarshal and marshal themselves from to JSON, or encapsulate any path/query parameters they need to capture:
Endpoints define an
Execute
function that takes a request struct and returns a response struct along with a possible error:This makes the endpoints a lot easier to write because serialization code gets removed, and errors can be returned succinctly according to normal Go practices instead of each one having to be handled in a custom way and be a liability in case of a forgotten
return
after writing it back in the response. The underlying API framework takes care of writing back errors that should be user-facing (anything in the newly addedapierror
package) or logging an internal error and return a generic message. Context deadline code also gets pushed down.The newly added test suite shows that the
Execute
shape also makes tests easier and more succinct to write because structs can be sent and read directly without having to go through a JSON/HTTP layer, and errors can be handled directly without having to worry about them being converted to a server error, which makes debugging broken tests a lot easier.We also add a suite of integration-level tests that test each endpoint through the entire HTTP/JSON stack to make sure that everything works at that level. This suite is written much more sparingly -- one test fer endpoint -- because the vast majority of endpoint tests should be written in the handler-level suite for the reasons mentioned above.