+++ /dev/null
-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
-}