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

Handling errors outside of CRUD handlers #272

Open
joeshaw opened this issue Nov 21, 2016 · 6 comments
Open

Handling errors outside of CRUD handlers #272

joeshaw opened this issue Nov 21, 2016 · 6 comments

Comments

@joeshaw
Copy link

joeshaw commented Nov 21, 2016

I'm new to api2go and I'm really struggling to figure out how best to handle errors outside of the standard CRUD handling methods on resources.

For instance, if I have middleware, I'd like to be able to intercept requests but still return nicely formatted error JSON to clients. However, there doesn't appear to be any exported way to do that in the library.

I'd expect to do something like api2go.NewHTTPError() and then call a method to write that to the http.ResponseWriter. There is a marshalHTTPError function that would be nice to use for this, but it's private. There is also an api.handleError() method that could work, if it were exported (though I am not sure I necessarily have access to an *API from inside my middleware)

How do real-world middleware (for instance, ones that do auth) handle this? I couldn't find examples.

@wwwdata
Copy link
Member

wwwdata commented Nov 23, 2016

The middleware is running before our routing and resources. The api2go.NewHTTPError() is currently a helper for our internal routes/api stuff.

But you can just use the Error and HTTPError structs and json.Marshal them by yourself. We are doing nothing else. The middleware only has access to the context and not the *API itself.

@joeshaw
Copy link
Author

joeshaw commented Nov 23, 2016

Ok, cool, thanks for the info.

I am doing the json.Marshal locally now, but it feels a little awkward. Would you consider exposing the marshalHTTPError function to make this a simpler thing to do?

My thinking behind this is that I'm of the opinion that a JSON API should always return JSON objects, even in the case of error. The extreme example is that even a 404 error for a wrong API endpoint should return a JSON error object. A more common example (and the one I am dealing with) is handling a panic in an HTTP handler... I want to make sure we return a 500 with an error payload.

The jsonapi.org spec is not clear on whether this is required or not, but I think it's a best practice. (I am going to ask on their repo for their opinion on this as well.)

@joeshaw
Copy link
Author

joeshaw commented Nov 23, 2016

json-api/json-api#1122

@wwwdata
Copy link
Member

wwwdata commented Nov 23, 2016

hm, yes I guess we can make that public, right @sharpner

What I also noticed is that marshalHTTPError converts []byte to a string only to be converted back to a []byte when it's used in the api.go file 😂 so we should let it be a []byte like one would expect.

@bf4
Copy link

bf4 commented Dec 7, 2016

@joeshaw re auth, you can treat it like a sessions resource

If there's no resource at the endpoint, then the response shouldn't have errors. I'm imaging a request and response that only have meta.

Could you provide an example request and what the library is returning and what you'd like it to?

@joeshaw
Copy link
Author

joeshaw commented Dec 7, 2016

The way I came to it was that I wrote middleware that caught panics and returns a 500 error. If you are calling a resource and it panics, you want a JSON response with an error response. This is what it ended up looking like:

func NewPanicHandler(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		defer func() {
			p := recover()
			if p == nil {
				return
			}

			var err error
			var ok bool
			if err, ok = p.(error); !ok {
				err = fmt.Errorf("%v", p)
			}

			e := api2go.NewHTTPError(
				err,
				err.Error(),
				http.StatusInternalServerError,
			)

			// Work around lack of exported error handling in api2go
			// https://github.com/manyminds/api2go/issues/272
			e.Errors = []api2go.Error{
				{
					Title:  err.Error(),
					Status: strconv.Itoa(http.StatusInternalServerError),
				},
			}

			rw.WriteHeader(http.StatusInternalServerError)
			enc := json.NewEncoder(rw)
			enc.Encode(e)
		}()

		handler.ServeHTTP(rw, req)
	})
}

Ideally, after constructing the HTTPError I'd like to be able to do something like api2go.MarshalHTTPError(rw, e) and skip setting e.Errors, encoding the JSON, and writing it to the http.ResponseWriter.

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

3 participants