feat: working version
1. implemented filesystem storage, NATS object storage and saving to Vault. 2. Test coverage is fine for filesystem and Vault (and NATS object does not really require extensive tests)
This commit is contained in:
@@ -1,12 +1,26 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.uint32.ru/tiny/storage/internal/errinternal"
|
||||
)
|
||||
|
||||
func Open(path string) (*Storage, error) {
|
||||
const (
|
||||
fileModeDir os.FileMode = 0755
|
||||
fileModeFile os.FileMode = 0644
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidKey = errinternal.ErrInvalidKey
|
||||
ErrNotFound = errinternal.ErrNotFound
|
||||
)
|
||||
|
||||
func New(path string) (*Storage, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -21,17 +35,24 @@ func Open(path string) (*Storage, error) {
|
||||
return nil, fmt.Errorf("could not tarnslate %s to absolute path", path)
|
||||
}
|
||||
|
||||
return &Storage{prefix: abs}, nil
|
||||
return &Storage{Dir: abs}, nil
|
||||
}
|
||||
|
||||
type Storage struct {
|
||||
prefix string
|
||||
Dir string
|
||||
}
|
||||
|
||||
func (s *Storage) Save(key string, data []byte) error {
|
||||
path := s.toAbs(key)
|
||||
if err := validateKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0664); err != nil {
|
||||
path := s.getKeyPath(key)
|
||||
if err := os.MkdirAll(path, fileModeDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(path, key), data, fileModeFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -39,10 +60,16 @@ func (s *Storage) Save(key string, data []byte) error {
|
||||
}
|
||||
|
||||
func (s *Storage) Load(key string) ([]byte, error) {
|
||||
path := s.toAbs(key)
|
||||
if err := validateKey(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
path := s.getKeyPath(key)
|
||||
|
||||
b, err := os.ReadFile(filepath.Join(path, key))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
return nil, errors.Join(errinternal.ErrNotFound, err)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -50,22 +77,47 @@ func (s *Storage) Load(key string) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (s *Storage) Delete(key string) error {
|
||||
path := s.toAbs(key)
|
||||
if err := validateKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := os.Remove(path)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
path := s.getKeyPath(key)
|
||||
|
||||
err := os.Remove(filepath.Join(path, key))
|
||||
if err != nil && errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: think of cleaning up path when no files left
|
||||
// in /basedir/a/b/ after deleting key abc
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) Close() error {
|
||||
func (s *Storage) getKeyPath(key string) string {
|
||||
return filepath.Join(s.Dir, getPrefixPath(key))
|
||||
}
|
||||
|
||||
func validateKey(key string) error {
|
||||
if len([]rune(key)) < 3 {
|
||||
return errors.Join(ErrInvalidKey, fmt.Errorf("key must be at least 3 characters long"))
|
||||
}
|
||||
|
||||
// Of course windoze guys are missing the whole point, but
|
||||
// let us use os-specific path separator and not ruin the whole fun
|
||||
// for them :)
|
||||
if strings.Contains(key, string(os.PathSeparator)) {
|
||||
return errors.Join(ErrInvalidKey, fmt.Errorf("key must not contain path separator character: %s", string(os.PathSeparator)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Storage) toAbs(path string) string {
|
||||
return filepath.Join(s.prefix, path)
|
||||
func getPrefixPath(key string) string {
|
||||
r := []rune(key)
|
||||
out := []rune{r[0], '/', r[1]}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
@@ -7,15 +7,16 @@ import (
|
||||
)
|
||||
|
||||
func TestStorageMethods(t *testing.T) {
|
||||
st, err := Open("./testdata")
|
||||
st, err := New("./testdata")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name := "mytestfile"
|
||||
data := []byte("contents of my test file")
|
||||
|
||||
defer os.Remove(name) // just in case
|
||||
defer os.RemoveAll("./testdata/m")
|
||||
|
||||
data := []byte("contents of my test file")
|
||||
|
||||
if err := st.Save(name, data); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -41,5 +42,4 @@ func TestStorageMethods(t *testing.T) {
|
||||
if err := st.Delete(name); err != nil {
|
||||
t.Errorf("delete of non-existent failed: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user