Codebase list golang-github-go-kit-kit / 7a9f5bb examples / profilesvc / transport.go
7a9f5bb

Tree @7a9f5bb (Download .tar.gz)

transport.go @7a9f5bbraw · history · blame

package main

import (
	"encoding/json"
	"errors"
	stdhttp "net/http"

	"github.com/gorilla/mux"
	"golang.org/x/net/context"

	kitlog "github.com/go-kit/kit/log"
	kithttp "github.com/go-kit/kit/transport/http"
)

var (
	errBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
)

func makeHandler(ctx context.Context, s ProfileService, logger kitlog.Logger) stdhttp.Handler {
	e := makeEndpoints(s)
	r := mux.NewRouter()

	commonOptions := []kithttp.ServerOption{
		kithttp.ServerErrorLogger(logger),
		kithttp.ServerErrorEncoder(encodeError),
	}

	// POST    /profiles                           adds another profile
	// GET     /profiles/:id                       retrieves the given profile by id
	// PUT     /profiles/:id                       post updated profile information about the profile
	// PATCH   /profiles/:id                       partial updated profile information
	// DELETE  /profiles/:id                       remove the given profile
	// GET     /profiles/:id/addresses             retrieve addresses associated with the profile
	// GET     /profiles/:id/addresses/:addressID  retrieve a particular profile address
	// POST    /profiles/:id/addresses             add a new address
	// DELETE  /profiles/:id/addresses/:addressID  remove an address

	r.Methods("POST").Path("/profiles/").Handler(kithttp.NewServer(
		ctx,
		e.postProfileEndpoint,
		decodePostProfileRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("GET").Path("/profiles/{id}").Handler(kithttp.NewServer(
		ctx,
		e.getProfileEndpoint,
		decodeGetProfileRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("PUT").Path("/profiles/{id}").Handler(kithttp.NewServer(
		ctx,
		e.putProfileEndpoint,
		decodePutProfileRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("PATCH").Path("/profiles/{id}").Handler(kithttp.NewServer(
		ctx,
		e.patchProfileEndpoint,
		decodePatchProfileRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("DELETE").Path("/profiles/{id}").Handler(kithttp.NewServer(
		ctx,
		e.deleteProfileEndpoint,
		decodeDeleteProfileRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
		ctx,
		e.getAddressesEndpoint,
		decodeGetAddressesRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
		ctx,
		e.getAddressEndpoint,
		decodeGetAddressRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
		ctx,
		e.postAddressEndpoint,
		decodePostAddressRequest,
		encodeResponse,
		commonOptions...,
	))
	r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
		ctx,
		e.deleteAddressEndpoint,
		decodeDeleteAddressRequest,
		encodeResponse,
		commonOptions...,
	))
	return r
}

func decodePostProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
	var req postProfileRequest
	if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
		return nil, e
	}
	return req, nil
}

func decodeGetProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	return getProfileRequest{ID: id}, nil
}

func decodePutProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	var profile Profile
	if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
		return nil, err
	}
	return putProfileRequest{
		ID:      id,
		Profile: profile,
	}, nil
}

func decodePatchProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	var profile Profile
	if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
		return nil, err
	}
	return patchProfileRequest{
		ID:      id,
		Profile: profile,
	}, nil
}

func decodeDeleteProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	return deleteProfileRequest{ID: id}, nil
}

func decodeGetAddressesRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	return getAddressesRequest{ProfileID: id}, nil
}

func decodeGetAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	addressID, ok := vars["addressID"]
	if !ok {
		return nil, errBadRouting
	}
	return getAddressRequest{
		ProfileID: id,
		AddressID: addressID,
	}, nil
}

func decodePostAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	var address Address
	if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
		return nil, err
	}
	return postAddressRequest{
		ProfileID: id,
		Address:   address,
	}, nil
}

func decodeDeleteAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
	vars := mux.Vars(r)
	id, ok := vars["id"]
	if !ok {
		return nil, errBadRouting
	}
	addressID, ok := vars["addressID"]
	if !ok {
		return nil, errBadRouting
	}
	return deleteAddressRequest{
		ProfileID: id,
		AddressID: addressID,
	}, nil
}

// errorer is implemented by all concrete response types. It allows us to
// change the HTTP response code without needing to trigger an endpoint
// (transport-level) error. For more information, read the big comment in
// endpoints.go.
type errorer interface {
	error() error
}

// encodeResponse is the common method to encode all response types to the
// client. I chose to do it this way because I didn't know if something more
// specific was necessary. It's certainly possible to specialize on a
// per-response (per-method) basis.
func encodeResponse(w stdhttp.ResponseWriter, response interface{}) error {
	if e, ok := response.(errorer); ok && e.error() != nil {
		// Not a Go kit transport error, but a business-logic error.
		// Provide those as HTTP errors.
		encodeError(w, e.error())
		return nil
	}
	return json.NewEncoder(w).Encode(response)
}

func encodeError(w stdhttp.ResponseWriter, err error) {
	if err == nil {
		panic("encodeError with nil error")
	}
	w.WriteHeader(codeFrom(err))
	json.NewEncoder(w).Encode(map[string]interface{}{
		"error": err.Error(),
	})
}

func codeFrom(err error) int {
	switch err {
	case errNotFound:
		return stdhttp.StatusNotFound
	case errAlreadyExists, errInconsistentIDs:
		return stdhttp.StatusBadRequest
	default:
		if _, ok := err.(kithttp.BadRequestError); ok {
			return stdhttp.StatusBadRequest
		}
		return stdhttp.StatusInternalServerError
	}
}