Additions to domain/ and workspace/ chron-note packages
This commit is contained in:
@@ -1,2 +1,11 @@
|
||||
package domain
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrInvalidObjectID = errors.New("invalid object id")
|
||||
ErrInvalidBlobID = errors.New("invalid blob id")
|
||||
ErrUnknownEventType = errors.New("unknown event type")
|
||||
ErrInvalidEvent = errors.New("invalid event")
|
||||
ErrDecode = errors.New("decode error")
|
||||
)
|
||||
|
||||
@@ -1 +1,137 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.michelsen.id/phill/chron/chron-note/internal/util"
|
||||
)
|
||||
|
||||
type EventType uint8
|
||||
|
||||
const (
|
||||
EventObjectUpsert EventType = 1
|
||||
EventObjectDelete EventType = 2
|
||||
)
|
||||
|
||||
const encodingVersion uint8 = 1
|
||||
|
||||
type ObjectUpsert struct {
|
||||
ObjectID ObjectID
|
||||
Name string
|
||||
Tags []string
|
||||
|
||||
HasBlob bool
|
||||
Blob BlobID
|
||||
}
|
||||
|
||||
type ObjectDelete struct {
|
||||
ObjectID ObjectID
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type EventType
|
||||
|
||||
Upsert *ObjectUpsert
|
||||
Delete *ObjectDelete
|
||||
}
|
||||
|
||||
func EncodeEvent(e Event) ([]byte, error) {
|
||||
enc := util.NewEncoder(nil)
|
||||
enc.U8(encodingVersion)
|
||||
enc.U8(uint8(e.Type))
|
||||
|
||||
switch e.Type {
|
||||
case EventObjectUpsert:
|
||||
if e.Upsert == nil {
|
||||
return nil, ErrInvalidEvent
|
||||
}
|
||||
u := e.Upsert
|
||||
|
||||
var flags uint8
|
||||
if u.HasBlob {
|
||||
flags |= 0x01
|
||||
}
|
||||
enc.U8(flags)
|
||||
|
||||
enc.BytesFixed(u.ObjectID[:])
|
||||
enc.String(u.Name)
|
||||
enc.StringSlice(u.Tags)
|
||||
|
||||
if u.HasBlob {
|
||||
enc.BytesFixed(u.Blob[:])
|
||||
}
|
||||
|
||||
case EventObjectDelete:
|
||||
if e.Delete == nil {
|
||||
return nil, ErrInvalidEvent
|
||||
}
|
||||
enc.BytesFixed(e.Delete.ObjectID[:])
|
||||
|
||||
default:
|
||||
return nil, ErrUnknownEventType
|
||||
}
|
||||
|
||||
if err := enc.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc.Bytes(), nil
|
||||
}
|
||||
|
||||
func DecodeEvent(b []byte) (Event, error) {
|
||||
dec := util.NewDecoder(b)
|
||||
|
||||
ver := dec.U8()
|
||||
if dec.Err() != nil {
|
||||
return Event{}, ErrDecode
|
||||
}
|
||||
if ver != encodingVersion {
|
||||
return Event{}, fmt.Errorf("%w: unsupported encoding version %d", ErrDecode, ver)
|
||||
}
|
||||
|
||||
typ := EventType(dec.U8())
|
||||
if dec.Err() != nil {
|
||||
return Event{}, ErrDecode
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case EventObjectUpsert:
|
||||
flags := dec.U8()
|
||||
var objID ObjectID
|
||||
copy(objID[:], dec.BytesFixed(len(objID)))
|
||||
|
||||
name := dec.String()
|
||||
tags := dec.StringSlice()
|
||||
|
||||
hasBlob := (flags & 0x01) != 0
|
||||
var blob BlobID
|
||||
if hasBlob {
|
||||
copy(blob[:], dec.BytesFixed(len(blob)))
|
||||
}
|
||||
|
||||
if dec.Err() != nil {
|
||||
return Event{}, ErrDecode
|
||||
}
|
||||
|
||||
return Event{
|
||||
Type: typ,
|
||||
Upsert: &ObjectUpsert{
|
||||
ObjectID: objID,
|
||||
Name: NormalizeName(name),
|
||||
Tags: NormalizeTags(tags),
|
||||
HasBlob: hasBlob,
|
||||
Blob: blob,
|
||||
},
|
||||
}, nil
|
||||
|
||||
case EventObjectDelete:
|
||||
var objID ObjectID
|
||||
copy(objID[:], dec.BytesFixed(len(objID)))
|
||||
if dec.Err() != nil {
|
||||
return Event{}, ErrDecode
|
||||
}
|
||||
return Event{Type: typ, Delete: &ObjectDelete{ObjectID: objID}}, nil
|
||||
|
||||
default:
|
||||
return Event{}, ErrUnknownEventType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,74 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ObjectID [16]byte
|
||||
type BlobID [32]byte
|
||||
|
||||
func NewObjectID() (ObjectID, error) {
|
||||
var id ObjectID
|
||||
_, err := rand.Read(id[:])
|
||||
return id, err
|
||||
}
|
||||
|
||||
func ParseObjectID(s string) (ObjectID, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil || len(b) != len(ObjectID{}) {
|
||||
return ObjectID{}, ErrInvalidObjectID
|
||||
}
|
||||
var id ObjectID
|
||||
copy(id[:], b)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (id ObjectID) String() string { return hex.EncodeToString(id[:]) }
|
||||
|
||||
func ParseBlobID(s string) (BlobID, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
b, err := hex.DecodeString(s)
|
||||
if err != nil || len(b) != len(BlobID{}) {
|
||||
return BlobID{}, ErrInvalidBlobID
|
||||
}
|
||||
var id BlobID
|
||||
copy(id[:], b)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (id BlobID) String() string { return hex.EncodeToString(id[:]) }
|
||||
|
||||
type ObjectState struct {
|
||||
ID ObjectID
|
||||
Name string
|
||||
Tags []string
|
||||
Blob BlobID
|
||||
HasBlob bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
// Keep normalization here so all layers agree.
|
||||
func NormalizeName(s string) string {
|
||||
s = strings.TrimSpace(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func NormalizeTags(tags []string) []string {
|
||||
out := make([]string, 0, len(tags))
|
||||
seen := make(map[string]struct{}, len(tags))
|
||||
for _, t := range tags {
|
||||
t = strings.TrimSpace(strings.ToLower(t))
|
||||
if t == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[t]; ok {
|
||||
continue
|
||||
}
|
||||
seen[t] = struct{}{}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user