aboutsummaryrefslogtreecommitdiffstats
path: root/asspb_test.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-10-28 20:29:42 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-10-28 20:32:18 -0700
commitd1a513e7cc46e576e644622ad7998bb8eb349e8b (patch)
tree933826e1f9be83f14a49aadb25e47bfbbe98aa9e /asspb_test.go
parent470d207390e5aa250fe7f50037fee1c6bf8aace9 (diff)
downloadccl-d1a513e7cc46e576e644622ad7998bb8eb349e8b.tar.zst
Rework struct unpacking
Diffstat (limited to 'asspb_test.go')
-rw-r--r--asspb_test.go242
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
+}