package main import ( "cmp" "encoding/json" "errors" "fmt" "os" "slices" "strings" "sync" ) var ( ContactsFile = "contacts.json" ) type ( Contacts []Contact ContactsStore struct { sync.RWMutex data map[int]Contact } ) func NewContactsStore() (*ContactsStore, error) { cs := ContactsStore{ data: make(map[int]Contact), } if err := cs.Load(); err != nil { return nil, err } return &cs, nil } func (cs *ContactsStore) Get(search string) Contacts { contacts := cs.GetAll() if search == "" { return contacts } return slices.DeleteFunc(contacts, func(c Contact) bool { return (c.First == "" || !strings.Contains(c.First, search)) && (c.Last == "" || !strings.Contains(c.Last, search)) && (c.Email == "" || !strings.Contains(c.Email, search)) && (c.Phone == "" || !strings.Contains(c.Phone, search)) }) } func MapValues[M ~map[K]V, K comparable, V any](m M) []V { sl := make([]V, 0, len(m)) for _, v := range m { sl = append(sl, v) } return sl } func (cs *ContactsStore) GetAll() Contacts { cs.RLock() defer cs.RUnlock() contacts := MapValues(cs.data) slices.SortStableFunc(contacts, func(a, b Contact) int { if sgn := cmp.Compare(a.First, b.First); sgn != 0 { return sgn } if sgn := cmp.Compare(a.Last, b.Last); sgn != 0 { return sgn } if sgn := cmp.Compare(a.Phone, b.Phone); sgn != 0 { return sgn } if sgn := cmp.Compare(a.Email, b.Email); sgn != 0 { return sgn } return cmp.Compare(a.ID, b.ID) }) return contacts } func (cs *ContactsStore) Load() error { if cs == nil { return errors.New("cannot load into nil store") } bs, err := os.ReadFile(ContactsFile) if err != nil { return fmt.Errorf("reading file: %w", err) } contacts := make(Contacts, 0) if err := json.Unmarshal(bs, &contacts); err != nil { return fmt.Errorf("unmarshalling file: %w", err) } cs.Lock() defer cs.Unlock() for _, contact := range contacts { cs.data[contact.ID] = contact } return nil } type Contact struct { ID int `json:"id"` First string `json:"first"` Last string `json:"last"` Phone string `json:"phone"` Email string `json:"email"` errors map[string]string }