Package list golang-github-go-kit-kit / 5d4daec
examples/profilesvc: via gorilla/mux Peter Bourgon 5 years ago
7 changed file(s) with 777 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
00 examples/addsvc/addsvc
11 examples/addsvc/client/client
2 examples/profilesvc/profilesvc
23 examples/stringsvc1/stringsvc1
34 examples/stringsvc2/stringsvc2
45 examples/stringsvc3/stringsvc3
0 # profilesvc
1
2 This example demonstrates how to use Go kit to implement a REST-y HTTP service.
3 It leverages the excellent [gorilla mux package](https://github.com/gorilla/mux) for routing.
0 package main
1
2 import (
3 "github.com/go-kit/kit/endpoint"
4 "golang.org/x/net/context"
5 )
6
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 methods, we have two options. We can
44 // return the error via the endpoint itself. That makes certain things a
45 // little bit easier, like providing non-200 HTTP responses to the client. But
46 // Go kit assumes that endpoint errors are (or may be treated as) transport-
47 // level errors. For example, an endpoint error will count against a circuit
48 // breaker error count. Therefore, it's almost certainly better to return
49 // service method (business logic) errors in the response object. This means
50 // we have to do a bit more work in the HTTP response encoder to detect e.g. a
51 // not-found error and provide a proper HTTP status code.
52
53 func makePostProfileEndpoint(s ProfileService) endpoint.Endpoint {
54 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
55 req := request.(postProfileRequest)
56 e := s.PostProfile(ctx, req.Profile)
57 return postProfileResponse{Err: e}, nil
58 }
59 }
60
61 type getProfileRequest struct {
62 ID string
63 }
64
65 type getProfileResponse struct {
66 Profile Profile `json:"profile,omitempty"`
67 Err error `json:"err,omitempty"`
68 }
69
70 func (r getProfileResponse) error() error { return r.Err }
71
72 func makeGetProfileEndpoint(s ProfileService) endpoint.Endpoint {
73 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
74 req := request.(getProfileRequest)
75 p, e := s.GetProfile(ctx, req.ID)
76 return getProfileResponse{Profile: p, Err: e}, nil
77 }
78 }
79
80 type putProfileRequest struct {
81 ID string
82 Profile Profile
83 }
84
85 type putProfileResponse struct {
86 Err error `json:"err,omitempty"`
87 }
88
89 func (r putProfileResponse) error() error { return nil }
90
91 func makePutProfileEndpoint(s ProfileService) endpoint.Endpoint {
92 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
93 req := request.(putProfileRequest)
94 e := s.PutProfile(ctx, req.ID, req.Profile)
95 return putProfileResponse{Err: e}, nil
96 }
97 }
98
99 type patchProfileRequest struct {
100 ID string
101 Profile Profile
102 }
103
104 type patchProfileResponse struct {
105 Err error `json:"err,omitempty"`
106 }
107
108 func (r patchProfileResponse) error() error { return r.Err }
109
110 func makePatchProfileEndpoint(s ProfileService) endpoint.Endpoint {
111 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
112 req := request.(patchProfileRequest)
113 e := s.PatchProfile(ctx, req.ID, req.Profile)
114 return patchProfileResponse{Err: e}, nil
115 }
116 }
117
118 type deleteProfileRequest struct {
119 ID string
120 }
121
122 type deleteProfileResponse struct {
123 Err error `json:"err,omitempty"`
124 }
125
126 func (r deleteProfileResponse) error() error { return r.Err }
127
128 func makeDeleteProfileEndpoint(s ProfileService) endpoint.Endpoint {
129 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
130 req := request.(deleteProfileRequest)
131 e := s.DeleteProfile(ctx, req.ID)
132 return deleteProfileResponse{Err: e}, nil
133 }
134 }
135
136 type getAddressesRequest struct {
137 ProfileID string
138 }
139
140 type getAddressesResponse struct {
141 Addresses []Address `json:"addresses,omitempty"`
142 Err error `json:"err,omitempty"`
143 }
144
145 func (r getAddressesResponse) error() error { return r.Err }
146
147 func makeGetAddressesEndpoint(s ProfileService) endpoint.Endpoint {
148 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
149 req := request.(getAddressesRequest)
150 a, e := s.GetAddresses(ctx, req.ProfileID)
151 return getAddressesResponse{Addresses: a, Err: e}, nil
152 }
153 }
154
155 type getAddressRequest struct {
156 ProfileID string
157 AddressID string
158 }
159
160 type getAddressResponse struct {
161 Address Address `json:"address,omitempty"`
162 Err error `json:"err,omitempty"`
163 }
164
165 func (r getAddressResponse) error() error { return r.Err }
166
167 func makeGetAddressEndpoint(s ProfileService) endpoint.Endpoint {
168 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
169 req := request.(getAddressRequest)
170 a, e := s.GetAddress(ctx, req.ProfileID, req.AddressID)
171 return getAddressResponse{Address: a, Err: e}, nil
172 }
173 }
174
175 type postAddressRequest struct {
176 ProfileID string
177 Address Address
178 }
179
180 type postAddressResponse struct {
181 Err error `json:"err,omitempty"`
182 }
183
184 func (r postAddressResponse) error() error { return r.Err }
185
186 func makePostAddressEndpoint(s ProfileService) endpoint.Endpoint {
187 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
188 req := request.(postAddressRequest)
189 e := s.PostAddress(ctx, req.ProfileID, req.Address)
190 return postAddressResponse{Err: e}, nil
191 }
192 }
193
194 type deleteAddressRequest struct {
195 ProfileID string
196 AddressID string
197 }
198
199 type deleteAddressResponse struct {
200 Err error `json:"err,omitempty"`
201 }
202
203 func (r deleteAddressResponse) error() error { return r.Err }
204
205 func makeDeleteAddressEndpoint(s ProfileService) endpoint.Endpoint {
206 return func(ctx context.Context, request interface{}) (response interface{}, err error) {
207 req := request.(deleteAddressRequest)
208 e := s.DeleteAddress(ctx, req.ProfileID, req.AddressID)
209 return deleteAddressResponse{Err: e}, nil
210 }
211 }
0 package main
1
2 import (
3 "encoding/json"
4 "errors"
5 stdhttp "net/http"
6
7 "github.com/gorilla/mux"
8 "golang.org/x/net/context"
9
10 kitlog "github.com/go-kit/kit/log"
11 kithttp "github.com/go-kit/kit/transport/http"
12 )
13
14 var (
15 errBadRouting = errors.New("inconsistent mapping between route and handler (programmer error)")
16 )
17
18 func makeHandler(ctx context.Context, s ProfileService, logger kitlog.Logger) stdhttp.Handler {
19 e := makeEndpoints(s)
20 r := mux.NewRouter()
21
22 commonOptions := []kithttp.ServerOption{
23 kithttp.ServerErrorLogger(logger),
24 kithttp.ServerErrorEncoder(encodeError),
25 }
26
27 // POST /profiles adds another profile
28 // GET /profiles/:id retrieves the given profile by id
29 // PUT /profiles/:id post updated profile information about the profile
30 // PATCH /profiles/:id partial updated profile information
31 // DELETE /profiles/:id remove the given profile
32 // GET /profiles/:id/addresses retrieve addresses associated with the profile
33 // GET /profiles/:id/addresses/:addressID retrieve a particular profile address
34 // POST /profiles/:id/addresses add a new address
35 // DELETE /profiles/:id/addresses/:addressID remove an address
36
37 r.Methods("POST").Path("/profiles/").Handler(kithttp.NewServer(
38 ctx,
39 e.postProfileEndpoint,
40 decodePostProfileRequest,
41 encodeResponse,
42 commonOptions...,
43 ))
44 r.Methods("GET").Path("/profiles/{id}").Handler(kithttp.NewServer(
45 ctx,
46 e.getProfileEndpoint,
47 decodeGetProfileRequest,
48 encodeResponse,
49 commonOptions...,
50 ))
51 r.Methods("PUT").Path("/profiles/{id}").Handler(kithttp.NewServer(
52 ctx,
53 e.putProfileEndpoint,
54 decodePutProfileRequest,
55 encodeResponse,
56 commonOptions...,
57 ))
58 r.Methods("PATCH").Path("/profiles/{id}").Handler(kithttp.NewServer(
59 ctx,
60 e.patchProfileEndpoint,
61 decodePatchProfileRequest,
62 encodeResponse,
63 commonOptions...,
64 ))
65 r.Methods("DELETE").Path("/profiles/{id}").Handler(kithttp.NewServer(
66 ctx,
67 e.deleteProfileEndpoint,
68 decodeDeleteProfileRequest,
69 encodeResponse,
70 commonOptions...,
71 ))
72 r.Methods("GET").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
73 ctx,
74 e.getAddressesEndpoint,
75 decodeGetAddressesRequest,
76 encodeResponse,
77 commonOptions...,
78 ))
79 r.Methods("GET").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
80 ctx,
81 e.getAddressEndpoint,
82 decodeGetAddressRequest,
83 encodeResponse,
84 commonOptions...,
85 ))
86 r.Methods("POST").Path("/profiles/{id}/addresses/").Handler(kithttp.NewServer(
87 ctx,
88 e.postAddressEndpoint,
89 decodePostAddressRequest,
90 encodeResponse,
91 commonOptions...,
92 ))
93 r.Methods("DELETE").Path("/profiles/{id}/addresses/{addressID}").Handler(kithttp.NewServer(
94 ctx,
95 e.deleteAddressEndpoint,
96 decodeDeleteAddressRequest,
97 encodeResponse,
98 commonOptions...,
99 ))
100 return r
101 }
102
103 func decodePostProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
104 var req postProfileRequest
105 if e := json.NewDecoder(r.Body).Decode(&req.Profile); e != nil {
106 return nil, e
107 }
108 return req, nil
109 }
110
111 func decodeGetProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
112 vars := mux.Vars(r)
113 id, ok := vars["id"]
114 if !ok {
115 return nil, errBadRouting
116 }
117 return getProfileRequest{ID: id}, nil
118 }
119
120 func decodePutProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
121 vars := mux.Vars(r)
122 id, ok := vars["id"]
123 if !ok {
124 return nil, errBadRouting
125 }
126 var profile Profile
127 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
128 return nil, err
129 }
130 return putProfileRequest{
131 ID: id,
132 Profile: profile,
133 }, nil
134 }
135
136 func decodePatchProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
137 vars := mux.Vars(r)
138 id, ok := vars["id"]
139 if !ok {
140 return nil, errBadRouting
141 }
142 var profile Profile
143 if err := json.NewDecoder(r.Body).Decode(&profile); err != nil {
144 return nil, err
145 }
146 return patchProfileRequest{
147 ID: id,
148 Profile: profile,
149 }, nil
150 }
151
152 func decodeDeleteProfileRequest(r *stdhttp.Request) (request interface{}, err error) {
153 vars := mux.Vars(r)
154 id, ok := vars["id"]
155 if !ok {
156 return nil, errBadRouting
157 }
158 return deleteProfileRequest{ID: id}, nil
159 }
160
161 func decodeGetAddressesRequest(r *stdhttp.Request) (request interface{}, err error) {
162 vars := mux.Vars(r)
163 id, ok := vars["id"]
164 if !ok {
165 return nil, errBadRouting
166 }
167 return getAddressesRequest{ProfileID: id}, nil
168 }
169
170 func decodeGetAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
171 vars := mux.Vars(r)
172 id, ok := vars["id"]
173 if !ok {
174 return nil, errBadRouting
175 }
176 addressID, ok := vars["addressID"]
177 if !ok {
178 return nil, errBadRouting
179 }
180 return getAddressRequest{
181 ProfileID: id,
182 AddressID: addressID,
183 }, nil
184 }
185
186 func decodePostAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
187 vars := mux.Vars(r)
188 id, ok := vars["id"]
189 if !ok {
190 return nil, errBadRouting
191 }
192 var address Address
193 if err := json.NewDecoder(r.Body).Decode(&address); err != nil {
194 return nil, err
195 }
196 return postAddressRequest{
197 ProfileID: id,
198 Address: address,
199 }, nil
200 }
201
202 func decodeDeleteAddressRequest(r *stdhttp.Request) (request interface{}, err error) {
203 vars := mux.Vars(r)
204 id, ok := vars["id"]
205 if !ok {
206 return nil, errBadRouting
207 }
208 addressID, ok := vars["addressID"]
209 if !ok {
210 return nil, errBadRouting
211 }
212 return deleteAddressRequest{
213 ProfileID: id,
214 AddressID: addressID,
215 }, nil
216 }
217
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 // endpoint.go.
222 type errorer interface {
223 error() error
224 }
225
226 // 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(w stdhttp.ResponseWriter, response interface{}) error {
231 if e, ok := response.(errorer); ok && e.error() != nil {
232 // Not a Go kit transport error, but a business-logic error.
233 // Provide those as HTTP errors.
234 encodeError(w, e.error())
235 return nil
236 }
237 return json.NewEncoder(w).Encode(response)
238 }
239
240 func encodeError(w stdhttp.ResponseWriter, err error) {
241 w.WriteHeader(codeFrom(err))
242 json.NewEncoder(w).Encode(map[string]interface{}{
243 "error": err.Error(),
244 })
245 }
246
247 func codeFrom(err error) int {
248 switch err {
249 case nil:
250 return stdhttp.StatusOK
251 case errNotFound:
252 return stdhttp.StatusNotFound
253 case errAlreadyExists, errInconsistentIDs:
254 return stdhttp.StatusBadRequest
255 default:
256 if _, ok := err.(kithttp.BadRequestError); ok {
257 return stdhttp.StatusBadRequest
258 }
259 return stdhttp.StatusInternalServerError
260 }
261 }
0 package main
1
2 import (
3 "time"
4
5 "golang.org/x/net/context"
6
7 "github.com/go-kit/kit/log"
8 )
9
10 type loggingMiddleware struct {
11 next ProfileService
12 logger log.Logger
13 }
14
15 func (mw loggingMiddleware) PostProfile(ctx context.Context, p Profile) (err error) {
16 defer func(begin time.Time) {
17 mw.logger.Log("method", "PostProfile", "id", p.ID, "took", time.Since(begin), "err", err)
18 }(time.Now())
19 return mw.next.PostProfile(ctx, p)
20 }
21
22 func (mw loggingMiddleware) GetProfile(ctx context.Context, id string) (p Profile, err error) {
23 defer func(begin time.Time) {
24 mw.logger.Log("method", "GetProfile", "id", id, "took", time.Since(begin), "err", err)
25 }(time.Now())
26 return mw.next.GetProfile(ctx, id)
27 }
28
29 func (mw loggingMiddleware) PutProfile(ctx context.Context, id string, p Profile) (err error) {
30 defer func(begin time.Time) {
31 mw.logger.Log("method", "PutProfile", "id", id, "took", time.Since(begin), "err", err)
32 }(time.Now())
33 return mw.next.PutProfile(ctx, id, p)
34 }
35
36 func (mw loggingMiddleware) PatchProfile(ctx context.Context, id string, p Profile) (err error) {
37 defer func(begin time.Time) {
38 mw.logger.Log("method", "PatchProfile", "id", id, "took", time.Since(begin), "err", err)
39 }(time.Now())
40 return mw.next.PatchProfile(ctx, id, p)
41 }
42
43 func (mw loggingMiddleware) DeleteProfile(ctx context.Context, id string) (err error) {
44 defer func(begin time.Time) {
45 mw.logger.Log("method", "DeleteProfile", "id", id, "took", time.Since(begin), "err", err)
46 }(time.Now())
47 return mw.next.DeleteProfile(ctx, id)
48 }
49
50 func (mw loggingMiddleware) GetAddresses(ctx context.Context, profileID string) (addresses []Address, err error) {
51 defer func(begin time.Time) {
52 mw.logger.Log("method", "GetAddresses", "profileID", profileID, "took", time.Since(begin), "err", err)
53 }(time.Now())
54 return mw.next.GetAddresses(ctx, profileID)
55 }
56
57 func (mw loggingMiddleware) GetAddress(ctx context.Context, profileID string, addressID string) (a Address, err error) {
58 defer func(begin time.Time) {
59 mw.logger.Log("method", "GetAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
60 }(time.Now())
61 return mw.next.GetAddress(ctx, profileID, addressID)
62 }
63
64 func (mw loggingMiddleware) PostAddress(ctx context.Context, profileID string, a Address) (err error) {
65 defer func(begin time.Time) {
66 mw.logger.Log("method", "PostAddress", "profileID", profileID, "took", time.Since(begin), "err", err)
67 }(time.Now())
68 return mw.next.PostAddress(ctx, profileID, a)
69 }
70
71 func (mw loggingMiddleware) DeleteAddress(ctx context.Context, profileID string, addressID string) (err error) {
72 defer func(begin time.Time) {
73 mw.logger.Log("method", "DeleteAddress", "profileID", profileID, "addressID", addressID, "took", time.Since(begin), "err", err)
74 }(time.Now())
75 return mw.next.DeleteAddress(ctx, profileID, addressID)
76 }
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
1
2 import (
3 "errors"
4 "sync"
5
6 "golang.org/x/net/context"
7 )
8
9 // ProfileService is a simple CRUD interface for user profiles.
10 type ProfileService interface {
11 PostProfile(ctx context.Context, p Profile) error
12 GetProfile(ctx context.Context, id string) (Profile, error)
13 PutProfile(ctx context.Context, id string, p Profile) error
14 PatchProfile(ctx context.Context, id string, p Profile) error
15 DeleteProfile(ctx context.Context, id string) error
16 GetAddresses(ctx context.Context, profileID string) ([]Address, error)
17 GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
18 PostAddress(ctx context.Context, profileID string, a Address) error
19 DeleteAddress(ctx context.Context, profileID string, addressID string) error
20 }
21
22 // Profile represents a single user profile.
23 // ID should be globally unique.
24 type Profile struct {
25 ID string `json:"id"`
26 Name string `json:"name,omitempty"`
27 Addresses []Address `json:"addresses,omitempty"`
28 }
29
30 // Address is a field of a user profile.
31 // ID should be unique within the profile (at a minimum).
32 type Address struct {
33 ID string `json:"id"`
34 Location string `json:"location,omitempty"`
35 }
36
37 var (
38 errInconsistentIDs = errors.New("inconsistent IDs")
39 errAlreadyExists = errors.New("already exists")
40 errNotFound = errors.New("not found")
41 )
42
43 type inmemService struct {
44 mtx sync.RWMutex
45 m map[string]Profile
46 }
47
48 func newInmemService() ProfileService {
49 return &inmemService{
50 m: map[string]Profile{},
51 }
52 }
53
54 func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
55 s.mtx.Lock()
56 defer s.mtx.Unlock()
57 if _, ok := s.m[p.ID]; ok {
58 return errAlreadyExists
59 }
60 s.m[p.ID] = p
61 return nil
62 }
63
64 func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
65 s.mtx.RLock()
66 defer s.mtx.RUnlock()
67 p, ok := s.m[id]
68 if !ok {
69 return Profile{}, errNotFound
70 }
71 return p, nil
72 }
73
74 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
75 if id != p.ID {
76 return errInconsistentIDs
77 }
78 s.mtx.Lock()
79 defer s.mtx.Unlock()
80 if _, ok := s.m[id]; ok {
81 return errAlreadyExists
82 }
83 s.m[id] = p
84 return nil
85 }
86
87 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
88 return s.PutProfile(ctx, id, p) // perhaps more granular behavior is needed here
89 }
90
91 func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
92 s.mtx.Lock()
93 defer s.mtx.Unlock()
94 if _, ok := s.m[id]; !ok {
95 return errNotFound
96 }
97 delete(s.m, id)
98 return nil
99 }
100
101 func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
102 s.mtx.RLock()
103 defer s.mtx.RUnlock()
104 p, ok := s.m[profileID]
105 if !ok {
106 return []Address{}, errNotFound
107 }
108 return p.Addresses, nil
109 }
110
111 func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
112 s.mtx.RLock()
113 defer s.mtx.RUnlock()
114 p, ok := s.m[profileID]
115 if !ok {
116 return Address{}, errNotFound
117 }
118 for _, address := range p.Addresses {
119 if address.ID == addressID {
120 return address, nil
121 }
122 }
123 return Address{}, errNotFound
124 }
125
126 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
127 s.mtx.Lock()
128 defer s.mtx.Unlock()
129 p, ok := s.m[profileID]
130 if !ok {
131 return errNotFound
132 }
133 for _, address := range p.Addresses {
134 if address.ID == a.ID {
135 return errAlreadyExists
136 }
137 }
138 p.Addresses = append(p.Addresses, a)
139 s.m[profileID] = p
140 return nil
141 }
142
143 func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
144 s.mtx.Lock()
145 defer s.mtx.Unlock()
146 p, ok := s.m[profileID]
147 if !ok {
148 return errNotFound
149 }
150 newAddresses := make([]Address, 0, len(p.Addresses))
151 for _, address := range p.Addresses {
152 if address.ID == addressID {
153 continue // delete
154 }
155 newAddresses = append(newAddresses, address)
156 }
157 if len(newAddresses) == len(p.Addresses) {
158 return errNotFound
159 }
160 p.Addresses = newAddresses
161 s.m[profileID] = p
162 return nil
163 }