Merge pull request #216 from go-kit/fix-small-logic-error-in-profilesvc
Fix small logic error in profilesvc transport
Peter Bourgon
8 years ago
40 | 40 | |
41 | 41 | func (r postProfileResponse) error() error { return r.Err } |
42 | 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. | |
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. | |
52 | 53 | |
53 | 54 | func makePostProfileEndpoint(s ProfileService) endpoint.Endpoint { |
54 | 55 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { |
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 | "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 | "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 // POST = create, don't overwrite | |
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 | s.m[id] = p // PUT = create or update | |
81 | return nil | |
82 | } | |
83 | ||
84 | func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error { | |
85 | if p.ID != "" && id != p.ID { | |
86 | return errInconsistentIDs | |
87 | } | |
88 | ||
89 | s.mtx.Lock() | |
90 | defer s.mtx.Unlock() | |
91 | ||
92 | existing, ok := s.m[id] | |
93 | if !ok { | |
94 | return errNotFound // PATCH = update existing, don't create | |
95 | } | |
96 | ||
97 | // We assume that it's not possible to PATCH the ID, and that it's not | |
98 | // possible to PATCH any field to its zero value. That is, the zero value | |
99 | // means not specified. The way around this is to use e.g. Name *string in | |
100 | // the Profile definition. But since this is just a demonstrative example, | |
101 | // I'm leaving that out. | |
102 | ||
103 | if p.Name != "" { | |
104 | existing.Name = p.Name | |
105 | } | |
106 | if len(p.Addresses) > 0 { | |
107 | existing.Addresses = p.Addresses | |
108 | } | |
109 | s.m[id] = existing | |
110 | return nil | |
111 | } | |
112 | ||
113 | func (s *inmemService) DeleteProfile(ctx context.Context, id string) error { | |
114 | s.mtx.Lock() | |
115 | defer s.mtx.Unlock() | |
116 | if _, ok := s.m[id]; !ok { | |
117 | return errNotFound | |
118 | } | |
119 | delete(s.m, id) | |
120 | return nil | |
121 | } | |
122 | ||
123 | func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) { | |
124 | s.mtx.RLock() | |
125 | defer s.mtx.RUnlock() | |
126 | p, ok := s.m[profileID] | |
127 | if !ok { | |
128 | return []Address{}, errNotFound | |
129 | } | |
130 | return p.Addresses, nil | |
131 | } | |
132 | ||
133 | func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) { | |
134 | s.mtx.RLock() | |
135 | defer s.mtx.RUnlock() | |
136 | p, ok := s.m[profileID] | |
137 | if !ok { | |
138 | return Address{}, errNotFound | |
139 | } | |
140 | for _, address := range p.Addresses { | |
141 | if address.ID == addressID { | |
142 | return address, nil | |
143 | } | |
144 | } | |
145 | return Address{}, errNotFound | |
146 | } | |
147 | ||
148 | func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error { | |
149 | s.mtx.Lock() | |
150 | defer s.mtx.Unlock() | |
151 | p, ok := s.m[profileID] | |
152 | if !ok { | |
153 | return errNotFound | |
154 | } | |
155 | for _, address := range p.Addresses { | |
156 | if address.ID == a.ID { | |
157 | return errAlreadyExists | |
158 | } | |
159 | } | |
160 | p.Addresses = append(p.Addresses, a) | |
161 | s.m[profileID] = p | |
162 | return nil | |
163 | } | |
164 | ||
165 | func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error { | |
166 | s.mtx.Lock() | |
167 | defer s.mtx.Unlock() | |
168 | p, ok := s.m[profileID] | |
169 | if !ok { | |
170 | return errNotFound | |
171 | } | |
172 | newAddresses := make([]Address, 0, len(p.Addresses)) | |
173 | for _, address := range p.Addresses { | |
174 | if address.ID == addressID { | |
175 | continue // delete | |
176 | } | |
177 | newAddresses = append(newAddresses, address) | |
178 | } | |
179 | if len(newAddresses) == len(p.Addresses) { | |
180 | return errNotFound | |
181 | } | |
182 | p.Addresses = newAddresses | |
183 | s.m[profileID] = p | |
184 | return nil | |
185 | } |
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 // POST = create, don't overwrite | |
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 | s.m[id] = p // PUT = create or update | |
81 | return nil | |
82 | } | |
83 | ||
84 | func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error { | |
85 | if p.ID != "" && id != p.ID { | |
86 | return errInconsistentIDs | |
87 | } | |
88 | ||
89 | s.mtx.Lock() | |
90 | defer s.mtx.Unlock() | |
91 | ||
92 | existing, ok := s.m[id] | |
93 | if !ok { | |
94 | return errNotFound // PATCH = update existing, don't create | |
95 | } | |
96 | ||
97 | // We assume that it's not possible to PATCH the ID, and that it's not | |
98 | // possible to PATCH any field to its zero value. That is, the zero value | |
99 | // means not specified. The way around this is to use e.g. Name *string in | |
100 | // the Profile definition. But since this is just a demonstrative example, | |
101 | // I'm leaving that out. | |
102 | ||
103 | if p.Name != "" { | |
104 | existing.Name = p.Name | |
105 | } | |
106 | if len(p.Addresses) > 0 { | |
107 | existing.Addresses = p.Addresses | |
108 | } | |
109 | s.m[id] = existing | |
110 | return nil | |
111 | } | |
112 | ||
113 | func (s *inmemService) DeleteProfile(ctx context.Context, id string) error { | |
114 | s.mtx.Lock() | |
115 | defer s.mtx.Unlock() | |
116 | if _, ok := s.m[id]; !ok { | |
117 | return errNotFound | |
118 | } | |
119 | delete(s.m, id) | |
120 | return nil | |
121 | } | |
122 | ||
123 | func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) { | |
124 | s.mtx.RLock() | |
125 | defer s.mtx.RUnlock() | |
126 | p, ok := s.m[profileID] | |
127 | if !ok { | |
128 | return []Address{}, errNotFound | |
129 | } | |
130 | return p.Addresses, nil | |
131 | } | |
132 | ||
133 | func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) { | |
134 | s.mtx.RLock() | |
135 | defer s.mtx.RUnlock() | |
136 | p, ok := s.m[profileID] | |
137 | if !ok { | |
138 | return Address{}, errNotFound | |
139 | } | |
140 | for _, address := range p.Addresses { | |
141 | if address.ID == addressID { | |
142 | return address, nil | |
143 | } | |
144 | } | |
145 | return Address{}, errNotFound | |
146 | } | |
147 | ||
148 | func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error { | |
149 | s.mtx.Lock() | |
150 | defer s.mtx.Unlock() | |
151 | p, ok := s.m[profileID] | |
152 | if !ok { | |
153 | return errNotFound | |
154 | } | |
155 | for _, address := range p.Addresses { | |
156 | if address.ID == a.ID { | |
157 | return errAlreadyExists | |
158 | } | |
159 | } | |
160 | p.Addresses = append(p.Addresses, a) | |
161 | s.m[profileID] = p | |
162 | return nil | |
163 | } | |
164 | ||
165 | func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error { | |
166 | s.mtx.Lock() | |
167 | defer s.mtx.Unlock() | |
168 | p, ok := s.m[profileID] | |
169 | if !ok { | |
170 | return errNotFound | |
171 | } | |
172 | newAddresses := make([]Address, 0, len(p.Addresses)) | |
173 | for _, address := range p.Addresses { | |
174 | if address.ID == addressID { | |
175 | continue // delete | |
176 | } | |
177 | newAddresses = append(newAddresses, address) | |
178 | } | |
179 | if len(newAddresses) == len(p.Addresses) { | |
180 | return errNotFound | |
181 | } | |
182 | p.Addresses = newAddresses | |
183 | s.m[profileID] = p | |
184 | return nil | |
185 | } |
218 | 218 | // errorer is implemented by all concrete response types. It allows us to |
219 | 219 | // change the HTTP response code without needing to trigger an endpoint |
220 | 220 | // (transport-level) error. For more information, read the big comment in |
221 | // endpoint.go. | |
221 | // endpoints.go. | |
222 | 222 | type errorer interface { |
223 | 223 | error() error |
224 | 224 | } |
238 | 238 | } |
239 | 239 | |
240 | 240 | func encodeError(w stdhttp.ResponseWriter, err error) { |
241 | if err == nil { | |
242 | panic("encodeError with nil error") | |
243 | } | |
241 | 244 | w.WriteHeader(codeFrom(err)) |
242 | 245 | json.NewEncoder(w).Encode(map[string]interface{}{ |
243 | 246 | "error": err.Error(), |
246 | 249 | |
247 | 250 | func codeFrom(err error) int { |
248 | 251 | switch err { |
249 | case nil: | |
250 | return stdhttp.StatusOK | |
251 | 252 | case errNotFound: |
252 | 253 | return stdhttp.StatusNotFound |
253 | 254 | case errAlreadyExists, errInconsistentIDs: |