github.com/creachadair/ffs@v0.17.3/storage/encoded/encoded.go (about)

     1  // Copyright 2019 Michael J. Fromberger. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package encoded implements a [blob.StoreCloser] that applies a reversible
    16  // encoding such as compression or encryption to the data.
    17  package encoded
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"io"
    23  	"iter"
    24  
    25  	"github.com/creachadair/ffs/blob"
    26  )
    27  
    28  // A Codec defines the capabilities needed to encode and decode.
    29  type Codec interface {
    30  	// Encode writes the encoding of src to w. After encoding, src may be garbage.
    31  	Encode(w io.Writer, src []byte) error
    32  
    33  	// Decode writes the decoding of src to w.  After decoding, src may be garbage.
    34  	Decode(w io.Writer, src []byte) error
    35  }
    36  
    37  // A Store wraps an existing [blob.Store] implementation so that its key spaces
    38  // are encoded using a [Codec].
    39  type Store struct {
    40  	codec Codec
    41  	real  blob.Store
    42  }
    43  
    44  // KV implements a method of [blob.Store]. The concrete type of keyspaces
    45  // returned is [KV].
    46  func (s Store) KV(ctx context.Context, name string) (blob.KV, error) {
    47  	kv, err := s.real.KV(ctx, name)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	return KV{codec: s.codec, real: kv}, nil
    52  }
    53  
    54  // CAS implements a method of [blob.Store].
    55  func (s Store) CAS(ctx context.Context, name string) (blob.CAS, error) {
    56  	return blob.CASFromKVError(s.KV(ctx, name))
    57  }
    58  
    59  // Sub implements a method of [blob.Store]. The concrete type of stores
    60  // returned is [Store].
    61  func (s Store) Sub(ctx context.Context, name string) (blob.Store, error) {
    62  	sub, err := s.real.Sub(ctx, name)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	return Store{codec: s.codec, real: sub}, nil
    67  }
    68  
    69  // Close implements a method of the [blob.StoreCloser] interface.
    70  func (s Store) Close(ctx context.Context) error {
    71  	if c, ok := s.real.(blob.Closer); ok {
    72  		return c.Close(ctx)
    73  	}
    74  	return nil
    75  }
    76  
    77  // New constructs a new store that delegates to s and uses c to encode and
    78  // decode blob data. New will panic if either s or c is nil.
    79  func New(s blob.Store, c Codec) Store {
    80  	if s == nil {
    81  		panic("store is nil")
    82  	} else if c == nil {
    83  		panic("codec is nil")
    84  	}
    85  	return Store{codec: c, real: s}
    86  }
    87  
    88  // A KV wraps an existing [blob.KV] implementation in which blobs are encoded
    89  // using a [Codec].
    90  type KV struct {
    91  	codec Codec   // used to compress and decompress blobs
    92  	real  blob.KV // the underlying storage implementation
    93  }
    94  
    95  // NewKV constructs a new KV that delegates to kv and uses c to encode and
    96  // decode blob data. NewKV will panic if either kv or c is nil.
    97  func NewKV(kv blob.KV, c Codec) KV {
    98  	if kv == nil {
    99  		panic("keyspace is nil")
   100  	} else if c == nil {
   101  		panic("codec is nil")
   102  	}
   103  	return KV{codec: c, real: kv}
   104  }
   105  
   106  // Get implements part of the [blob.KV] interface.
   107  func (s KV) Get(ctx context.Context, key string) ([]byte, error) {
   108  	enc, err := s.real.Get(ctx, key)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// Ideally we would request the decoded length and use that to allocate a
   114  	// buffer for the output. For some codecs, however, it isn't possible to
   115  	// compute the decoded length without performing the decoding, which loses
   116  	// the benefit.
   117  	var buf bytes.Buffer
   118  	if err := s.codec.Decode(&buf, enc); err != nil {
   119  		return nil, err
   120  	}
   121  	return buf.Bytes(), nil
   122  }
   123  
   124  // Has implements part of the [blob.KV] interface.
   125  func (s KV) Has(ctx context.Context, keys ...string) (blob.KeySet, error) {
   126  	return s.real.Has(ctx, keys...)
   127  }
   128  
   129  // Put implements part of the [blob.KV] interface.
   130  func (s KV) Put(ctx context.Context, opts blob.PutOptions) error {
   131  	buf := bytes.NewBuffer(make([]byte, 0, len(opts.Data)))
   132  	if err := s.codec.Encode(buf, opts.Data); err != nil {
   133  		return err
   134  	}
   135  	// Leave the original options as given, but replace the data.
   136  	opts.Data = buf.Bytes()
   137  	return s.real.Put(ctx, opts)
   138  }
   139  
   140  // Delete implements part of the [blob.KV] interface.
   141  // It delegates directly to the underlying store.
   142  func (s KV) Delete(ctx context.Context, key string) error {
   143  	return s.real.Delete(ctx, key)
   144  }
   145  
   146  // List implements part of the [blob.KV] interface.
   147  // It delegates directly to the underlying store.
   148  func (s KV) List(ctx context.Context, start string) iter.Seq2[string, error] {
   149  	return s.real.List(ctx, start)
   150  }
   151  
   152  // Len implements part of the [blob.KV] interface.
   153  // It delegates directly to the underlying store.
   154  func (s KV) Len(ctx context.Context) (int64, error) { return s.real.Len(ctx) }