haberdasher-twirp/internal/haberdasherserver/server.go

167 lines
3.0 KiB
Go

package haberdasherserver
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"math/rand"
"os"
"github.com/boltdb/bolt"
"github.com/twitchtv/twirp"
pb "haberdasher-twirp/haberdasher"
)
const dbBucket = "hats"
type Server struct {
DBPath string
}
type Hat struct {
Inches int32 `json:"inchues"`
Color string `json:"color"`
Name string `json:"name"`
}
type HatQuery struct {
Limit int32
}
type Store struct {
db *bolt.DB
}
func HatToHatModel(h *pb.Hat) Hat {
return Hat{
Inches: h.Inches,
Color: h.Color,
Name: h.Name,
}
}
func HatModelToHat(h Hat) *pb.Hat {
return &pb.Hat{
Inches: h.Inches,
Color: h.Color,
Name: h.Name,
}
}
func HatsModelToHats(hs []Hat) (hats *pb.Hats) {
hats = &pb.Hats{}
for _, h := range hs {
hat := HatModelToHat(h)
hats.Hats = append(hats.Hats, hat)
}
return hats
}
func HatQueryToHatQueryModel(q *pb.HatQuery) HatQuery {
return HatQuery{
Limit: q.Limit,
}
}
func (s *Server) MakeHat(ctx context.Context, size *pb.Size) (hat *pb.Hat, err error) {
st, _ := NewStore(s.DBPath, 0600, nil)
defer st.Close()
if size.Inches <= 0 {
return nil, twirp.InvalidArgumentError("inches", "I can't make a hat that small!")
}
h := Hat{
Inches: size.Inches,
Color: []string{"white", "black", "red", "blue"}[rand.Intn(4)],
Name: []string{"bowler", "baseball cap", "top hat", "derby"}[rand.Intn(3)],
}
fmt.Printf("made hat: %+v\n", h)
st.SaveHat(h)
return HatModelToHat(h), nil
}
func (s *Server) ListHats(ctx context.Context, q *pb.HatQuery) (hats *pb.Hats, err error) {
st, _ := NewStore(s.DBPath, 0600, nil)
defer st.Close()
hs, err := st.ListHats(HatQueryToHatQueryModel(q))
return HatsModelToHats(hs), nil
}
func itob(v uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(v))
return b
}
func btoi(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
func NewStore(path string, mode os.FileMode, options *bolt.Options) (s *Store, err error) {
s = &Store{}
s.db, err = bolt.Open(path, mode, options)
if err != nil {
fmt.Printf("bolt open: %s", err)
}
s.db.Update(func(tx *bolt.Tx) error {
if _, err := tx.CreateBucket([]byte(dbBucket)); err != nil {
return fmt.Errorf("create bucket: %s", err)
}
return nil
})
return s, err
}
func (s *Store) SaveHat(h Hat) (err error) {
return s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("hats"))
id, _ := b.NextSequence()
buf, err := json.Marshal(h)
if err != nil {
return fmt.Errorf("json marshal: %s", err)
}
return b.Put(itob(id), buf)
})
}
func (s *Store) ListHats(q HatQuery) (hats []Hat, err error) {
var hat Hat
var i int
s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("hats"))
c := b.Cursor()
for k, v := c.Last(); k != nil && i < int(q.Limit); k, v = c.Prev() {
if err := json.Unmarshal(v, &hat); err != nil {
return fmt.Errorf("json unmarshal: %s", err)
}
h := hat
hats = append(hats, h)
i++
}
return nil
})
return hats, nil
}
func (s *Store) Close() (err error) {
return s.db.Close()
}