examples/profilesvc: refactor + add client package
Peter Bourgon
7 years ago
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 | |
1 | 1 | |
2 | 2 | import ( |
3 | "net/url" | |
4 | "strings" | |
5 | ||
6 | "golang.org/x/net/context" | |
7 | ||
3 | 8 | "github.com/go-kit/kit/endpoint" |
4 | "golang.org/x/net/context" | |
9 | httptransport "github.com/go-kit/kit/transport/http" | |
5 | 10 | ) |
6 | 11 | |
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 { | |
55 | 189 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { |
56 | 190 | req := request.(postProfileRequest) |
57 | 191 | e := s.PostProfile(ctx, req.Profile) |
59 | 193 | } |
60 | 194 | } |
61 | 195 | |
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 | ||
62 | 301 | type getProfileRequest struct { |
63 | 302 | ID string |
64 | 303 | } |
70 | 309 | |
71 | 310 | func (r getProfileResponse) error() error { return r.Err } |
72 | 311 | |
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 | ||
81 | 312 | type putProfileRequest struct { |
82 | 313 | ID string |
83 | 314 | Profile Profile |
89 | 320 | |
90 | 321 | func (r putProfileResponse) error() error { return nil } |
91 | 322 | |
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 | ||
100 | 323 | type patchProfileRequest struct { |
101 | 324 | ID string |
102 | 325 | Profile Profile |
108 | 331 | |
109 | 332 | func (r patchProfileResponse) error() error { return r.Err } |
110 | 333 | |
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 | ||
119 | 334 | type deleteProfileRequest struct { |
120 | 335 | ID string |
121 | 336 | } |
125 | 340 | } |
126 | 341 | |
127 | 342 | 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 | } | |
136 | 343 | |
137 | 344 | type getAddressesRequest struct { |
138 | 345 | ProfileID string |
145 | 352 | |
146 | 353 | func (r getAddressesResponse) error() error { return r.Err } |
147 | 354 | |
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 | ||
156 | 355 | type getAddressRequest struct { |
157 | 356 | ProfileID string |
158 | 357 | AddressID string |
165 | 364 | |
166 | 365 | func (r getAddressResponse) error() error { return r.Err } |
167 | 366 | |
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 | ||
176 | 367 | type postAddressRequest struct { |
177 | 368 | ProfileID string |
178 | 369 | Address Address |
184 | 375 | |
185 | 376 | func (r postAddressResponse) error() error { return r.Err } |
186 | 377 | |
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 | ||
195 | 378 | type deleteAddressRequest struct { |
196 | 379 | ProfileID string |
197 | 380 | AddressID string |
202 | 385 | } |
203 | 386 | |
204 | 387 | 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 | 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 | |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "time" |
7 | 7 | "github.com/go-kit/kit/log" |
8 | 8 | ) |
9 | 9 | |
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 | ||
10 | 22 | type loggingMiddleware struct { |
11 | next ProfileService | |
23 | next Service | |
12 | 24 | logger log.Logger |
13 | 25 | } |
14 | 26 |
0 | package main | |
0 | package profilesvc | |
1 | 1 | |
2 | 2 | import ( |
3 | 3 | "errors" |
6 | 6 | "golang.org/x/net/context" |
7 | 7 | ) |
8 | 8 | |
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 { | |
11 | 11 | PostProfile(ctx context.Context, p Profile) error |
12 | 12 | GetProfile(ctx context.Context, id string) (Profile, error) |
13 | 13 | PutProfile(ctx context.Context, id string, p Profile) error |
35 | 35 | } |
36 | 36 | |
37 | 37 | 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") | |
41 | 41 | ) |
42 | 42 | |
43 | 43 | type inmemService struct { |
45 | 45 | m map[string]Profile |
46 | 46 | } |
47 | 47 | |
48 | func newInmemService() ProfileService { | |
48 | func NewInmemService() Service { | |
49 | 49 | return &inmemService{ |
50 | 50 | m: map[string]Profile{}, |
51 | 51 | } |
55 | 55 | s.mtx.Lock() |
56 | 56 | defer s.mtx.Unlock() |
57 | 57 | if _, ok := s.m[p.ID]; ok { |
58 | return errAlreadyExists // POST = create, don't overwrite | |
58 | return ErrAlreadyExists // POST = create, don't overwrite | |
59 | 59 | } |
60 | 60 | s.m[p.ID] = p |
61 | 61 | return nil |
66 | 66 | defer s.mtx.RUnlock() |
67 | 67 | p, ok := s.m[id] |
68 | 68 | if !ok { |
69 | return Profile{}, errNotFound | |
69 | return Profile{}, ErrNotFound | |
70 | 70 | } |
71 | 71 | return p, nil |
72 | 72 | } |
73 | 73 | |
74 | 74 | func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error { |
75 | 75 | if id != p.ID { |
76 | return errInconsistentIDs | |
76 | return ErrInconsistentIDs | |
77 | 77 | } |
78 | 78 | s.mtx.Lock() |
79 | 79 | defer s.mtx.Unlock() |
83 | 83 | |
84 | 84 | func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error { |
85 | 85 | if p.ID != "" && id != p.ID { |
86 | return errInconsistentIDs | |
86 | return ErrInconsistentIDs | |
87 | 87 | } |
88 | 88 | |
89 | 89 | s.mtx.Lock() |
91 | 91 | |
92 | 92 | existing, ok := s.m[id] |
93 | 93 | if !ok { |
94 | return errNotFound // PATCH = update existing, don't create | |
94 | return ErrNotFound // PATCH = update existing, don't create | |
95 | 95 | } |
96 | 96 | |
97 | 97 | // We assume that it's not possible to PATCH the ID, and that it's not |
114 | 114 | s.mtx.Lock() |
115 | 115 | defer s.mtx.Unlock() |
116 | 116 | if _, ok := s.m[id]; !ok { |
117 | return errNotFound | |
117 | return ErrNotFound | |
118 | 118 | } |
119 | 119 | delete(s.m, id) |
120 | 120 | return nil |
125 | 125 | defer s.mtx.RUnlock() |
126 | 126 | p, ok := s.m[profileID] |
127 | 127 | if !ok { |
128 | return []Address{}, errNotFound | |
128 | return []Address{}, ErrNotFound | |
129 | 129 | } |
130 | 130 | return p.Addresses, nil |
131 | 131 | } |
135 | 135 | defer s.mtx.RUnlock() |
136 | 136 | p, ok := s.m[profileID] |
137 | 137 | if !ok { |
138 | return Address{}, errNotFound | |
138 | return Address{}, ErrNotFound | |
139 | 139 | } |
140 | 140 | for _, address := range p.Addresses { |
141 | 141 | if address.ID == addressID { |
142 | 142 | return address, nil |
143 | 143 | } |
144 | 144 | } |
145 | return Address{}, errNotFound | |
145 | return Address{}, ErrNotFound | |
146 | 146 | } |
147 | 147 | |
148 | 148 | func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error { |
150 | 150 | defer s.mtx.Unlock() |
151 | 151 | p, ok := s.m[profileID] |
152 | 152 | if !ok { |
153 | return errNotFound | |
153 | return ErrNotFound | |
154 | 154 | } |
155 | 155 | for _, address := range p.Addresses { |
156 | 156 | if address.ID == a.ID { |
157 | return errAlreadyExists | |
157 | return ErrAlreadyExists | |
158 | 158 | } |
159 | 159 | } |
160 | 160 | p.Addresses = append(p.Addresses, a) |
167 | 167 | defer s.mtx.Unlock() |
168 | 168 | p, ok := s.m[profileID] |
169 | 169 | if !ok { |
170 | return errNotFound | |
170 | return ErrNotFound | |
171 | 171 | } |
172 | 172 | newAddresses := make([]Address, 0, len(p.Addresses)) |
173 | 173 | for _, address := range p.Addresses { |
177 | 177 | newAddresses = append(newAddresses, address) |
178 | 178 | } |
179 | 179 | if len(newAddresses) == len(p.Addresses) { |
180 | return errNotFound | |
180 | return ErrNotFound | |
181 | 181 | } |
182 | 182 | p.Addresses = newAddresses |
183 | 183 | 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. | |
1 | 3 | |
2 | 4 | import ( |
5 | "bytes" | |
3 | 6 | "encoding/json" |
4 | 7 | "errors" |
5 | stdhttp "net/http" | |
8 | "io/ioutil" | |
9 | "net/http" | |
6 | 10 | |
7 | 11 | "github.com/gorilla/mux" |
8 | 12 | "golang.org/x/net/context" |
9 | 13 | |
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" | |
12 | 18 | ) |
13 | 19 | |
14 | 20 | 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)") | |
16 | 24 | ) |
17 | 25 | |
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 { | |
20 | 29 | 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), | |
25 | 34 | } |
26 | 35 | |
27 | 36 | // POST /profiles adds another profile |
34 | 43 | // POST /profiles/:id/addresses add a new address |
35 | 44 | // DELETE /profiles/:id/addresses/:addressID remove an address |
36 | 45 | |
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, | |
40 | 49 | decodePostProfileRequest, |
41 | 50 | 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, | |
47 | 56 | decodeGetProfileRequest, |
48 | 57 | 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, | |
54 | 63 | decodePutProfileRequest, |
55 | 64 | 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, | |
61 | 70 | decodePatchProfileRequest, |
62 | 71 | 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, | |
68 | 77 | decodeDeleteProfileRequest, |
69 | 78 | 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, | |
75 | 84 | decodeGetAddressesRequest, |
76 | 85 | 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, | |
82 | 91 | decodeGetAddressRequest, |
83 | 92 | 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, | |
89 | 98 | decodePostAddressRequest, |
90 | 99 | 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, | |
96 | 105 | decodeDeleteAddressRequest, |
97 | 106 | encodeResponse, |
98 | commonOptions..., | |
107 | options..., | |
99 | 108 | )) |
100 | 109 | return r |
101 | 110 | } |
102 | 111 | |
103 | func decodePostProfileRequest(_ context.Context, r *stdhttp.Request) (request interface{}, err error) { | |
112 | func decodePostProfileRequest(_ context.Context, r *http.Request) (request interface{}, err error) { | |
104 | 113 | var req postProfileRequest |
105 | 114 | if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil { |
106 | 115 | return nil, e |
108 | 117 | return req, nil |
109 | 118 | } |
110 | 119 | |
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 | |
116 | 125 | } |
117 | 126 | return getProfileRequest{ID: id}, nil |
118 | 127 | } |
119 | 128 | |
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 | |
125 | 134 | } |
126 | 135 | var profile Profile |
127 | 136 | if err := json.NewDecoder(r.Body).Decode(&profile); err != nil { |
133 | 142 | }, nil |
134 | 143 | } |
135 | 144 | |
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 | |
141 | 150 | } |
142 | 151 | var profile Profile |
143 | 152 | if err := json.NewDecoder(r.Body).Decode(&profile); err != nil { |
149 | 158 | }, nil |
150 | 159 | } |
151 | 160 | |
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 | |
157 | 166 | } |
158 | 167 | return deleteProfileRequest{ID: id}, nil |
159 | 168 | } |
160 | 169 | |
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 | |
166 | 175 | } |
167 | 176 | return getAddressesRequest{ProfileID: id}, nil |
168 | 177 | } |
169 | 178 | |
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 | |
175 | 184 | } |
176 | 185 | addressID, ok := vars["addressID"] |
177 | 186 | if !ok { |
178 | return nil, errBadRouting | |
187 | return nil, ErrBadRouting | |
179 | 188 | } |
180 | 189 | return getAddressRequest{ |
181 | 190 | ProfileID: id, |
183 | 192 | }, nil |
184 | 193 | } |
185 | 194 | |
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 | |
191 | 200 | } |
192 | 201 | var address Address |
193 | 202 | if err := json.NewDecoder(r.Body).Decode(&address); err != nil { |
199 | 208 | }, nil |
200 | 209 | } |
201 | 210 | |
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 | |
207 | 216 | } |
208 | 217 | addressID, ok := vars["addressID"] |
209 | 218 | if !ok { |
210 | return nil, errBadRouting | |
219 | return nil, ErrBadRouting | |
211 | 220 | } |
212 | 221 | return deleteAddressRequest{ |
213 | 222 | ProfileID: id, |
215 | 224 | }, nil |
216 | 225 | } |
217 | 226 | |
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. | |
222 | 347 | type errorer interface { |
223 | 348 | error() error |
224 | 349 | } |
225 | 350 | |
226 | 351 | // 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 { | |
231 | 356 | if e, ok := response.(errorer); ok && e.error() != nil { |
232 | 357 | // Not a Go kit transport error, but a business-logic error. |
233 | 358 | // Provide those as HTTP errors. |
234 | 359 | encodeError(ctx, e.error(), w) |
235 | 360 | return nil |
236 | 361 | } |
362 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
237 | 363 | return json.NewEncoder(w).Encode(response) |
238 | 364 | } |
239 | 365 | |
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) { | |
241 | 380 | if err == nil { |
242 | 381 | panic("encodeError with nil error") |
243 | 382 | } |
383 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
244 | 384 | w.WriteHeader(codeFrom(err)) |
245 | 385 | json.NewEncoder(w).Encode(map[string]interface{}{ |
246 | 386 | "error": err.Error(), |
249 | 389 | |
250 | 390 | func codeFrom(err error) int { |
251 | 391 | 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 | |
256 | 396 | default: |
257 | if e, ok := err.(kithttp.Error); ok { | |
397 | if e, ok := err.(httptransport.Error); ok { | |
258 | 398 | 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 | |
263 | 403 | default: |
264 | return stdhttp.StatusInternalServerError | |
404 | return http.StatusInternalServerError | |
265 | 405 | } |
266 | 406 | } |
267 | return stdhttp.StatusInternalServerError | |
268 | } | |
269 | } | |
407 | return http.StatusInternalServerError | |
408 | } | |
409 | } |