forked from go-kit/kit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
service.go
185 lines (165 loc) · 4.47 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package profilesvc
import (
"context"
"errors"
"sync"
)
// Service is a simple CRUD interface for user profiles.
type Service interface {
PostProfile(ctx context.Context, p Profile) error
GetProfile(ctx context.Context, id string) (Profile, error)
PutProfile(ctx context.Context, id string, p Profile) error
PatchProfile(ctx context.Context, id string, p Profile) error
DeleteProfile(ctx context.Context, id string) error
GetAddresses(ctx context.Context, profileID string) ([]Address, error)
GetAddress(ctx context.Context, profileID string, addressID string) (Address, error)
PostAddress(ctx context.Context, profileID string, a Address) error
DeleteAddress(ctx context.Context, profileID string, addressID string) error
}
// Profile represents a single user profile.
// ID should be globally unique.
type Profile struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Addresses []Address `json:"addresses,omitempty"`
}
// Address is a field of a user profile.
// ID should be unique within the profile (at a minimum).
type Address struct {
ID string `json:"id"`
Location string `json:"location,omitempty"`
}
var (
ErrInconsistentIDs = errors.New("inconsistent IDs")
ErrAlreadyExists = errors.New("already exists")
ErrNotFound = errors.New("not found")
)
type inmemService struct {
mtx sync.RWMutex
m map[string]Profile
}
func NewInmemService() Service {
return &inmemService{
m: map[string]Profile{},
}
}
func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[p.ID]; ok {
return ErrAlreadyExists // POST = create, don't overwrite
}
s.m[p.ID] = p
return nil
}
func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[id]
if !ok {
return Profile{}, ErrNotFound
}
return p, nil
}
func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
if id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
s.m[id] = p // PUT = create or update
return nil
}
func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
if p.ID != "" && id != p.ID {
return ErrInconsistentIDs
}
s.mtx.Lock()
defer s.mtx.Unlock()
existing, ok := s.m[id]
if !ok {
return ErrNotFound // PATCH = update existing, don't create
}
// We assume that it's not possible to PATCH the ID, and that it's not
// possible to PATCH any field to its zero value. That is, the zero value
// means not specified. The way around this is to use e.g. Name *string in
// the Profile definition. But since this is just a demonstrative example,
// I'm leaving that out.
if p.Name != "" {
existing.Name = p.Name
}
if len(p.Addresses) > 0 {
existing.Addresses = p.Addresses
}
s.m[id] = existing
return nil
}
func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
if _, ok := s.m[id]; !ok {
return ErrNotFound
}
delete(s.m, id)
return nil
}
func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return []Address{}, ErrNotFound
}
return p.Addresses, nil
}
func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
s.mtx.RLock()
defer s.mtx.RUnlock()
p, ok := s.m[profileID]
if !ok {
return Address{}, ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == addressID {
return address, nil
}
}
return Address{}, ErrNotFound
}
func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
for _, address := range p.Addresses {
if address.ID == a.ID {
return ErrAlreadyExists
}
}
p.Addresses = append(p.Addresses, a)
s.m[profileID] = p
return nil
}
func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
s.mtx.Lock()
defer s.mtx.Unlock()
p, ok := s.m[profileID]
if !ok {
return ErrNotFound
}
newAddresses := make([]Address, 0, len(p.Addresses))
for _, address := range p.Addresses {
if address.ID == addressID {
continue // delete
}
newAddresses = append(newAddresses, address)
}
if len(newAddresses) == len(p.Addresses) {
return ErrNotFound
}
p.Addresses = newAddresses
s.m[profileID] = p
return nil
}