Package list golang-github-go-kit-kit / 2e72246
examples/profilesvc: refactor + add client package Peter Bourgon 6 years ago
7 changed file(s) with 762 addition(s) and 313 deletion(s). Raw diff Collapse all Expand all
0 // Package client provides a profilesvc client based on a predefined Consul
1 // service name and relevant tags. Users must only provide the address of a
2 // Consul server.
3 package client
4
5 import (
6 "io"
7 "time"
8
9 consulapi "github.com/hashicorp/consul/api"
10
11 "github.com/go-kit/kit/endpoint"
12 "github.com/go-kit/kit/examples/profilesvc"
13 "github.com/go-kit/kit/log"
14 "github.com/go-kit/kit/sd"
15 "github.com/go-kit/kit/sd/consul"
16 "github.com/go-kit/kit/sd/lb"
17 )
18
19 // New returns a service that's load-balanced over instances of profilesvc found
20 // in the provided Consul server. The mechanism of looking up profilesvc
21 // instances in Consul is hard-coded into the client.
22 func New(consulAddr string, logger log.Logger) (profilesvc.Service, error) {
23 apiclient, err := consulapi.NewClient(&consulapi.Config{
24 Address: consulAddr,
25 })
26 if err != nil {
27 return nil, err
28 }
29
30 // As the implementer of profilesvc, we declare and enforce these
31 // parameters for all of the profilesvc consumers.
32 var (
33 consulService = "profilesvc"
34 consulTags = []string{"prod"}
35 passingOnly = true
36 retryMax = 3
37 retryTimeout = 500 * time.Millisecond
38 )
39
40 var (
41 sdclient = consul.NewClient(apiclient)
42 endpoints profilesvc.Endpoints
43 )
44 {
45 factory := factoryFor(profilesvc.MakePostProfileEndpoint)
46 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
47 balancer := lb.NewRoundRobin(subscriber)
48 retry := lb.Retry(retryMax, retryTimeout, balancer)
49 endpoints.PostProfileEndpoint = retry
50 }
51 {
52 factory := factoryFor(profilesvc.MakeGetProfileEndpoint)
53 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
54 balancer := lb.NewRoundRobin(subscriber)
55 retry := lb.Retry(retryMax, retryTimeout, balancer)
56 endpoints.GetProfileEndpoint = retry
57 }
58 {
59 factory := factoryFor(profilesvc.MakePutProfileEndpoint)
60 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
61 balancer := lb.NewRoundRobin(subscriber)
62 retry := lb.Retry(retryMax, retryTimeout, balancer)
63 endpoints.PutProfileEndpoint = retry
64 }
65 {
66 factory := factoryFor(profilesvc.MakePatchProfileEndpoint)
67 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
68 balancer := lb.NewRoundRobin(subscriber)
69 retry := lb.Retry(retryMax, retryTimeout, balancer)
70 endpoints.PatchProfileEndpoint = retry
71 }
72 {
73 factory := factoryFor(profilesvc.MakeDeleteProfileEndpoint)
74 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
75 balancer := lb.NewRoundRobin(subscriber)
76 retry := lb.Retry(retryMax, retryTimeout, balancer)
77 endpoints.DeleteProfileEndpoint = retry
78 }
79 {
80 factory := factoryFor(profilesvc.MakeGetAddressesEndpoint)
81 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
82 balancer := lb.NewRoundRobin(subscriber)
83 retry := lb.Retry(retryMax, retryTimeout, balancer)
84 endpoints.GetAddressesEndpoint = retry
85 }
86 {
87 factory := factoryFor(profilesvc.MakeGetAddressEndpoint)
88 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
89 balancer := lb.NewRoundRobin(subscriber)
90 retry := lb.Retry(retryMax, retryTimeout, balancer)
91 endpoints.GetAddressEndpoint = retry
92 }
93 {
94 factory := factoryFor(profilesvc.MakePostAddressEndpoint)
95 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
96 balancer := lb.NewRoundRobin(subscriber)
97 retry := lb.Retry(retryMax, retryTimeout, balancer)
98 endpoints.PostAddressEndpoint = retry
99 }
100 {
101 factory := factoryFor(profilesvc.MakeDeleteAddressEndpoint)
102 subscriber := consul.NewSubscriber(sdclient, factory, logger, consulService, consulTags, passingOnly)
103 balancer := lb.NewRoundRobin(subscriber)
104 retry := lb.Retry(retryMax, retryTimeout, balancer)
105 endpoints.DeleteAddressEndpoint = retry
106 }
107
108 return endpoints, nil
109 }
110
111 func factoryFor(makeEndpoint func(profilesvc.Service) endpoint.Endpoint) sd.Factory {
112 return func(instance string) (endpoint.Endpoint, io.Closer, error) {
113 service, err := profilesvc.MakeClientEndpoints(instance)
114 if err != nil {
115 return nil, nil, err
116 }
117 return makeEndpoint(service), nil, nil
118 }
119 }
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "net/http"
6 "os"
7 "os/signal"
8 "syscall"
9
10 "golang.org/x/net/context"
11
12 "github.com/go-kit/kit/examples/profilesvc"
13 "github.com/go-kit/kit/log"
14 )
15
16 func main() {
17 var (
18 httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
19 )
20 flag.Parse()
21
22 var logger log.Logger
23 {
24 logger = log.NewLogfmtLogger(os.Stderr)
25 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
26 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
27 }
28
29 var ctx context.Context
30 {
31 ctx = context.Background()
32 }
33
34 var s profilesvc.Service
35 {
36 s = profilesvc.NewInmemService()
37 s = profilesvc.LoggingMiddleware(logger)(s)
38 }
39
40 var h http.Handler
41 {
42 h = profilesvc.MakeHTTPHandler(ctx, s, log.NewContext(logger).With("component", "HTTP"))
43 }
44
45 errs := make(chan error)
46 go func() {
47 c := make(chan os.Signal)
48 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
49 errs <- fmt.Errorf("%s", <-c)
50 }()
51
52 go func() {
53 logger.Log("transport", "HTTP", "addr", *httpAddr)
54 errs <- http.ListenAndServe(*httpAddr, h)
55 }()
56
57 logger.Log("exit", <-errs)
58 }
0 package main
0 package profilesvc
11
22 import (
3 "net/url"
4 "strings"
5
6 "golang.org/x/net/context"
7
38 "github.com/go-kit/kit/endpoint"
4 "golang.org/x/net/context"
9 httptransport "github.com/go-kit/kit/transport/http"
510 )
611
7 type endpoints struct {
8 postProfileEndpoint endpoint.Endpoint
9 getProfileEndpoint endpoint.Endpoint
10 putProfileEndpoint endpoint.Endpoint
11 patchProfileEndpoint endpoint.Endpoint
12 deleteProfileEndpoint endpoint.Endpoint
13 getAddressesEndpoint endpoint.Endpoint
14 getAddressEndpoint endpoint.Endpoint
15 postAddressEndpoint endpoint.Endpoint
16 deleteAddressEndpoint endpoint.Endpoint
17 }
18
19 func makeEndpoints(s ProfileService) endpoints {
20 return endpoints{
21 postProfileEndpoint: makePostProfileEndpoint(s),
22 getProfileEndpoint: makeGetProfileEndpoint(s),
23 putProfileEndpoint: makePutProfileEndpoint(s),
24 patchProfileEndpoint: makePatchProfileEndpoint(s),
25 deleteProfileEndpoint: makeDeleteProfileEndpoint(s),
26 getAddressesEndpoint: makeGetAddressesEndpoint(s),
27 getAddressEndpoint: makeGetAddressEndpoint(s),
28 postAddressEndpoint: makePostAddressEndpoint(s),
29 deleteAddressEndpoint: makeDeleteAddressEndpoint(s),
30 }
31 }
32
33 type postProfileRequest struct {
34 Profile Profile
35 }
36
37 type postProfileResponse struct {
38 Err error `json:"err,omitempty"`
39 }
40
41 func (r postProfileResponse) error() error { return r.Err }
42
43 // Regarding errors returned from service (business logic) methods, we have two
44 // options. We could return the error via the endpoint itself. That makes
45 // certain things a little bit easier, like providing non-200 HTTP responses to
46 // the client. But Go kit assumes that endpoint errors are (or may be treated
47 // as) transport-domain errors. For example, an endpoint error will count
48 // against a circuit breaker error count. Therefore, it's almost certainly
49 // better to return service (business logic) errors in the response object. This
50 // means we have to do a bit more work in the HTTP response encoder to detect
51 // e.g. a not-found error and provide a proper HTTP status code. That work is
52 // done with the errorer interface, in transport.go.
53
54 func makePostProfileEndpoint(s ProfileService) endpoint.Endpoint {
12 // Endpoints collects all of the endpoints that compose a profile service. It's
13 // meant to be used as a helper struct, to collect all of the endpoints into a
14 // single parameter.
15 //
16 // In a server, it's useful for functions that need to operate on a per-endpoint
17 // basis. For example, you might pass an Endpoints to a function that produces
18 // an http.Handler, with each method (endpoint) wired up to a specific path. (It
19 // is probably a mistake in design to invoke the Service methods on the
20 // Endpoints struct in a server.)
21 //
22 // In a client, it's useful to collect individually constructed endpoints into a
23 // single type that implements the Service interface. For example, you might
24 // construct individual endpoints using transport/http.NewClient, combine them
25 // into an Endpoints, and return it to the caller as a Service.
26 type Endpoints struct {
27 PostProfileEndpoint endpoint.Endpoint
28 GetProfileEndpoint endpoint.Endpoint
29 PutProfileEndpoint endpoint.Endpoint
30 PatchProfileEndpoint endpoint.Endpoint
31 DeleteProfileEndpoint endpoint.Endpoint
32 GetAddressesEndpoint endpoint.Endpoint
33 GetAddressEndpoint endpoint.Endpoint
34 PostAddressEndpoint endpoint.Endpoint
35 DeleteAddressEndpoint endpoint.Endpoint
36 }
37
38 // MakeServerEndpoints returns an Endpoints struct where each endpoint invokes
39 // the corresponding method on the provided service. Useful in a profilesvc
40 // server.
41 func MakeServerEndpoints(s Service) Endpoints {
42 return Endpoints{
43 PostProfileEndpoint: MakePostProfileEndpoint(s),
44 GetProfileEndpoint: MakeGetProfileEndpoint(s),
45 PutProfileEndpoint: MakePutProfileEndpoint(s),
46 PatchProfileEndpoint: MakePatchProfileEndpoint(s),
47 DeleteProfileEndpoint: MakeDeleteProfileEndpoint(s),
48 GetAddressesEndpoint: MakeGetAddressesEndpoint(s),
49 GetAddressEndpoint: MakeGetAddressEndpoint(s),
50 PostAddressEndpoint: MakePostAddressEndpoint(s),
51 DeleteAddressEndpoint: MakeDeleteAddressEndpoint(s),
52 }
53 }
54
55 // MakeClientEndpoints returns an Endpoints struct where each endpoint invokes
56 // the corresponding method on the remote instance, via a transport/http.Client.
57 // Useful in a profilesvc client.
58 func MakeClientEndpoints(instance string) (Endpoints, error) {
59 if !strings.HasPrefix(instance, "http") {
60 instance = "http://" + instance
61 }
62 tgt, err := url.Parse(instance)
63 if err != nil {
64 return Endpoints{}, err
65 }
66 tgt.Path = ""
67
68 options := []httptransport.ClientOption{}
69
70 // Note that the request encoders need to modify the request URL, changing
71 // the path and method. That's fine: we simply need to provide specific
72 // encoders for each endpoint.
73
74 return Endpoints{
75 PostProfileEndpoint: httptransport.NewClient("POST", tgt, encodePostProfileRequest, decodePostProfileResponse, options...).Endpoint(),
76 GetProfileEndpoint: httptransport.NewClient("GET", tgt, encodeGetProfileRequest, decodeGetProfileResponse, options...).Endpoint(),
77 PutProfileEndpoint: httptransport.NewClient("PUT", tgt, encodePutProfileRequest, decodePutProfileResponse, options...).Endpoint(),
78 PatchProfileEndpoint: httptransport.NewClient("PATCH", tgt, encodePatchProfileRequest, decodePatchProfileResponse, options...).Endpoint(),
79 DeleteProfileEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteProfileRequest, decodeDeleteProfileResponse, options...).Endpoint(),
80 GetAddressesEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressesRequest, decodeGetAddressesResponse, options...).Endpoint(),
81 GetAddressEndpoint: httptransport.NewClient("GET", tgt, encodeGetAddressRequest, decodeGetAddressResponse, options...).Endpoint(),
82 PostAddressEndpoint: httptransport.NewClient("POST", tgt, encodePostAddressRequest, decodePostAddressResponse, options...).Endpoint(),
83 DeleteAddressEndpoint: httptransport.NewClient("DELETE", tgt, encodeDeleteAddressRequest, decodeDeleteAddressResponse, options...).Endpoint(),
84 }, nil
85 }
86
87 // PostProfile implements Service. Primarily useful in a client.
88 func (e Endpoints) PostProfile(ctx context.Context, p Profile) error {
89 request := postProfileRequest{Profile: p}
90 response, err := e.PostProfileEndpoint(ctx, request)
91 if err != nil {
92 return err
93 }
94 resp := response.(postProfileResponse)
95 return resp.Err
96 }
97
98 // GetProfile implements Service. Primarily useful in a client.
99 func (e Endpoints) GetProfile(ctx context.Context, id string) (Profile, error) {
100 request := getProfileRequest{ID: id}
101 response, err := e.GetProfileEndpoint(ctx, request)
102 if err != nil {
103 return Profile{}, err
104 }
105 resp := response.(getProfileResponse)
106 return resp.Profile, resp.Err
107 }
108
109 // PutProfile implements Service. Primarily useful in a client.
110 func (e Endpoints) PutProfile(ctx context.Context, id string, p Profile) error {
111 request := putProfileRequest{ID: id, Profile: p}
112 response, err := e.PutProfileEndpoint(ctx, request)
113 if err != nil {
114 return err
115 }
116 resp := response.(putProfileResponse)
117 return resp.Err
118 }
119
120 // PatchProfile implements Service. Primarily useful in a client.
121 func (e Endpoints) PatchProfile(ctx context.Context, id string, p Profile) error {
122 request := patchProfileRequest{ID: id, Profile: p}
123 response, err := e.PatchProfileEndpoint(ctx, request)
124 if err != nil {
125 return err
126 }
127 resp := response.(patchProfileResponse)
128 return resp.Err
129 }
130
131 // DeleteProfile implements Service. Primarily useful in a client.
132 func (e Endpoints) DeleteProfile(ctx context.Context, id string) error {
133 request := deleteProfileRequest{ID: id}
134 response, err := e.DeleteProfileEndpoint(ctx, request)
135 if err != nil {
136 return err
137 }
138 resp := response.(deleteProfileResponse)
139 return resp.Err
140 }
141
142 // GetAddresses implements Service. Primarily useful in a client.
143 func (e Endpoints) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
144 request := getAddressesRequest{ProfileID: profileID}
145 response, err := e.GetAddressesEndpoint(ctx, request)
146 if err != nil {
147 return nil, err
148 }
149 resp := response.(getAddressesResponse)
150 return resp.Addresses, resp.Err
151 }
152
153 // GetAddress implements Service. Primarily useful in a client.
154 func (e Endpoints) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
155 request := getAddressRequest{ProfileID: profileID, AddressID: addressID}
156 response, err := e.GetAddressEndpoint(ctx, request)
157 if err != nil {
158 return Address{}, err
159 }
160 resp := response.(getAddressResponse)
161 return resp.Address, resp.Err
162 }
163
164 // PostAddress implements Service. Primarily useful in a client.
165 func (e Endpoints) PostAddress(ctx context.Context, profileID string, a Address) error {
166 request := postAddressRequest{ProfileID: profileID, Address: a}
167 response, err := e.PostAddressEndpoint(ctx, request)
168 if err != nil {
169 return err
170 }
171 resp := response.(postAddressResponse)
172 return resp.Err
173 }
174
175 // DeleteAddress implements Service. Primarily useful in a client.
176 func (e Endpoints) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
177 request := deleteAddressRequest{ProfileID: profileID, AddressID: addressID}
178 response, err := e.DeleteAddressEndpoint(ctx, request)
179 if err != nil {
180 return err
181 }
182 resp := response.(deleteAddressResponse)
183 return resp.Err
184 }
185
186 // MakePostProfileEndpoint returns an endpoint via the passed service.
187 // Primarily useful in a server.
188 func MakePostProfileEndpoint(s Service) endpoint.Endpoint {
55189 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
56190 req := request.(postProfileRequest)
57191 e := s.PostProfile(ctx, req.Profile)
59193 }
60194 }
61195
196 // MakeGetProfileEndpoint returns an endpoint via the passed service.
197 // Primarily useful in a server.
198 func MakeGetProfileEndpoint(s Service) endpoint.Endpoint {
199 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
200 req := request.(getProfileRequest)
201 p, e := s.GetProfile(ctx, req.ID)
202 return getProfileResponse{Profile: p, Err: e}, nil
203 }
204 }
205
206 // MakePutProfileEndpoint returns an endpoint via the passed service.
207 // Primarily useful in a server.
208 func MakePutProfileEndpoint(s Service) endpoint.Endpoint {
209 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
210 req := request.(putProfileRequest)
211 e := s.PutProfile(ctx, req.ID, req.Profile)
212 return putProfileResponse{Err: e}, nil
213 }
214 }
215
216 // MakePatchProfileEndpoint returns an endpoint via the passed service.
217 // Primarily useful in a server.
218 func MakePatchProfileEndpoint(s Service) endpoint.Endpoint {
219 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
220 req := request.(patchProfileRequest)
221 e := s.PatchProfile(ctx, req.ID, req.Profile)
222 return patchProfileResponse{Err: e}, nil
223 }
224 }
225
226 // MakeDeleteProfileEndpoint returns an endpoint via the passed service.
227 // Primarily useful in a server.
228 func MakeDeleteProfileEndpoint(s Service) endpoint.Endpoint {
229 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
230 req := request.(deleteProfileRequest)
231 e := s.DeleteProfile(ctx, req.ID)
232 return deleteProfileResponse{Err: e}, nil
233 }
234 }
235
236 // MakeGetAddressesEndpoint returns an endpoint via the passed service.
237 // Primarily useful in a server.
238 func MakeGetAddressesEndpoint(s Service) endpoint.Endpoint {
239 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
240 req := request.(getAddressesRequest)
241 a, e := s.GetAddresses(ctx, req.ProfileID)
242 return getAddressesResponse{Addresses: a, Err: e}, nil
243 }
244 }
245
246 // MakeGetAddressEndpoint returns an endpoint via the passed service.
247 // Primarily useful in a server.
248 func MakeGetAddressEndpoint(s Service) endpoint.Endpoint {
249 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
250 req := request.(getAddressRequest)
251 a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
252 return getAddressResponse{Address: a, Err: e}, nil
253 }
254 }
255
256 // MakePostAddressEndpoint returns an endpoint via the passed service.
257 // Primarily useful in a server.
258 func MakePostAddressEndpoint(s Service) endpoint.Endpoint {
259 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
260 req := request.(postAddressRequest)
261 e := s.PostAddress(ctx, req.ProfileID, req.Address)
262 return postAddressResponse{Err: e}, nil
263 }
264 }
265
266 // MakeDeleteAddressEndpoint returns an endpoint via the passed service.
267 // Primarily useful in a server.
268 func MakeDeleteAddressEndpoint(s Service) endpoint.Endpoint {
269 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
270 req := request.(deleteAddressRequest)
271 e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
272 return deleteAddressResponse{Err: e}, nil
273 }
274 }
275
276 // We have two options to return errors from the business logic.
277 //
278 // We could return the error via the endpoint itself. That makes certain things
279 // a little bit easier, like providing non-200 HTTP responses to the client. But
280 // Go kit assumes that endpoint errors are (or may be treated as)
281 // transport-domain errors. For example, an endpoint error will count against a
282 // circuit breaker error count.
283 //
284 // Therefore, it's often better to return service (business logic) errors in the
285 // response object. This means we have to do a bit more work in the HTTP
286 // response encoder to detect e.g. a not-found error and provide a proper HTTP
287 // status code. That work is done with the errorer interface, in transport.go.
288 // Response types that may contain business-logic errors implement that
289 // interface.
290
291 type postProfileRequest struct {
292 Profile Profile
293 }
294
295 type postProfileResponse struct {
296 Err error `json:"err,omitempty"`
297 }
298
299 func (r postProfileResponse) error() error { return r.Err }
300
62301 type getProfileRequest struct {
63302 ID string
64303 }
70309
71310 func (r getProfileResponse) error() error { return r.Err }
72311
73 func makeGetProfileEndpoint(s ProfileService) endpoint.Endpoint {
74 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
75 req := request.(getProfileRequest)
76 p, e := s.GetProfile(ctx, req.ID)
77 return getProfileResponse{Profile: p, Err: e}, nil
78 }
79 }
80
81312 type putProfileRequest struct {
82313 ID string
83314 Profile Profile
89320
90321 func (r putProfileResponse) error() error { return nil }
91322
92 func makePutProfileEndpoint(s ProfileService) endpoint.Endpoint {
93 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
94 req := request.(putProfileRequest)
95 e := s.PutProfile(ctx, req.ID, req.Profile)
96 return putProfileResponse{Err: e}, nil
97 }
98 }
99
100323 type patchProfileRequest struct {
101324 ID string
102325 Profile Profile
108331
109332 func (r patchProfileResponse) error() error { return r.Err }
110333
111 func makePatchProfileEndpoint(s ProfileService) endpoint.Endpoint {
112 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
113 req := request.(patchProfileRequest)
114 e := s.PatchProfile(ctx, req.ID, req.Profile)
115 return patchProfileResponse{Err: e}, nil
116 }
117 }
118
119334 type deleteProfileRequest struct {
120335 ID string
121336 }
125340 }
126341
127342 func (r deleteProfileResponse) error() error { return r.Err }
128
129 func makeDeleteProfileEndpoint(s ProfileService) endpoint.Endpoint {
130 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
131 req := request.(deleteProfileRequest)
132 e := s.DeleteProfile(ctx, req.ID)
133 return deleteProfileResponse{Err: e}, nil
134 }
135 }
136343
137344 type getAddressesRequest struct {
138345 ProfileID string
145352
146353 func (r getAddressesResponse) error() error { return r.Err }
147354
148 func makeGetAddressesEndpoint(s ProfileService) endpoint.Endpoint {
149 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
150 req := request.(getAddressesRequest)
151 a, e := s.GetAddresses(ctx, req.ProfileID)
152 return getAddressesResponse{Addresses: a, Err: e}, nil
153 }
154 }
155
156355 type getAddressRequest struct {
157356 ProfileID string
158357 AddressID string
165364
166365 func (r getAddressResponse) error() error { return r.Err }
167366
168 func makeGetAddressEndpoint(s ProfileService) endpoint.Endpoint {
169 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
170 req := request.(getAddressRequest)
171 a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
172 return getAddressResponse{Address: a, Err: e}, nil
173 }
174 }
175
176367 type postAddressRequest struct {
177368 ProfileID string
178369 Address Address
184375
185376 func (r postAddressResponse) error() error { return r.Err }
186377
187 func makePostAddressEndpoint(s ProfileService) endpoint.Endpoint {
188 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
189 req := request.(postAddressRequest)
190 e := s.PostAddress(ctx, req.ProfileID, req.Address)
191 return postAddressResponse{Err: e}, nil
192 }
193 }
194
195378 type deleteAddressRequest struct {
196379 ProfileID string
197380 AddressID string
202385 }
203386
204387 func (r deleteAddressResponse) error() error { return r.Err }
205
206 func makeDeleteAddressEndpoint(s ProfileService) endpoint.Endpoint {
207 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
208 req := request.(deleteAddressRequest)
209 e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
210 return deleteAddressResponse{Err: e}, nil
211 }
212 }
+0
-57
examples/profilesvc/main.go less more
0 package main
1
2 import (
3 "flag"
4 "fmt"
5 "net/http"
6 "os"
7 "os/signal"
8 "syscall"
9
10 "golang.org/x/net/context"
11
12 "github.com/go-kit/kit/log"
13 )
14
15 func main() {
16 var (
17 httpAddr = flag.String("http.addr", ":8080", "HTTP listen address")
18 )
19 flag.Parse()
20
21 var logger log.Logger
22 {
23 logger = log.NewLogfmtLogger(os.Stderr)
24 logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC)
25 logger = log.NewContext(logger).With("caller", log.DefaultCaller)
26 }
27
28 var ctx context.Context
29 {
30 ctx = context.Background()
31 }
32
33 var s ProfileService
34 {
35 s = newInmemService()
36 s = loggingMiddleware{s, log.NewContext(logger).With("component", "svc")}
37 }
38
39 var h http.Handler
40 {
41 h = makeHandler(ctx, s, log.NewContext(logger).With("component", "http"))
42 }
43
44 errs := make(chan error, 2)
45 go func() {
46 logger.Log("transport", "http", "address", *httpAddr, "msg", "listening")
47 errs <- http.ListenAndServe(*httpAddr, h)
48 }()
49 go func() {
50 c := make(chan os.Signal)
51 signal.Notify(c, syscall.SIGINT)
52 errs <- fmt.Errorf("%s", <-c)
53 }()
54
55 logger.Log("terminated", <-errs)
56 }
0 package main
0 package profilesvc
11
22 import (
33 "time"
77 "github.com/go-kit/kit/log"
88 )
99
10 // Middleware describes a service (as opposed to endpoint) middleware.
11 type Middleware func(Service) Service
12
13 func LoggingMiddleware(logger log.Logger) Middleware {
14 return func(next Service) Service {
15 return &loggingMiddleware{
16 next: next,
17 logger: logger,
18 }
19 }
20 }
21
1022 type loggingMiddleware struct {
11 next ProfileService
23 next Service
1224 logger log.Logger
1325 }
1426
0 package main
0 package profilesvc
11
22 import (
33 "errors"
66 "golang.org/x/net/context"
77 )
88
9 // ProfileService is a simple CRUD interface for user profiles.
10 type ProfileService interface {
9 // Service is a simple CRUD interface for user profiles.
10 type Service interface {
1111 PostProfile(ctx context.Context, p Profile) error
1212 GetProfile(ctx context.Context, id string) (Profile, error)
1313 PutProfile(ctx context.Context, id string, p Profile) error
3535 }
3636
3737 var (
38 errInconsistentIDs = errors.New("inconsistent IDs")
39 errAlreadyExists = errors.New("already exists")
40 errNotFound = errors.New("not found")
38 ErrInconsistentIDs = errors.New("inconsistent IDs")
39 ErrAlreadyExists = errors.New("already exists")
40 ErrNotFound = errors.New("not found")
4141 )
4242
4343 type inmemService struct {
4545 m map[string]Profile
4646 }
4747
48 func newInmemService() ProfileService {
48 func NewInmemService() Service {
4949 return &inmemService{
5050 m: map[string]Profile{},
5151 }
5555 s.mtx.Lock()
5656 defer s.mtx.Unlock()
5757 if _, ok := s.m[p.ID]; ok {
58 return errAlreadyExists // POST = create, don't overwrite
58 return ErrAlreadyExists // POST = create, don't overwrite
5959 }
6060 s.m[p.ID] = p
6161 return nil
6666 defer s.mtx.RUnlock()
6767 p, ok := s.m[id]
6868 if !ok {
69 return Profile{}, errNotFound
69 return Profile{}, ErrNotFound
7070 }
7171 return p, nil
7272 }
7373
7474 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
7575 if id != p.ID {
76 return errInconsistentIDs
76 return ErrInconsistentIDs
7777 }
7878 s.mtx.Lock()
7979 defer s.mtx.Unlock()
8383
8484 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
8585 if p.ID != "" && id != p.ID {
86 return errInconsistentIDs
86 return ErrInconsistentIDs
8787 }
8888
8989 s.mtx.Lock()
9191
9292 existing, ok := s.m[id]
9393 if !ok {
94 return errNotFound // PATCH = update existing, don't create
94 return ErrNotFound // PATCH = update existing, don't create
9595 }
9696
9797 // We assume that it's not possible to PATCH the ID, and that it's not
114114 s.mtx.Lock()
115115 defer s.mtx.Unlock()
116116 if _, ok := s.m[id]; !ok {
117 return errNotFound
117 return ErrNotFound
118118 }
119119 delete(s.m, id)
120120 return nil
125125 defer s.mtx.RUnlock()
126126 p, ok := s.m[profileID]
127127 if !ok {
128 return []Address{}, errNotFound
128 return []Address{}, ErrNotFound
129129 }
130130 return p.Addresses, nil
131131 }
135135 defer s.mtx.RUnlock()
136136 p, ok := s.m[profileID]
137137 if !ok {
138 return Address{}, errNotFound
138 return Address{}, ErrNotFound
139139 }
140140 for _, address := range p.Addresses {
141141 if address.ID == addressID {
142142 return address, nil
143143 }
144144 }
145 return Address{}, errNotFound
145 return Address{}, ErrNotFound
146146 }
147147
148148 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
150150 defer s.mtx.Unlock()
151151 p, ok := s.m[profileID]
152152 if !ok {
153 return errNotFound
153 return ErrNotFound
154154 }
155155 for _, address := range p.Addresses {
156156 if address.ID == a.ID {
157 return errAlreadyExists
157 return ErrAlreadyExists
158158 }
159159 }
160160 p.Addresses = append(p.Addresses, a)
167167 defer s.mtx.Unlock()
168168 p, ok := s.m[profileID]
169169 if !ok {
170 return errNotFound
170 return ErrNotFound
171171 }
172172 newAddresses := make([]Address, 0, len(p.Addresses))
173173 for _, address := range p.Addresses {
177177 newAddresses = append(newAddresses, address)
178178 }
179179 if len(newAddresses) == len(p.Addresses) {
180 return errNotFound
180 return ErrNotFound
181181 }
182182 p.Addresses = newAddresses
183183 s.m[profileID] = p
0 package main
0 package profilesvc
1
2 // The profilesvc is just over HTTP, so we just have a single transport.go.
13
24 import (
5 "bytes"
36 "encoding/json"
47 "errors"
5 stdhttp "net/http"
8 "io/ioutil"
9 "net/http"
610
711 "github.com/gorilla/mux"
812 "golang.org/x/net/context"
913
10 kitlog "github.com/go-kit/kit/log"
11 kithttp "github.com/go-kit/kit/transport/http"
14 "net/url"
15
16 "github.com/go-kit/kit/log"
17 httptransport "github.com/go-kit/kit/transport/http"
1218 )
1319
1420 var (
15 errBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
21 // ErrBadRouting is returned when an expected path variable is missing.
22 // It always indicates programmer error.
23 ErrBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
1624 )
1725
18 func makeHandler(ctx context.Context, s ProfileService, logger kitlog.Logger) stdhttp.Handler {
19 e := makeEndpoints(s)
26 // MakeHTTPHandler mounts all of the service endpoints into an http.Handler.
27 // Useful in a profilesvc server.
28 func MakeHTTPHandler(ctx context.Context, s Service, logger log.Logger) http.Handler {
2029 r := mux.NewRouter()
21
22 commonOptions := []kithttp.ServerOption{
23 kithttp.ServerErrorLogger(logger),
24 kithttp.ServerErrorEncoder(encodeError),
30 e := MakeServerEndpoints(s)
31 options := []httptransport.ServerOption{
32 httptransport.ServerErrorLogger(logger),
33 httptransport.ServerErrorEncoder(encodeError),
2534 }
2635
2736 // POST /profiles adds another profile
3443 // POST /profiles/:id/addresses add a new address
3544 // DELETE /profiles/:id/addresses/:addressID remove an address
3645
37 r.Methods("POST").Path("/profiles/").Handler(kithttp.NewServer(
38 ctx,
39 e.postProfileEndpoint,
46 r.Methods("POST").Path("/profiles/").Handler(httptransport.NewServer(
47 ctx,
48 e.PostProfileEndpoint,
4049 decodePostProfileRequest,
4150 encodeResponse,
42 commonOptions...,
43 ))
44 r.Methods("GET").Path("/profiles/{id}").Handler(kithttp.NewServer(
45 ctx,
46 e.getProfileEndpoint,
51 options...,
52 ))
53 r.Methods("GET").Path("/profiles/{id}").Handler(httptransport.NewServer(
54 ctx,
55 e.GetProfileEndpoint,
4756 decodeGetProfileRequest,
4857 encodeResponse,
49 commonOptions...,
50 ))
51 r.Methods("PUT").Path("/profiles/{id}").Handler(kithttp.NewServer(
52 ctx,
53 e.putProfileEndpoint,
58 options...,
59 ))
60 r.Methods("PUT").Path("/profiles/{id}").Handler(httptransport.NewServer(
61 ctx,
62 e.PutProfileEndpoint,
5463 decodePutProfileRequest,
5564 encodeResponse,
56 commonOptions...,
57 ))
58 r.Methods("PATCH").Path("/profiles/{id}").Handler(kithttp.NewServer(
59 ctx,
60 e.patchProfileEndpoint,
65 options...,
66 ))
67 r.Methods("PATCH").Path("/profiles/{id}").Handler(httptransport.NewServer(
68 ctx,
69 e.PatchProfileEndpoint,
6170 decodePatchProfileRequest,
6271 encodeResponse,
63 commonOptions...,
64 ))
65 r.Methods("DELETE").Path("/profiles/{id}").Handler(kithttp.NewServer(
66 ctx,
67 e.deleteProfileEndpoint,
72 options...,
73 ))
74 r.Methods("DELETE").Path("/profiles/{id}").Handler(httptransport.NewServer(
75 ctx,
76 e.DeleteProfileEndpoint,
6877 decodeDeleteProfileRequest,
6978 encodeResponse,
70 commonOptions...,
71 ))
72 r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
73 ctx,
74 e.getAddressesEndpoint,
79 options...,
80 ))
81 r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
82 ctx,
83 e.GetAddressesEndpoint,
7584 decodeGetAddressesRequest,
7685 encodeResponse,
77 commonOptions...,
78 ))
79 r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
80 ctx,
81 e.getAddressEndpoint,
86 options...,
87 ))
88 r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
89 ctx,
90 e.GetAddressEndpoint,
8291 decodeGetAddressRequest,
8392 encodeResponse,
84 commonOptions...,
85 ))
86 r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
87 ctx,
88 e.postAddressEndpoint,
93 options...,
94 ))
95 r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(httptransport.NewServer(
96 ctx,
97 e.PostAddressEndpoint,
8998 decodePostAddressRequest,
9099 encodeResponse,
91 commonOptions...,
92 ))
93 r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
94 ctx,
95 e.deleteAddressEndpoint,
100 options...,
101 ))
102 r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(httptransport.NewServer(
103 ctx,
104 e.DeleteAddressEndpoint,
96105 decodeDeleteAddressRequest,
97106 encodeResponse,
98 commonOptions...,
107 options...,
99108 ))
100109 return r
101110 }
102111
103 func decodePostProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
112 func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
104113 var req postProfileRequest
105114 if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
106115 return nil, e
108117 return req, nil
109118 }
110119
111 func decodeGetProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
112 vars := mux.Vars(r)
113 id, ok := vars["id"]
114 if !ok {
115 return nil, errBadRouting
120 func decodeGetProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
121 vars := mux.Vars(r)
122 id, ok := vars["id"]
123 if !ok {
124 return nil, ErrBadRouting
116125 }
117126 return getProfileRequest{ID: id}, nil
118127 }
119128
120 func decodePutProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
121 vars := mux.Vars(r)
122 id, ok := vars["id"]
123 if !ok {
124 return nil, errBadRouting
129 func decodePutProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
130 vars := mux.Vars(r)
131 id, ok := vars["id"]
132 if !ok {
133 return nil, ErrBadRouting
125134 }
126135 var profile Profile
127136 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
133142 }, nil
134143 }
135144
136 func decodePatchProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
137 vars := mux.Vars(r)
138 id, ok := vars["id"]
139 if !ok {
140 return nil, errBadRouting
145 func decodePatchProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
146 vars := mux.Vars(r)
147 id, ok := vars["id"]
148 if !ok {
149 return nil, ErrBadRouting
141150 }
142151 var profile Profile
143152 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
149158 }, nil
150159 }
151160
152 func decodeDeleteProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
153 vars := mux.Vars(r)
154 id, ok := vars["id"]
155 if !ok {
156 return nil, errBadRouting
161 func decodeDeleteProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
162 vars := mux.Vars(r)
163 id, ok := vars["id"]
164 if !ok {
165 return nil, ErrBadRouting
157166 }
158167 return deleteProfileRequest{ID: id}, nil
159168 }
160169
161 func decodeGetAddressesRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
162 vars := mux.Vars(r)
163 id, ok := vars["id"]
164 if !ok {
165 return nil, errBadRouting
170 func decodeGetAddressesRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
171 vars := mux.Vars(r)
172 id, ok := vars["id"]
173 if !ok {
174 return nil, ErrBadRouting
166175 }
167176 return getAddressesRequest{ProfileID: id}, nil
168177 }
169178
170 func decodeGetAddressRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
171 vars := mux.Vars(r)
172 id, ok := vars["id"]
173 if !ok {
174 return nil, errBadRouting
179 func decodeGetAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
180 vars := mux.Vars(r)
181 id, ok := vars["id"]
182 if !ok {
183 return nil, ErrBadRouting
175184 }
176185 addressID, ok := vars["addressID"]
177186 if !ok {
178 return nil, errBadRouting
187 return nil, ErrBadRouting
179188 }
180189 return getAddressRequest{
181190 ProfileID: id,
183192 }, nil
184193 }
185194
186 func decodePostAddressRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
187 vars := mux.Vars(r)
188 id, ok := vars["id"]
189 if !ok {
190 return nil, errBadRouting
195 func decodePostAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
196 vars := mux.Vars(r)
197 id, ok := vars["id"]
198 if !ok {
199 return nil, ErrBadRouting
191200 }
192201 var address Address
193202 if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
199208 }, nil
200209 }
201210
202 func decodeDeleteAddressRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) {
203 vars := mux.Vars(r)
204 id, ok := vars["id"]
205 if !ok {
206 return nil, errBadRouting
211 func decodeDeleteAddressRequest(_ context.Context, r *http.Request) (request interface{}, err error) {
212 vars := mux.Vars(r)
213 id, ok := vars["id"]
214 if !ok {
215 return nil, ErrBadRouting
207216 }
208217 addressID, ok := vars["addressID"]
209218 if !ok {
210 return nil, errBadRouting
219 return nil, ErrBadRouting
211220 }
212221 return deleteAddressRequest{
213222 ProfileID: id,
215224 }, nil
216225 }
217226
218 // errorer is implemented by all concrete response types. It allows us to
219 // change the HTTP response code without needing to trigger an endpoint
220 // (transport-level) error. For more information, read the big comment in
221 // endpoints.go.
227 func encodePostProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
228 // r.Methods("POST").Path("/profiles/")
229 req.Method, req.URL.Path = "POST", url.QueryEscape("/profiles/")
230 return encodeRequest(ctx, req, request)
231 }
232
233 func encodeGetProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
234 // r.Methods("GET").Path("/profiles/{id}")
235 r := request.(getProfileRequest)
236 req.Method, req.URL.Path = "GET", url.QueryEscape("/profiles/"+r.ID)
237 return encodeRequest(ctx, req, request)
238 }
239
240 func encodePutProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
241 // r.Methods("PUT").Path("/profiles/{id}")
242 r := request.(putProfileRequest)
243 req.Method, req.URL.Path = "PUT", url.QueryEscape("/profiles/"+r.ID)
244 return encodeRequest(ctx, req, request)
245 }
246
247 func encodePatchProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
248 // r.Methods("PATCH").Path("/profiles/{id}")
249 r := request.(patchProfileRequest)
250 req.Method, req.URL.Path = "PATCH", url.QueryEscape("/profiles/"+r.ID)
251 return encodeRequest(ctx, req, request)
252 }
253
254 func encodeDeleteProfileRequest(ctx context.Context, req *http.Request, request interface{}) error {
255 // r.Methods("DELETE").Path("/profiles/{id}")
256 r := request.(deleteProfileRequest)
257 req.Method, req.URL.Path = "DELETE", url.QueryEscape("/profiles/"+r.ID)
258 return encodeRequest(ctx, req, request)
259 }
260
261 func encodeGetAddressesRequest(ctx context.Context, req *http.Request, request interface{}) error {
262 // r.Methods("GET").Path("/profiles/{id}/addresses/")
263 r := request.(getAddressesRequest)
264 req.Method, req.URL.Path = "GET", url.QueryEscape("/profiles/"+r.ProfileID+"/addresses/")
265 return encodeRequest(ctx, req, request)
266 }
267
268 func encodeGetAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
269 // r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}")
270 r := request.(getAddressRequest)
271 req.Method, req.URL.Path = "GET", url.QueryEscape("/profiles/"+r.ProfileID+"/addresses/"+r.AddressID)
272 return encodeRequest(ctx, req, request)
273 }
274
275 func encodePostAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
276 // r.Methods("POST").Path("/profiles/{id}/addresses/")
277 r := request.(postAddressRequest)
278 req.Method, req.URL.Path = "POST", url.QueryEscape("/profiles/"+r.ProfileID+"/addresses/")
279 return encodeRequest(ctx, req, request)
280 }
281
282 func encodeDeleteAddressRequest(ctx context.Context, req *http.Request, request interface{}) error {
283 // r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}")
284 r := request.(deleteAddressRequest)
285 req.Method, req.URL.Path = "DELETE", url.QueryEscape("/profiles/"+r.ProfileID+"/addresses/"+r.AddressID)
286 return encodeRequest(ctx, req, request)
287 }
288
289 func decodePostProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
290 var response postProfileResponse
291 err := json.NewDecoder(resp.Body).Decode(&response)
292 return response, err
293 }
294
295 func decodeGetProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
296 var response getProfileResponse
297 err := json.NewDecoder(resp.Body).Decode(&response)
298 return response, err
299 }
300
301 func decodePutProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
302 var response putProfileResponse
303 err := json.NewDecoder(resp.Body).Decode(&response)
304 return response, err
305 }
306
307 func decodePatchProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
308 var response patchProfileResponse
309 err := json.NewDecoder(resp.Body).Decode(&response)
310 return response, err
311 }
312
313 func decodeDeleteProfileResponse(_ context.Context, resp *http.Response) (interface{}, error) {
314 var response deleteProfileResponse
315 err := json.NewDecoder(resp.Body).Decode(&response)
316 return response, err
317 }
318
319 func decodeGetAddressesResponse(_ context.Context, resp *http.Response) (interface{}, error) {
320 var response getAddressesResponse
321 err := json.NewDecoder(resp.Body).Decode(&response)
322 return response, err
323 }
324
325 func decodeGetAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
326 var response getAddressResponse
327 err := json.NewDecoder(resp.Body).Decode(&response)
328 return response, err
329 }
330
331 func decodePostAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
332 var response postAddressResponse
333 err := json.NewDecoder(resp.Body).Decode(&response)
334 return response, err
335 }
336
337 func decodeDeleteAddressResponse(_ context.Context, resp *http.Response) (interface{}, error) {
338 var response deleteAddressResponse
339 err := json.NewDecoder(resp.Body).Decode(&response)
340 return response, err
341 }
342
343 // errorer is implemented by all concrete response types that may contain
344 // errors. It allows us to change the HTTP response code without needing to
345 // trigger an endpoint (transport-level) error. For more information, read the
346 // big comment in endpoints.go.
222347 type errorer interface {
223348 error() error
224349 }
225350
226351 // encodeResponse is the common method to encode all response types to the
227 // client. I chose to do it this way because I didn't know if something more
228 // specific was necessary. It's certainly possible to specialize on a
229 // per-response (per-method) basis.
230 func encodeResponse(ctx context.Context, w stdhttp.ResponseWriter, response interface{}) error {
352 // client. I chose to do it this way because, since we're using JSON, there's no
353 // reason to provide anything more specific. It's certainly possible to
354 // specialize on a per-response (per-method) basis.
355 func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
231356 if e, ok := response.(errorer); ok && e.error() != nil {
232357 // Not a Go kit transport error, but a business-logic error.
233358 // Provide those as HTTP errors.
234359 encodeError(ctx, e.error(), w)
235360 return nil
236361 }
362 w.Header().Set("Content-Type", "application/json; charset=utf-8")
237363 return json.NewEncoder(w).Encode(response)
238364 }
239365
240 func encodeError(_ context.Context, err error, w stdhttp.ResponseWriter) {
366 // encodeRequest likewise JSON-encodes the request to the HTTP request body.
367 // Don't use it directly as a transport/http.Client EncodeRequestFunc:
368 // profilesvc endpoints require mutating the HTTP method and request path.
369 func encodeRequest(_ context.Context, req *http.Request, request interface{}) error {
370 var buf bytes.Buffer
371 err := json.NewEncoder(&buf).Encode(request)
372 if err != nil {
373 return err
374 }
375 req.Body = ioutil.NopCloser(&buf)
376 return nil
377 }
378
379 func encodeError(_ context.Context, err error, w http.ResponseWriter) {
241380 if err == nil {
242381 panic("encodeError with nil error")
243382 }
383 w.Header().Set("Content-Type", "application/json; charset=utf-8")
244384 w.WriteHeader(codeFrom(err))
245385 json.NewEncoder(w).Encode(map[string]interface{}{
246386 "error": err.Error(),
249389
250390 func codeFrom(err error) int {
251391 switch err {
252 case errNotFound:
253 return stdhttp.StatusNotFound
254 case errAlreadyExists, errInconsistentIDs:
255 return stdhttp.StatusBadRequest
392 case ErrNotFound:
393 return http.StatusNotFound
394 case ErrAlreadyExists, ErrInconsistentIDs:
395 return http.StatusBadRequest
256396 default:
257 if e, ok := err.(kithttp.Error); ok {
397 if e, ok := err.(httptransport.Error); ok {
258398 switch e.Domain {
259 case kithttp.DomainDecode:
260 return stdhttp.StatusBadRequest
261 case kithttp.DomainDo:
262 return stdhttp.StatusServiceUnavailable
399 case httptransport.DomainDecode:
400 return http.StatusBadRequest
401 case httptransport.DomainDo:
402 return http.StatusServiceUnavailable
263403 default:
264 return stdhttp.StatusInternalServerError
404 return http.StatusInternalServerError
265405 }
266406 }
267 return stdhttp.StatusInternalServerError
268 }
269 }
407 return http.StatusInternalServerError
408 }
409 }