github.com/safing/portbase@v0.19.5/formats/dsd/dsd.go (about)

     1  package dsd
     2  
     3  // dynamic structured data
     4  // check here for some benchmarks: https://github.com/alecthomas/go_serialization_benchmarks
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  
    12  	"github.com/fxamacker/cbor/v2"
    13  	"github.com/ghodss/yaml"
    14  	"github.com/vmihailenco/msgpack/v5"
    15  
    16  	"github.com/safing/portbase/formats/varint"
    17  	"github.com/safing/portbase/utils"
    18  )
    19  
    20  // Load loads an dsd structured data blob into the given interface.
    21  func Load(data []byte, t interface{}) (format uint8, err error) {
    22  	format, read, err := loadFormat(data)
    23  	if err != nil {
    24  		return 0, err
    25  	}
    26  
    27  	_, ok := ValidateSerializationFormat(format)
    28  	if ok {
    29  		return format, LoadAsFormat(data[read:], format, t)
    30  	}
    31  	return DecompressAndLoad(data[read:], format, t)
    32  }
    33  
    34  // LoadAsFormat loads a data blob into the interface using the specified format.
    35  func LoadAsFormat(data []byte, format uint8, t interface{}) (err error) {
    36  	switch format {
    37  	case RAW:
    38  		return ErrIsRaw
    39  	case JSON:
    40  		err = json.Unmarshal(data, t)
    41  		if err != nil {
    42  			return fmt.Errorf("dsd: failed to unpack json: %w, data: %s", err, utils.SafeFirst16Bytes(data))
    43  		}
    44  		return nil
    45  	case YAML:
    46  		err = yaml.Unmarshal(data, t)
    47  		if err != nil {
    48  			return fmt.Errorf("dsd: failed to unpack yaml: %w, data: %s", err, utils.SafeFirst16Bytes(data))
    49  		}
    50  		return nil
    51  	case CBOR:
    52  		err = cbor.Unmarshal(data, t)
    53  		if err != nil {
    54  			return fmt.Errorf("dsd: failed to unpack cbor: %w, data: %s", err, utils.SafeFirst16Bytes(data))
    55  		}
    56  		return nil
    57  	case MsgPack:
    58  		err = msgpack.Unmarshal(data, t)
    59  		if err != nil {
    60  			return fmt.Errorf("dsd: failed to unpack msgpack: %w, data: %s", err, utils.SafeFirst16Bytes(data))
    61  		}
    62  		return nil
    63  	case GenCode:
    64  		genCodeStruct, ok := t.(GenCodeCompatible)
    65  		if !ok {
    66  			return errors.New("dsd: gencode is not supported by the given data structure")
    67  		}
    68  		_, err = genCodeStruct.GenCodeUnmarshal(data)
    69  		if err != nil {
    70  			return fmt.Errorf("dsd: failed to unpack gencode: %w, data: %s", err, utils.SafeFirst16Bytes(data))
    71  		}
    72  		return nil
    73  	default:
    74  		return ErrIncompatibleFormat
    75  	}
    76  }
    77  
    78  func loadFormat(data []byte) (format uint8, read int, err error) {
    79  	format, read, err = varint.Unpack8(data)
    80  	if err != nil {
    81  		return 0, 0, err
    82  	}
    83  	if len(data) <= read {
    84  		return 0, 0, io.ErrUnexpectedEOF
    85  	}
    86  
    87  	return format, read, nil
    88  }
    89  
    90  // Dump stores the interface as a dsd formatted data structure.
    91  func Dump(t interface{}, format uint8) ([]byte, error) {
    92  	return DumpIndent(t, format, "")
    93  }
    94  
    95  // DumpIndent stores the interface as a dsd formatted data structure with indentation, if available.
    96  func DumpIndent(t interface{}, format uint8, indent string) ([]byte, error) {
    97  	data, err := dumpWithoutIdentifier(t, format, indent)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	// TODO: Find a better way to do this.
   103  	return append(varint.Pack8(format), data...), nil
   104  }
   105  
   106  func dumpWithoutIdentifier(t interface{}, format uint8, indent string) ([]byte, error) {
   107  	format, ok := ValidateSerializationFormat(format)
   108  	if !ok {
   109  		return nil, ErrIncompatibleFormat
   110  	}
   111  
   112  	var data []byte
   113  	var err error
   114  	switch format {
   115  	case RAW:
   116  		var ok bool
   117  		data, ok = t.([]byte)
   118  		if !ok {
   119  			return nil, ErrIncompatibleFormat
   120  		}
   121  	case JSON:
   122  		// TODO: use SetEscapeHTML(false)
   123  		if indent != "" {
   124  			data, err = json.MarshalIndent(t, "", indent)
   125  		} else {
   126  			data, err = json.Marshal(t)
   127  		}
   128  		if err != nil {
   129  			return nil, err
   130  		}
   131  	case YAML:
   132  		data, err = yaml.Marshal(t)
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  	case CBOR:
   137  		data, err = cbor.Marshal(t)
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  	case MsgPack:
   142  		data, err = msgpack.Marshal(t)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  	case GenCode:
   147  		genCodeStruct, ok := t.(GenCodeCompatible)
   148  		if !ok {
   149  			return nil, errors.New("dsd: gencode is not supported by the given data structure")
   150  		}
   151  		data, err = genCodeStruct.GenCodeMarshal(nil)
   152  		if err != nil {
   153  			return nil, fmt.Errorf("dsd: failed to pack gencode struct: %w", err)
   154  		}
   155  	default:
   156  		return nil, ErrIncompatibleFormat
   157  	}
   158  
   159  	return data, nil
   160  }