isle/go/secrets/store.go

106 lines
2.7 KiB
Go

package secrets
import (
"context"
"encoding/json"
"errors"
"fmt"
)
// ErrNotFound is returned when an ID could not be found.
var ErrNotFound = errors.New("not found")
// Store is used to persist and retrieve secrets. If a Store serializes a
// payload it will do so using JSON.
type Store interface {
// Set stores the secret payload of the given ID.
Set(context.Context, ID, any) error
// Get retrieves the secret of the given ID, setting it into the given
// pointer value, or returns ErrNotFound.
Get(context.Context, any, ID) error
}
// GetSetFunctions returns a Get/Set function pair for the given ID and payload
// type.
func GetSetFunctions[T any](
id ID,
) (
func(context.Context, Store) (T, error), // Get
func(context.Context, Store, T) error, // Set
) {
var (
get = func(ctx context.Context, store Store) (T, error) {
var v T
err := store.Get(ctx, &v, id)
return v, err
}
set = func(ctx context.Context, store Store, v T) error {
return store.Set(ctx, id, v)
}
)
return get, set
}
// MultiSet will call Set on the given Store for every key-value pair in the
// given map.
func MultiSet(ctx context.Context, s Store, m map[ID]any) error {
var errs []error
for id, payload := range m {
if err := s.Set(ctx, id, payload); err != nil {
errs = append(errs, fmt.Errorf("setting payload for %q: %w", id, err))
}
}
return errors.Join(errs...)
}
// MultiGet will call Get on the given Store for every key-value pair in the
// given map. Each value in the map must be a pointer receiver.
func MultiGet(ctx context.Context, s Store, m map[ID]any) error {
var errs []error
for id, into := range m {
if err := s.Get(ctx, into, id); err != nil {
errs = append(errs, fmt.Errorf("getting payload for %q: %w", id, err))
}
}
return errors.Join(errs...)
}
// Export returns a map of ID to raw payload for each ID given. An error is
// returned for _each_ ID which could not be exported, wrapped using
// `errors.Join`, alongside whatever keys could be exported.
func Export(
ctx context.Context, s Store, ids []ID,
) (
map[ID]json.RawMessage, error,
) {
var (
m = map[ID]json.RawMessage{}
errs []error
)
for _, id := range ids {
var into json.RawMessage
if err := s.Get(ctx, &into, id); err != nil {
errs = append(errs, fmt.Errorf("exporting %q: %w", id, err))
continue
}
m[id] = into
}
return m, errors.Join(errs...)
}
// Import sets all given ID/payload pairs into the Store.
func Import(
ctx context.Context, s Store, m map[ID]json.RawMessage,
) error {
var errs []error
for id, payload := range m {
if err := s.Set(ctx, id, payload); err != nil {
errs = append(errs, fmt.Errorf("importing %q: %w", id, err))
}
}
return errors.Join(errs...)
}