feature
2015-03-16
feature/server/store.go
First pass at an implementation. Implement the calculation to determine whether a string is included in the partition or not, using crc32 to coerce the string to an evenly distributed uint32, and a modulo to turn it into a percentage. Implement a store to keep track of available flags and the level of the partition for each flag. Implement an API to retrieve, create, and modify these available flags.
| paddy@0 | 1 package server |
| paddy@0 | 2 |
| paddy@0 | 3 import ( |
| paddy@0 | 4 "fmt" |
| paddy@0 | 5 "sync" |
| paddy@0 | 6 |
| paddy@0 | 7 "code.secondbit.org/feature.hg" |
| paddy@0 | 8 ) |
| paddy@0 | 9 |
| paddy@0 | 10 const ( |
| paddy@0 | 11 errNotFound = "flag not found" |
| paddy@0 | 12 errAlreadyExists = "flag already exists" |
| paddy@0 | 13 ) |
| paddy@0 | 14 |
| paddy@0 | 15 type flagError struct { |
| paddy@0 | 16 pos int |
| paddy@0 | 17 code string |
| paddy@0 | 18 } |
| paddy@0 | 19 |
| paddy@0 | 20 func (f *flagError) Error() string { |
| paddy@0 | 21 return fmt.Sprintf("[%d]: %s", f.pos, f.code) |
| paddy@0 | 22 } |
| paddy@0 | 23 |
| paddy@0 | 24 type flagStore interface { |
| paddy@0 | 25 create(f feature.Flag) *flagError |
| paddy@0 | 26 update(flags []feature.Flag) []flagError |
| paddy@0 | 27 destroy(flag string) *flagError |
| paddy@0 | 28 list() ([]feature.Flag, *flagError) |
| paddy@0 | 29 } |
| paddy@0 | 30 |
| paddy@0 | 31 type memstore struct { |
| paddy@0 | 32 flags map[string]feature.Flag |
| paddy@0 | 33 flock sync.RWMutex |
| paddy@0 | 34 } |
| paddy@0 | 35 |
| paddy@0 | 36 func (m *memstore) create(f feature.Flag) *flagError { |
| paddy@0 | 37 m.flock.Lock() |
| paddy@0 | 38 defer m.flock.Unlock() |
| paddy@0 | 39 |
| paddy@0 | 40 if _, ok := m.flags[f.ID]; ok { |
| paddy@0 | 41 return &flagError{pos: 0, code: errNotFound} |
| paddy@0 | 42 } |
| paddy@0 | 43 m.flags[f.ID] = f |
| paddy@0 | 44 return nil |
| paddy@0 | 45 } |
| paddy@0 | 46 |
| paddy@0 | 47 func (m *memstore) update(flags []feature.Flag) []flagError { |
| paddy@0 | 48 m.flock.Lock() |
| paddy@0 | 49 defer m.flock.Unlock() |
| paddy@0 | 50 |
| paddy@0 | 51 errs := []flagError{} |
| paddy@0 | 52 for pos, f := range flags { |
| paddy@0 | 53 if _, ok := m.flags[f.ID]; !ok { |
| paddy@0 | 54 errs = append(errs, flagError{pos: pos, code: errNotFound}) |
| paddy@0 | 55 } |
| paddy@0 | 56 } |
| paddy@0 | 57 if len(errs) > 0 { |
| paddy@0 | 58 return errs |
| paddy@0 | 59 } |
| paddy@0 | 60 for _, flag := range flags { |
| paddy@0 | 61 m.flags[flag.ID] = flag |
| paddy@0 | 62 } |
| paddy@0 | 63 return nil |
| paddy@0 | 64 } |
| paddy@0 | 65 |
| paddy@0 | 66 func (m *memstore) destroy(flag string) *flagError { |
| paddy@0 | 67 m.flock.Lock() |
| paddy@0 | 68 defer m.flock.Unlock() |
| paddy@0 | 69 |
| paddy@0 | 70 if _, ok := m.flags[flag]; !ok { |
| paddy@0 | 71 return &flagError{pos: 0, code: errNotFound} |
| paddy@0 | 72 } |
| paddy@0 | 73 delete(m.flags, flag) |
| paddy@0 | 74 return nil |
| paddy@0 | 75 } |
| paddy@0 | 76 |
| paddy@0 | 77 func (m *memstore) list() ([]feature.Flag, *flagError) { |
| paddy@0 | 78 m.flock.RLock() |
| paddy@0 | 79 defer m.flock.RUnlock() |
| paddy@0 | 80 |
| paddy@0 | 81 flags := make([]feature.Flag, len(m.flags)) |
| paddy@0 | 82 pos := 0 |
| paddy@0 | 83 for _, flag := range m.flags { |
| paddy@0 | 84 flags[pos] = flag |
| paddy@0 | 85 pos = pos + 1 |
| paddy@0 | 86 } |
| paddy@0 | 87 return flags, nil |
| paddy@0 | 88 } |