diff options
author | Rasmus Dahlberg <rasmus@mullvad.net> | 2022-04-25 00:43:06 +0200 |
---|---|---|
committer | Rasmus Dahlberg <rasmus@mullvad.net> | 2022-04-25 00:43:06 +0200 |
commit | 528a53f7f76f08af5902f4cfa8235380b3434ba0 (patch) | |
tree | 662b7834d5ce15627554e9307a4e00f7364fba11 /pkg/ascii | |
parent | 4fc0ff2ec2f48519ee245d6d7edee1921cb3b8bc (diff) |
drafty types refactor with simple ascii packagergdd/sketch
types.go compiles but that is about it, here be dragons. Pushing so
that we can get an idea of what this refactor would roughly look like.
Diffstat (limited to 'pkg/ascii')
-rw-r--r-- | pkg/ascii/ascii.go | 306 | ||||
-rw-r--r-- | pkg/ascii/ascii_test.go | 455 |
2 files changed, 0 insertions, 761 deletions
diff --git a/pkg/ascii/ascii.go b/pkg/ascii/ascii.go deleted file mode 100644 index d91a240..0000000 --- a/pkg/ascii/ascii.go +++ /dev/null @@ -1,306 +0,0 @@ -// package ascii implements an ASCII key-value parser. -// -// The top-most (de)serialize must operate on a struct pointer. A struct may -// contain other structs, in which case all tag names should be unique. Public -// fields without tag names are ignored. Private fields are also ignored. -// -// The supported field types are: -// - struct -// - string (no empty strings) -// - uint64 (only digits in ASCII representation) -// - byte array (only lower-case hex in ASCII representation) -// - slice of uint64 (no empty slices) -// - slice of byte array (no empty slices) -// -// A key must not contain an encoding's end-of-key value. -// A value must not contain an encoding's end-of-value value. -// -// For additional details, please refer to the Sigsum v0 API documentation. -// -package ascii - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "reflect" - "strconv" - "strings" - - "git.sigsum.org/sigsum-go/pkg/hex" -) - -var StdEncoding = NewEncoding("ascii", "=", "\n") - -type Encoding struct { - identifier string - endOfKey string - endOfValue string -} - -func NewEncoding(id, eok, eov string) *Encoding { - return &Encoding{ - identifier: id, - endOfKey: eok, - endOfValue: eov, - } -} - -type someValue struct { - v reflect.Value - ok bool -} - -// Serialize tries to serialize an interface as ASCII key-value pairs -func (e *Encoding) Serialize(w io.Writer, i interface{}) error { - v, err := dereferenceStructPointer(i) - if err != nil { - return err - } - - t := v.Type() - for i := 0; i < t.NumField(); i++ { - switch v.Field(i).Type().Kind() { - case reflect.Struct: - if err := e.Serialize(w, v.Field(i).Addr().Interface()); err != nil { - return err - } - default: - if t.Field(i).PkgPath != "" { - continue // skip private field - } - key, ok := t.Field(i).Tag.Lookup(e.identifier) - if !ok { - continue // skip public field without tag - } - - if strings.Contains(key, e.endOfKey) { - return fmt.Errorf("ascii: key %q contains end-of-key character", key) - } - if err := e.write(w, key, v.Field(i)); err != nil { - return err - } - } - } - return nil -} - -func (e *Encoding) write(w io.Writer, key string, v reflect.Value) error { - t := v.Type() - switch t { - case reflect.TypeOf(uint64(0)): - val := fmt.Sprintf("%d", v.Uint()) - return e.writeOne(w, key, val) - } - - k := t.Kind() - switch k { - case reflect.Array: - if kind := t.Elem().Kind(); kind != reflect.Uint8 { - return fmt.Errorf("ascii: array kind not supported: %v", kind) - } - - arrayLen := v.Len() - array := make([]byte, arrayLen, arrayLen) - for i := 0; i < arrayLen; i++ { - array[i] = uint8(v.Index(i).Uint()) - } - - val := hex.Serialize(array) - return e.writeOne(w, key, val) - - case reflect.Slice: - kind := t.Elem().Kind() - if kind != reflect.Array && kind != reflect.Uint64 { - return fmt.Errorf("ascii: slice kind not supported: %v", kind) - } - if v.Len() == 0 { - return fmt.Errorf("ascii: slice must not be empty") - } - - var err error - for i := 0; i < v.Len(); i++ { - err = e.write(w, key, v.Index(i)) - } - return err - - case reflect.String: - if v.Len() == 0 { - return fmt.Errorf("ascii: string must not be empty") - } - return e.writeOne(w, key, v.String()) - } - - return fmt.Errorf("ascii: unsupported type %v and kind %v", t, k) -} - -func (e *Encoding) writeOne(w io.Writer, key, value string) error { - _, err := w.Write([]byte(key + e.endOfKey + value + e.endOfValue)) - return err -} - -// Deserialize tries to deserialize a buffer of ASCII key-value pairs -func (e *Encoding) Deserialize(r io.Reader, i interface{}) error { - m := make(map[string]*someValue) - if err := e.mapKeys(i, m); err != nil { - return err - } - - buf, err := ioutil.ReadAll(r) - if err != nil { - return fmt.Errorf("ascii: failed reading incoming buffer") - } - - // trim end of buffer so that loop does not run on an empty line - if len(buf) <= len(e.endOfValue) { - return fmt.Errorf("ascii: buffer contains no key-value pair") - } - offset := len(buf) - len(e.endOfValue) - if !bytes.Equal(buf[offset:], []byte(e.endOfValue)) { - return fmt.Errorf("ascii: buffer must end with endOfValue") - } - buf = buf[:offset] - - for _, kv := range bytes.Split(buf, []byte(e.endOfValue)) { - split := bytes.Split(kv, []byte(e.endOfKey)) - if len(split) == 1 { - return fmt.Errorf("ascii: missing key-value pair in %q", string(kv)) - } - - key := string(split[0]) - value := string(bytes.Join(split[1:], nil)) - ref, ok := m[key] - if !ok { - return fmt.Errorf("ascii: unexpected key %q", key) - } - if len(value) == 0 { - fmt.Errorf("ascii: missing value for key %q", key) - } - if err := setKey(ref, key, value); err != nil { - return err - } - } - return requireValues(m) -} - -func (e *Encoding) mapKeys(i interface{}, m map[string]*someValue) error { - v, err := dereferenceStructPointer(i) - if err != nil { - return err - } - - t := v.Type() - for i := 0; i < t.NumField(); i++ { - switch v.Field(i).Type().Kind() { - case reflect.Struct: - i := v.Field(i).Addr().Interface() - e.mapKeys(i, m) // return is always nil - default: - if t.Field(i).PkgPath != "" { - continue // skip private field - } - key, ok := t.Field(i).Tag.Lookup(e.identifier) - if !ok { - continue // skip public field without tag - } - m[key] = &someValue{ - v: v.Field(i), - } - } - } - return nil -} - -func setKey(ref *someValue, key, value string) error { - v := ref.v - if v.Kind() == reflect.Ptr && !v.IsNil() { - v = v.Elem() - } - - t := v.Type() - switch t { - case reflect.TypeOf(uint64(0)): - num, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - - ref.ok = true - v.SetUint(num) - return nil - } - - k := t.Kind() - switch k { - case reflect.Array: - arrayLen := v.Len() - b, err := hex.Deserialize(value) - if err != nil { - return err - } - if len(b) != arrayLen { - return fmt.Errorf("ascii: invalid array size for key %q", key) - } - - ref.ok = true - reflect.Copy(v, reflect.ValueOf(b)) - return nil - - case reflect.Slice: - sliceType := t - kind := sliceType.Elem().Kind() - if kind != reflect.Array && kind != reflect.Uint64 { - return fmt.Errorf("ascii: slice kind not supported: %v", kind) - } - - if v.IsNil() { - v.Set(reflect.MakeSlice(sliceType, 0, 0)) - } - sv := &someValue{ - v: reflect.New(sliceType.Elem()), - } - if err := setKey(sv, key, value); err != nil { - return err - } - - ref.ok = true - v.Set(reflect.Append(v, sv.v.Elem())) - return nil - - case reflect.String: - if len(value) == 0 { - return fmt.Errorf("ascii: string must not be empty") - } - - ref.ok = true - v.SetString(value) - return nil - } - - return fmt.Errorf("ascii: unsupported type %v and kind %v", t, k) -} - -func requireValues(m map[string]*someValue) error { - for k, v := range m { - if !v.ok { - return fmt.Errorf("ascii: missing value for key %q", k) - } - } - return nil -} - -func dereferenceStructPointer(i interface{}) (*reflect.Value, error) { - v := reflect.ValueOf(i) - if v.Kind() != reflect.Ptr { - return nil, fmt.Errorf("ascii: interface value must be pointer") - } - if v.IsNil() { - return nil, fmt.Errorf("ascii: interface value must be non-nil pointer") - } - v = v.Elem() - if v.Type().Kind() != reflect.Struct { - return nil, fmt.Errorf("ascii: interface value must point to struct") - } - return &v, nil -} diff --git a/pkg/ascii/ascii_test.go b/pkg/ascii/ascii_test.go deleted file mode 100644 index 8aee70c..0000000 --- a/pkg/ascii/ascii_test.go +++ /dev/null @@ -1,455 +0,0 @@ -package ascii - -import ( - "bytes" - "reflect" - "testing" -) - -type testStruct struct { - Num uint64 `ascii/test:"num"` - Struct testStructOther - Skip uint64 - skip uint64 -} - -type testStructOther struct { - Array testArray `ascii/test:"array"` - Slice []testArray `ascii/test:"slice"` - String string `ascii/test:"string"` -} - -type testArray [2]byte - -type testStructUnsupportedType struct { - ByteSlice []byte `ascii/test:"byte_slice"` -} - -func TestSerialize(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - want string - err bool - i interface{} - }{ - { - desc: "invalid: not pointer to struct", - err: true, - i: testStruct{}, - }, - { - desc: "invalid: struct with invalid key", - err: true, - i: &struct { - Num uint64 `ascii/test:"num<--nom"` - }{ - Num: 1, - }, - }, - { - desc: "invalid: struct with invalid type", - err: true, - i: &testStructUnsupportedType{ - ByteSlice: []byte("hellow"), - }, - }, - { - desc: "invalid: struct with invalid type and kind", - err: true, - i: &struct { - Struct testStructUnsupportedType - }{ - Struct: testStructUnsupportedType{ - ByteSlice: []byte("hellow"), - }, - }, - }, - { - desc: "valid", - want: "num<--1;;array<--01fe;;slice<--01fe;;slice<--00ff;;string<--hellow;;", - i: &testStruct{ - Num: 1, - Struct: testStructOther{ - Array: testArray{1, 254}, - Slice: []testArray{ - testArray{1, 254}, - testArray{0, 255}, - }, - String: "hellow", - }, - }, - }, - } { - b := bytes.NewBuffer(nil) - err := e.Serialize(b, table.i) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - if got, want := string(b.Bytes()), table.want; got != want { - t.Errorf("got buf %s but wanted %s in test %q", got, want, table.desc) - } - } -} - -func TestWrite(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - want string - err bool - i interface{} - }{ - { - desc: "invalid: array with wrong type", - err: true, - i: [2]string{"first", "second"}, - }, - { - desc: "invalid: slice with wrong type", - err: true, - i: []string{"first", "second"}, - }, - { - desc: "invalid: empty slice with right type", - err: true, - i: make([][2]byte, 0), - }, - { - desc: "invalid: empty string", - err: true, - i: "", - }, - { - desc: "invalid: unsupported type and kind", - err: true, - i: int32(0), - }, - { - desc: "valid: uint64", - want: "some key<--1;;", - i: uint64(1), - }, - { - desc: "valid: byte array", - want: "some key<--01fe;;", - i: [2]byte{1, 254}, - }, - { - desc: "valid: slice array", - want: "some key<--01fe;;some key<--00ff;;", - i: [][2]byte{ - [2]byte{1, 254}, - [2]byte{0, 255}, - }, - }, - { - desc: "valid: slice uint64", - want: "some key<--1;;some key<--2;;", - i: []uint64{1, 2}, - }, - { - desc: "valid: string", - want: "some key<--some value;;", - i: "some value", - }, - } { - buf := bytes.NewBuffer(nil) - err := e.write(buf, "some key", reflect.ValueOf(table.i)) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - if got, want := string(buf.Bytes()), table.want; got != want { - t.Errorf("got buf %s but wanted %s in test %q", got, want, table.desc) - } - } -} - -func TestWriteOne(t *testing.T) { - buf := bytes.NewBuffer(nil) - e := NewEncoding("ascii/test", "<--", ";;") - e.writeOne(buf, "some key", "some value") - want := "some key<--some value;;" - if got := string(buf.Bytes()); got != want { - t.Errorf("got buf %s but wanted %s", got, want) - } -} - -func TestDeserialize(t *testing.T) { - e := NewEncoding("ascii/test", "<--", ";;") - for _, table := range []struct { - desc string - buf string - want interface{} - err bool - }{ - { - desc: "invalid: interface must be pointer to struct", - buf: ";", - want: uint64(0), - err: true, - }, - { - desc: "invalid: buffer too small", - buf: ";", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: buffer must end with endOfValue", - buf: "num<--1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff^^", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing key num", - buf: "string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing key-value pair on num line", - buf: "string<--hellow;;num;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing value for key num", - buf: "num<--;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: value for key num must be digits only", - buf: "num<--+1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "invalid: missing field for key num2", - buf: "num<--1;;string<--hellow;;num2<--2;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{}, - err: true, - }, - { - desc: "valid", - buf: "num<--1;;string<--hellow;;array<--01fe;;slice<--01fe;;slice<--00ff;;", - want: testStruct{ - Num: 1, - Struct: testStructOther{ - Array: testArray{1, 254}, - Slice: []testArray{ - testArray{1, 254}, - testArray{0, 255}, - }, - String: "hellow", - }, - }, - }, - } { - v := reflect.New(reflect.TypeOf(table.want)) - err := e.Deserialize(bytes.NewBuffer([]byte(table.buf)), v.Interface()) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - - v = v.Elem() // have pointer to struct, get just struct as in table - if got, want := v.Interface(), table.want; !reflect.DeepEqual(got, want) { - t.Errorf("got interface %v but wanted %v in test %q", got, want, table.desc) - } - } - -} - -func TestMapKeys(t *testing.T) { - s := testStruct{} - m := make(map[string]*someValue) - e := NewEncoding("ascii/test", "<--", ";;") - if err := e.mapKeys(s, m); err == nil { - t.Errorf("expected mapping to fail without pointer") - } - if err := e.mapKeys(&s, m); err != nil { - t.Errorf("expected mapping to succeed") - return - } - - wantKeys := []string{"num", "array", "slice", "string"} - if got, want := len(m), len(wantKeys); got != want { - t.Errorf("got %d keys, wanted %d", got, want) - } - for _, key := range wantKeys { - if _, ok := m[key]; !ok { - t.Errorf("expected key %q in map", key) - } - } -} - -func TestSetKey(t *testing.T) { - for _, table := range []struct { - desc string - key string - value string - want interface{} - err bool - }{ - { - desc: "invalid: unsupported type and kind", - key: "num", - value: "1", - want: uint32(1), - err: true, - }, - // uint64 - { - desc: "invalid: uint64: underflow", - key: "num", - value: "-1", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: overflow", - key: "num", - value: "18446744073709551616", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: not a number", - key: "num", - value: "+1", - want: uint64(0), - err: true, - }, - { - desc: "invalid: uint64: number with white space", - key: "num", - value: "1 ", - want: uint64(0), - err: true, - }, - { - desc: "valid: uint64", - key: "num", - value: "1", - want: uint64(1), - }, - // string - { - desc: "invalid: string: empty", - key: "string", - value: "", - want: "", - err: true, - }, - { - desc: "valid: string", - key: "string", - value: "hellow", - want: "hellow", - }, - // array - { - desc: "invalid: array: bad hex", - key: "array", - value: "00fE", - want: [2]byte{}, - err: true, - }, - { - desc: "invalid: array: wrong size", - key: "array", - value: "01fe", - want: [3]byte{}, - err: true, - }, - { - desc: "valid: array", - key: "num", - value: "01fe", - want: [2]byte{1, 254}, - }, - // slice - { - desc: "invalid: slice: bad type", - key: "slice", - value: "01fe", - want: []string{ - "hello", - }, - err: true, - }, - { - desc: "invalid: bad hex", - key: "slice", - value: "01FE", - want: [][2]byte{ - [2]byte{1, 254}, - }, - err: true, - }, - { - desc: "valid: slice", - key: "slice", - value: "01fe", - want: [][2]byte{ - [2]byte{1, 254}, - }, - }, - { - desc: "valid: slice", - key: "slice", - value: "4711", - want: []uint64{4711}, - }, - } { - ref := &someValue{ - v: reflect.New(reflect.TypeOf(table.want)), - } - err := setKey(ref, table.key, table.value) - if got, want := err != nil, table.err; got != want { - t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err) - } - if err != nil { - continue - } - - ref.v = ref.v.Elem() // get same type as table - if got, want := ref.v.Interface(), table.want; !reflect.DeepEqual(got, want) { - t.Errorf("got interface %v but wanted %v in test %q", got, want, table.desc) - } - if got, want := ref.ok, true; got != want { - t.Errorf("got ok %v but wanted %v in test %q", got, want, table.desc) - } - } -} - -func TestDereferenceStructPointer(t *testing.T) { - var ts testStruct - if _, err := dereferenceStructPointer(ts); err == nil { - t.Errorf("should have failed dereferencing non-pointer") - } - - var tsp *testStruct - if _, err := dereferenceStructPointer(tsp); err == nil { - t.Errorf("should have failed dereferencing nil-pointer") - } - - var ta testArray - if _, err := dereferenceStructPointer(&ta); err == nil { - t.Errorf("should have failed dereferencing non-struct pointer") - } - - if _, err := dereferenceStructPointer(&ts); err != nil { - t.Errorf("should have succeeded dereferencing pointer to struct") - } -} |