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