diff options
| author | Rose Hogenson <rosehogenson@posteo.net> | 2025-10-28 20:29:42 -0700 |
|---|---|---|
| committer | Rose Hogenson <rosehogenson@posteo.net> | 2025-10-28 20:32:18 -0700 |
| commit | d1a513e7cc46e576e644622ad7998bb8eb349e8b (patch) | |
| tree | 933826e1f9be83f14a49aadb25e47bfbbe98aa9e /asspb_test.go | |
| parent | 470d207390e5aa250fe7f50037fee1c6bf8aace9 (diff) | |
| download | ccl-d1a513e7cc46e576e644622ad7998bb8eb349e8b.tar.zst | |
Rework struct unpacking
Diffstat (limited to 'asspb_test.go')
| -rw-r--r-- | asspb_test.go | 242 |
1 files changed, 220 insertions, 22 deletions
diff --git a/asspb_test.go b/asspb_test.go index bda34bf..fdf8b9b 100644 --- a/asspb_test.go +++ b/asspb_test.go @@ -1,7 +1,9 @@ package asspb import ( + "fmt" "testing" + "time" "github.com/google/go-cmp/cmp" ) @@ -15,13 +17,25 @@ func TestUnmarshal(t *testing.T) { type message struct { String string `ccl:"string"` String2 string `ccl:"string2"` - Int int64 `ccl:"int"` + Int int `ccl:"int"` + Int8 int8 `ccl:"int8"` + Int16 int16 `ccl:"int16"` + Int32 int32 `ccl:"int32"` + Int64 int64 `ccl:"int64"` + Uint uint `ccl:"uint"` + Uint8 uint8 `ccl:"uint8"` + Uint16 uint16 `ccl:"uint16"` + Uint32 uint32 `ccl:"uint32"` + Uint64 uint64 `ccl:"uint64"` Float float64 `ccl:"float"` Bool bool `ccl:"bool"` Bool2 bool `ccl:"bool2"` Message *nestedMessage `ccl:"message"` Repeated []int64 `ccl:"repeated"` RepeatedMessage []*nestedMessage `ccl:"repeated_message"` + Bytes []byte `ccl:"bytes"` + Time time.Time `ccl:"time"` + TimePointer *time.Time `ccl:"time_pointer"` Ignore map[int]int `ccl:"-,"` // unlike JSON this also means ignore unexported int64 @@ -33,18 +47,19 @@ func TestUnmarshal(t *testing.T) { want message }{{ desc: "Complete", - msg: `# This is a comment -string: 'asdf\n' # comment end of line -string2: "asdf\n" -int: 10 -float: 10.5e13 -bool: true -bool2: false -message { field: 10 } -repeated [1, 2, 3] -repeated: 4 -repeated [5, 6] -`, + msg: ` + # This is a comment + string: 'asdf\n' # comment end of line + string2: "asdf\n" + int: 10 + float: 10.5e13 + bool: true + bool2: false + message { field: 10 } + repeated: [1, 2, 3] + repeated: 4 + repeated: [5, 6] + `, want: message{ String: "asdf\n", String2: "asdf\n", @@ -77,6 +92,14 @@ can just span multiple lines"`, msg: `int: 0x0f`, want: message{Int: 15}, }, { + desc: "NegativeHex", + msg: `int: -0x0f`, + want: message{Int: -15}, + }, { + desc: "PositiveHex", + msg: `int: +0x0f`, + want: message{Int: 15}, + }, { desc: "Float", msg: `float: 1.5e10`, want: message{Float: 1.5e10}, @@ -105,6 +128,66 @@ can just span multiple lines"`, msg: `int: +10`, want: message{Int: 10}, }, { + desc: "Int8", + msg: `int8:1`, + want: message{Int8: 1}, + }, { + desc: "Int16", + msg: `int16:1`, + want: message{Int16: 1}, + }, { + desc: "Int32", + msg: `int32:1`, + want: message{Int32: 1}, + }, { + desc: "Int64", + msg: `int64:1`, + want: message{Int64: 1}, + }, { + desc: "Uint", + msg: `uint:1`, + want: message{Uint: 1}, + }, { + desc: "Uint8", + msg: `uint8:1`, + want: message{Uint8: 1}, + }, { + desc: "Uint16", + msg: `uint16:1`, + want: message{Uint16: 1}, + }, { + desc: "Uint32", + msg: `uint32:1`, + want: message{Uint32: 1}, + }, { + desc: "Uint64", + msg: `uint64:1`, + want: message{Uint64: 1}, + }, { + desc: "IntTrue", + msg: `int:on`, + want: message{Int: 1}, + }, { + desc: "IntFalse", + msg: `int:no`, + want: message{Int: 0}, + }, { + desc: "UintTrue", + msg: `uint:on`, + want: message{Uint: 1}, + }, { + desc: "UintFalse", + msg: `uint:no`, + want: message{Uint: 0}, + }, { + desc: "IntFloat", + msg: `float:-1`, + want: message{Float: -1}, + }, { + desc: "UintFloat", + msg: `float:1`, + want: message{Float: 1}, + }, { desc: "String", msg: `string: 'asdf'`, want: message{String: "asdf"}, @@ -234,6 +317,18 @@ from string'`, desc: "RemoveNewlineWindows", msg: "string: 'remove newline \\\r\nfrom string'", want: message{String: "remove newline from string"}, + }, { + desc: "Base64", + msg: `bytes:"dGVzdA=="`, + want: message{Bytes: []byte("test")}, + }, { + desc: "TextUnmarshaler", + msg: `time:"2025-10-28T07:41:47Z"`, + want: message{Time: time.Date(2025, time.October, 28, 7, 41, 47, 0, time.UTC)}, + }, { + desc: "TextUnmarshalerPointer", + msg: `time_pointer:"2025-10-28T07:41:47Z"`, + want: message{TimePointer: &[]time.Time{time.Date(2025, time.October, 28, 7, 41, 47, 0, time.UTC)}[0]}, }} { t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -257,10 +352,12 @@ func TestUnmarshal_Invalid(t *testing.T) { } type message struct { Int int64 `ccl:"int"` + Int8 int8 `ccl:"int8"` String string `ccl:"string"` Msg nestedMessage `ccl:"msg"` Repeated []int64 `ccl:"repeated"` RepeatedMsg []nestedMessage `ccl:"repeated_msg"` + Bytes []byte `ccl:"bytes"` } for _, tc := range []struct { @@ -288,14 +385,17 @@ func TestUnmarshal_Invalid(t *testing.T) { desc: "MsgNoFieldName", msg: `msg {10}`, }, { + desc: "ListMissingColon", + msg: `repeated []`, + }, { desc: "ListMissingComma", - msg: `repeated [1 2]`, + msg: `repeated: [1 2]`, }, { desc: "ListBadVal", - msg: `repeated [asdf]`, + msg: `repeated: [asdf]`, }, { desc: "ListBadMsgVal", - msg: `repeated_msg [{asdf}]`, + msg: `repeated_msg: [{asdf}]`, }, { desc: "IntLeadingZero", msg: `int: 0644`, @@ -311,6 +411,21 @@ func TestUnmarshal_Invalid(t *testing.T) { }, { desc: "FieldMissingColon", msg: `string "abc"`, + }, { + desc: "Repeated", + msg: `int:5 int:6`, + }, { + desc: "IntOutOfRange", + msg: `int8:512`, + }, { + desc: "IntOutOfRangeNegative", + msg: `int8:-512`, + }, { + desc: "Base64", + msg: `bytes:"dGVzdAo"`, + }, { + desc: "BadField", + msg: `asdfasdfasdf:"asdf"`, }} { t.Run(tc.desc, func(t *testing.T) { t.Parallel() @@ -336,6 +451,10 @@ func TestUnmarshal_InvalidType(t *testing.T) { msg: `field {}`, out: (*struct{})(nil), }, { + desc: "NilMap", + msg: `field {}`, + out: map[string]any(nil), + }, { desc: "Struct", msg: `field {}`, out: new(int), @@ -344,6 +463,10 @@ func TestUnmarshal_InvalidType(t *testing.T) { msg: `Field: 123`, out: new(struct{ Field string }), }, { + desc: "NegativeInt", + msg: `Field: -123`, + out: new(struct{ Field string }), + }, { desc: "IntHex", msg: `Field: 0x123`, out: new(struct{ Field string }), @@ -354,15 +477,15 @@ func TestUnmarshal_InvalidType(t *testing.T) { }, { desc: "NestedMessage", msg: `Field {Field {}}`, - out: new(struct{ Field int64 }), + out: new(struct{ Field struct{ Field int64 } }), }, { desc: "True", msg: `F:on`, - out: new(struct{ F int64 }), + out: new(struct{ F string }), }, { desc: "False", msg: `F:no`, - out: new(struct{ F int64 }), + out: new(struct{ F string }), }, { desc: "List", msg: `F:[]`, @@ -370,18 +493,55 @@ func TestUnmarshal_InvalidType(t *testing.T) { }, { desc: "RepeatedBool", msg: `F:on F:no`, - out: new(struct{ F []int64 }), + out: new(struct{ F []string }), }, { desc: "String", msg: `F:"abc"`, out: new(struct{ F int64 }), + }, { + desc: "Option", + msg: `F:"abc"`, + out: new(struct { + F string `ccl:",asdf"` + }), + }, { + desc: "OptionNested", + msg: `F{F:"abc"}`, + out: new(struct { + F struct { + F string `ccl:",asdf"` + } + }), + }, { + desc: "OptionNestedPointer", + msg: `F{F:"abc"}`, + out: new(struct { + F *struct { + F string `ccl:",asdf"` + } + }), + }, { + desc: "OptionSlicePointer", + msg: `F{F:"abc"}`, + out: new(struct { + F []*struct { + F string `ccl:",asdf"` + } + }), + }, { + desc: "RepeatedTagName", + msg: `F:"abc"`, + out: new(struct { + F string + G string `ccl:"F"` + }), }} { t.Run(tc.desc, func(t *testing.T) { t.Parallel() err := Unmarshal([]byte(tc.msg), tc.out) if err == nil { - t.Errorf("Unmarshal(%+v) returned success, want error", tc.out) + t.Errorf("Unmarshal(%q) returned %+v, want error", tc.msg, tc.out) } }) } @@ -403,10 +563,48 @@ func TestUnmarshal_ErrorLineCol(t *testing.T) { err := Unmarshal([]byte(msg), new(message)) syntaxErr, ok := err.(*syntaxError) if !ok { - t.Errorf("Unmarshal(%q): expected *syntaxError, got error %T %[2]v", msg, err) + t.Fatalf("Unmarshal(%q): expected *syntaxError, got error %T %[2]v", msg, err) } want := &syntaxError{line: 5, col: 15} if syntaxErr.line != want.line || syntaxErr.col != want.col { t.Errorf("Unmarshal(%q) returned error %+v, want line %d, col %d", msg, syntaxErr, want.line, want.col) } } + +func ExampleUnmarshal() { + // Pretend this was loaded from a file + msg := []byte(` + server { + listen: "0.0.0.0:80" + listen: "[::0]:80" + location { + path: "/" + return: "301 https://$host$request_uri" # redirect to https + } + location { + path: "/.well-known/acme-challenge/" + root: "/var/lib/acme/acme-challenge" + auth_basic: off + auth_request: off + } + } + `) + var serverConfig struct { + Server struct { + Listen []string `ccl:"listen"` + Location []struct { + Path string `ccl:"path"` + Root string `ccl:"root"` + AuthBasic bool `ccl:"auth_basic"` + AuthRequest bool `ccl:"auth_request"` + Return string `ccl:"return"` + } `ccl:"location"` + } `ccl:"server"` + } + if err := Unmarshal(msg, &serverConfig); err != nil { + panic(err) + } + fmt.Println(serverConfig.Server.Location[0].Return) + // Output: + // 301 https://$host$request_uri +} |
