9 // Service is a simple CRUD interface for user profiles.
10 type Service 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
22 // Profile represents a single user profile.
23 // ID should be globally unique.
26 Name string `json:"name,omitempty"`
27 Addresses []Address `json:"addresses,omitempty"`
30 // Address is a field of a user profile.
31 // ID should be unique within the profile (at a minimum).
34 Location string `json:"location,omitempty"`
38 ErrInconsistentIDs = errors.New("inconsistent IDs")
39 ErrAlreadyExists = errors.New("already exists")
40 ErrNotFound = errors.New("not found")
43 type inmemService struct {
48 func NewInmemService() Service {
50 m: map[string]Profile{},
54 func (s *inmemService) PostProfile(ctx context.Context, p Profile) error {
57 if _, ok := s.m[p.ID]; ok {
58 return ErrAlreadyExists // POST = create, don't overwrite
64 func (s *inmemService) GetProfile(ctx context.Context, id string) (Profile, error) {
69 return Profile{}, ErrNotFound
74 func (s *inmemService) PutProfile(ctx context.Context, id string, p Profile) error {
76 return ErrInconsistentIDs
80 s.m[id] = p // PUT = create or update
84 func (s *inmemService) PatchProfile(ctx context.Context, id string, p Profile) error {
85 if p.ID != "" && id != p.ID {
86 return ErrInconsistentIDs
92 existing, ok := s.m[id]
94 return ErrNotFound // PATCH = update existing, don't create
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.
104 existing.Name = p.Name
106 if len(p.Addresses) > 0 {
107 existing.Addresses = p.Addresses
113 func (s *inmemService) DeleteProfile(ctx context.Context, id string) error {
116 if _, ok := s.m[id]; !ok {
123 func (s *inmemService) GetAddresses(ctx context.Context, profileID string) ([]Address, error) {
125 defer s.mtx.RUnlock()
126 p, ok := s.m[profileID]
128 return []Address{}, ErrNotFound
130 return p.Addresses, nil
133 func (s *inmemService) GetAddress(ctx context.Context, profileID string, addressID string) (Address, error) {
135 defer s.mtx.RUnlock()
136 p, ok := s.m[profileID]
138 return Address{}, ErrNotFound
140 for _, address := range p.Addresses {
141 if address.ID == addressID {
145 return Address{}, ErrNotFound
148 func (s *inmemService) PostAddress(ctx context.Context, profileID string, a Address) error {
151 p, ok := s.m[profileID]
155 for _, address := range p.Addresses {
156 if address.ID == a.ID {
157 return ErrAlreadyExists
160 p.Addresses = append(p.Addresses, a)
165 func (s *inmemService) DeleteAddress(ctx context.Context, profileID string, addressID string) error {
168 p, ok := s.m[profileID]
172 newAddresses := make([]Address, 0, len(p.Addresses))
173 for _, address := range p.Addresses {
174 if address.ID == addressID {
177 newAddresses = append(newAddresses, address)
179 if len(newAddresses) == len(p.Addresses) {
182 p.Addresses = newAddresses