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

Solve code duplication in server with generics #241

Open
sietseringers opened this issue Sep 8, 2022 · 0 comments
Open

Solve code duplication in server with generics #241

sietseringers opened this issue Sep 8, 2022 · 0 comments

Comments

@sietseringers
Copy link
Member

The various packages under the server package have code duplication: a lot of HTTP handler functions unmarshal their input and then marshal their output in exactly the same way. This could be solved using Go generics, with a generic helper function that deals with the (un)marshaling:

// GenericHandlerFunc is a HTTP handler function that takes typed input and output,
// which is JSON-(un)marshaled automatically by WrapGenericHandler.
type GenericHandlerFunc[InType, OutType any] func(
	input *InType, w http.ResponseWriter, r *http.Request,
) (OutType, *server.Error, error)

// WrapGenericHandler wraps a GenericHandlerFunc into an ordinary http.HandlerFunc.
func WrapGenericHandler[InType, OutType any](handler GenericHandlerFunc[InType, OutType]) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		input := new(InType)
		if err := server.ParseBody(r, input); err != nil {
			server.WriteError(w, server.ErrorInvalidRequest, err.Error())
			return
		}

		output, servErr, err := handler(input, w, r)

		if err != nil {
			if servErr == nil {
				servErr = &server.ErrorInternal
			}
			// Logging of the error should be done in the handler where the details of the error are known
			server.WriteError(w, *servErr, err.Error())
			return
		}
		server.WriteJson(w, output)
	}
}

Example usage code:

type Server struct{}
type Input struct{ x int }
type Output struct{}

// handleSomeEndpoint handles /endpoint. It takes a typed input parameter and may return
// a typed output parameter, or an error.
func (s *Server) handleSomeEndpoint(input *Input, w http.ResponseWriter, r *http.Request) (*Output, *server.Error, error) {
	// do something useful with input.
	// w and r may be used as well, as with ordinary http.Handler functions
	// (but probably that will often not be necessary).

	// Errors can be returned as follows.
	if input.x == 0 {
		// Perhaps do some logging here
		return nil, &server.ErrorUnexpectedRequest, errors.New("x was 0")
	}

	return &Output{}, nil, nil
}

func (s *Server) Handler() http.Handler {
	r := chi.NewRouter()

	r.Get("/endpoint", WrapGenericHandler(s.handleSomeEndpoint))

	return r
}
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