1package logical
2
3import (
4	"context"
5	"errors"
6	"strings"
7)
8
9type StorageView struct {
10	storage Storage
11	prefix  string
12}
13
14var (
15	ErrRelativePath = errors.New("relative paths not supported")
16)
17
18func NewStorageView(storage Storage, prefix string) *StorageView {
19	return &StorageView{
20		storage: storage,
21		prefix:  prefix,
22	}
23}
24
25// logical.Storage impl.
26func (s *StorageView) List(ctx context.Context, prefix string) ([]string, error) {
27	if err := s.SanityCheck(prefix); err != nil {
28		return nil, err
29	}
30	return s.storage.List(ctx, s.ExpandKey(prefix))
31}
32
33// logical.Storage impl.
34func (s *StorageView) Get(ctx context.Context, key string) (*StorageEntry, error) {
35	if err := s.SanityCheck(key); err != nil {
36		return nil, err
37	}
38	entry, err := s.storage.Get(ctx, s.ExpandKey(key))
39	if err != nil {
40		return nil, err
41	}
42	if entry == nil {
43		return nil, nil
44	}
45	if entry != nil {
46		entry.Key = s.TruncateKey(entry.Key)
47	}
48
49	return &StorageEntry{
50		Key:      entry.Key,
51		Value:    entry.Value,
52		SealWrap: entry.SealWrap,
53	}, nil
54}
55
56// logical.Storage impl.
57func (s *StorageView) Put(ctx context.Context, entry *StorageEntry) error {
58	if entry == nil {
59		return errors.New("cannot write nil entry")
60	}
61
62	if err := s.SanityCheck(entry.Key); err != nil {
63		return err
64	}
65
66	expandedKey := s.ExpandKey(entry.Key)
67
68	nested := &StorageEntry{
69		Key:      expandedKey,
70		Value:    entry.Value,
71		SealWrap: entry.SealWrap,
72	}
73
74	return s.storage.Put(ctx, nested)
75}
76
77// logical.Storage impl.
78func (s *StorageView) Delete(ctx context.Context, key string) error {
79	if err := s.SanityCheck(key); err != nil {
80		return err
81	}
82
83	expandedKey := s.ExpandKey(key)
84
85	return s.storage.Delete(ctx, expandedKey)
86}
87
88func (s *StorageView) Prefix() string {
89	return s.prefix
90}
91
92// SubView constructs a nested sub-view using the given prefix
93func (s *StorageView) SubView(prefix string) *StorageView {
94	sub := s.ExpandKey(prefix)
95	return &StorageView{storage: s.storage, prefix: sub}
96}
97
98// SanityCheck is used to perform a sanity check on a key
99func (s *StorageView) SanityCheck(key string) error {
100	if strings.Contains(key, "..") {
101		return ErrRelativePath
102	}
103	return nil
104}
105
106// ExpandKey is used to expand to the full key path with the prefix
107func (s *StorageView) ExpandKey(suffix string) string {
108	return s.prefix + suffix
109}
110
111// TruncateKey is used to remove the prefix of the key
112func (s *StorageView) TruncateKey(full string) string {
113	return strings.TrimPrefix(full, s.prefix)
114}
115