add kv fs store and tests

This commit is contained in:
AJ ONeal 2020-09-16 22:34:25 +00:00
parent 9de2f796db
commit 557f9085f6
2 changed files with 194 additions and 0 deletions

131
kvdb/kvdb.go Normal file
View File

@ -0,0 +1,131 @@
package kvdb
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
)
type KVDB struct {
Prefix string
Ext string
}
func (kv *KVDB) Load(
keyif interface{},
typ ...interface{},
) (value interface{}, ok bool, err error) {
key, _ := keyif.(string)
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#!:| \n") {
return nil, false, nil
}
userFile := filepath.Join(kv.Prefix, key+"."+kv.Ext)
fmt.Println("Debug user file:", userFile)
b, err := ioutil.ReadFile(userFile)
if nil != err {
if os.IsNotExist(err) {
return nil, false, nil
}
fmt.Println("kvdb debug read:", err)
return nil, false, errors.New("database read failed")
}
ok = true
value = b
if 1 == len(typ) {
err := json.Unmarshal(b, typ[0])
if nil != err {
return nil, false, err
}
value = typ[0]
} else if len(b) > 0 && '"' == b[0] {
var str string
err := json.Unmarshal(b, &str)
if nil == err {
value = str
}
}
return value, ok, nil
}
func (kv *KVDB) Store(keyif interface{}, value interface{}) (err error) {
key, _ := keyif.(string)
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") {
return errors.New("invalid key name")
}
keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext)
f, err := os.Open(keypath)
if nil == err {
s, err := f.Stat()
if nil != err {
// if we can open, we should be able to stat
return errors.New("database connection failure")
}
ts := strconv.FormatInt(s.ModTime().Unix(), 10)
bakpath := filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext)
if err := os.Rename(keypath, bakpath); nil != err {
// keep the old record as a backup
return errors.New("database write failure")
}
}
var b []byte
switch v := value.(type) {
case []byte:
b = v
case string:
b, _ = json.Marshal(v)
default:
fmt.Println("kvdb: not []byte or string:", v)
jsonb, err := json.Marshal(v)
if nil != err {
return err
}
b = jsonb
}
if err := ioutil.WriteFile(
keypath,
b,
os.FileMode(0600),
); nil != err {
fmt.Println("write failure:", err)
return errors.New("database write failed")
}
return nil
}
func (kv *KVDB) Delete(keyif interface{}) (err error) {
key, _ := keyif.(string)
if "" == key || strings.Contains(key, "..") || strings.ContainsAny(key, "$#! \n") {
return errors.New("invalid key name")
}
keypath := filepath.Join(kv.Prefix, key+"."+kv.Ext)
f, err := os.Open(keypath)
if nil == err {
s, err := f.Stat()
if nil != err {
return errors.New("database connection failure")
}
ts := strconv.FormatInt(s.ModTime().Unix(), 64)
if err := os.Rename(keypath, filepath.Join(kv.Prefix, key+"."+ts+"."+kv.Ext)); nil != err {
return errors.New("database connection failure")
}
}
return nil
}
func (kv *KVDB) Vacuum() (err error) {
return nil
}

63
kvdb/kvdb_test.go Normal file
View File

@ -0,0 +1,63 @@
package kvdb
import (
"strings"
"testing"
)
type TestEntry struct {
Email string `json:"email"`
Subjects []string `json:"subjects"`
}
var email = "john@example.com"
var sub = "id123"
var dbPrefix = "../testdb"
var testKV = &KVDB{
Prefix: dbPrefix + "/test-entries",
Ext: "eml.json",
}
func TestStore(t *testing.T) {
entry := &TestEntry{
Email: email,
Subjects: []string{sub},
}
if err := testKV.Store(email, entry); nil != err {
t.Fatal(err)
return
}
value, ok, err := testKV.Load(email, &(TestEntry{}))
if nil != err {
t.Fatal(err)
return
}
if !ok {
t.Fatal("test entry not found")
}
v, ok := value.(*TestEntry)
if !ok {
t.Fatal("test entry not of type TestEntry")
}
if email != v.Email || sub != strings.Join(v.Subjects, ",") {
t.Fatalf("value: %#v", v)
}
}
func TestNoExist(t *testing.T) {
value, ok, err := testKV.Load("not"+email, &(TestEntry{}))
if nil != err {
t.Fatal(err)
return
}
if ok {
t.Fatal("found entry that doesn't exist")
}
if value != nil {
t.Fatal("had value for entry that doesn't exist")
}
}