github.com/safing/portbase@v0.19.5/database/record/wrapper.go (about)

     1  package record
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  
     8  	"github.com/safing/portbase/container"
     9  	"github.com/safing/portbase/database/accessor"
    10  	"github.com/safing/portbase/formats/dsd"
    11  	"github.com/safing/portbase/formats/varint"
    12  )
    13  
    14  // Wrapper wraps raw data and implements the Record interface.
    15  type Wrapper struct {
    16  	Base
    17  	sync.Mutex
    18  
    19  	Format uint8
    20  	Data   []byte
    21  }
    22  
    23  // NewRawWrapper returns a record wrapper for the given data, including metadata. This is normally only used by storage backends when loading records.
    24  func NewRawWrapper(database, key string, data []byte) (*Wrapper, error) {
    25  	version, offset, err := varint.Unpack8(data)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  	if version != 1 {
    30  		return nil, fmt.Errorf("incompatible record version: %d", version)
    31  	}
    32  
    33  	metaSection, n, err := varint.GetNextBlock(data[offset:])
    34  	if err != nil {
    35  		return nil, fmt.Errorf("could not get meta section: %w", err)
    36  	}
    37  	offset += n
    38  
    39  	newMeta := &Meta{}
    40  	_, err = dsd.Load(metaSection, newMeta)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("could not unmarshal meta section: %w", err)
    43  	}
    44  
    45  	var format uint8 = dsd.RAW
    46  	if !newMeta.IsDeleted() {
    47  		format, n, err = varint.Unpack8(data[offset:])
    48  		if err != nil {
    49  			return nil, fmt.Errorf("could not get dsd format: %w", err)
    50  		}
    51  		offset += n
    52  	}
    53  
    54  	return &Wrapper{
    55  		Base{
    56  			database,
    57  			key,
    58  			newMeta,
    59  		},
    60  		sync.Mutex{},
    61  		format,
    62  		data[offset:],
    63  	}, nil
    64  }
    65  
    66  // NewWrapper returns a new record wrapper for the given data.
    67  func NewWrapper(key string, meta *Meta, format uint8, data []byte) (*Wrapper, error) {
    68  	dbName, dbKey := ParseKey(key)
    69  
    70  	return &Wrapper{
    71  		Base{
    72  			dbName: dbName,
    73  			dbKey:  dbKey,
    74  			meta:   meta,
    75  		},
    76  		sync.Mutex{},
    77  		format,
    78  		data,
    79  	}, nil
    80  }
    81  
    82  // Marshal marshals the object, without the database key or metadata.
    83  func (w *Wrapper) Marshal(r Record, format uint8) ([]byte, error) {
    84  	if w.Meta() == nil {
    85  		return nil, errors.New("missing meta")
    86  	}
    87  
    88  	if w.Meta().Deleted > 0 {
    89  		return nil, nil
    90  	}
    91  
    92  	if format != dsd.AUTO && format != w.Format {
    93  		return nil, errors.New("could not dump model, wrapped object format mismatch")
    94  	}
    95  
    96  	data := make([]byte, len(w.Data)+1)
    97  	data[0] = w.Format
    98  	copy(data[1:], w.Data)
    99  
   100  	return data, nil
   101  }
   102  
   103  // MarshalRecord packs the object, including metadata, into a byte array for saving in a database.
   104  func (w *Wrapper) MarshalRecord(r Record) ([]byte, error) {
   105  	// Duplication necessary, as the version from Base would call Base.Marshal instead of Wrapper.Marshal
   106  
   107  	if w.Meta() == nil {
   108  		return nil, errors.New("missing meta")
   109  	}
   110  
   111  	// version
   112  	c := container.New([]byte{1})
   113  
   114  	// meta
   115  	metaSection, err := dsd.Dump(w.meta, dsd.GenCode)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	c.AppendAsBlock(metaSection)
   120  
   121  	// data
   122  	dataSection, err := w.Marshal(r, dsd.AUTO)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	c.Append(dataSection)
   127  
   128  	return c.CompileData(), nil
   129  }
   130  
   131  // IsWrapped returns whether the record is a Wrapper.
   132  func (w *Wrapper) IsWrapped() bool {
   133  	return true
   134  }
   135  
   136  // Unwrap unwraps data into a record.
   137  func Unwrap(wrapped, r Record) error {
   138  	wrapper, ok := wrapped.(*Wrapper)
   139  	if !ok {
   140  		return fmt.Errorf("cannot unwrap %T", wrapped)
   141  	}
   142  
   143  	err := dsd.LoadAsFormat(wrapper.Data, wrapper.Format, r)
   144  	if err != nil {
   145  		return fmt.Errorf("failed to unwrap %T: %w", r, err)
   146  	}
   147  
   148  	r.SetKey(wrapped.Key())
   149  	r.SetMeta(wrapped.Meta())
   150  
   151  	return nil
   152  }
   153  
   154  // GetAccessor returns an accessor for this record, if available.
   155  func (w *Wrapper) GetAccessor(self Record) accessor.Accessor {
   156  	if w.Format == dsd.JSON && len(w.Data) > 0 {
   157  		return accessor.NewJSONBytesAccessor(&w.Data)
   158  	}
   159  	return nil
   160  }