From 5b9d1e4fe8f4332d22177495738b525b41250f9b Mon Sep 17 00:00:00 2001 From: Yonah Date: Wed, 17 Sep 2025 05:41:40 +0900 Subject: [PATCH] generic: parse string representation of integer This makes the data easier to handle down the pipeline. Signed-off-by: Yonah --- generic.go | 23 +++++++++++++++++++ generic_test.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 generic.go create mode 100644 generic_test.go diff --git a/generic.go b/generic.go new file mode 100644 index 0000000..7c977b9 --- /dev/null +++ b/generic.go @@ -0,0 +1,23 @@ +package monstersirenfetch + +import ( + "encoding/json" + "strconv" +) + +// StringInt is a JSON string representing an integer. +type StringInt int + +func (i *StringInt) MarshalJSON() ([]byte, error) { return json.Marshal(strconv.Itoa(int(*i))) } +func (i *StringInt) UnmarshalJSON(data []byte) (err error) { + var v string + err = json.Unmarshal(data, &v) + if err == nil { + var n int + n, err = strconv.Atoi(v) + if err == nil { + *i = StringInt(n) + } + } + return +} diff --git a/generic_test.go b/generic_test.go new file mode 100644 index 0000000..84d4485 --- /dev/null +++ b/generic_test.go @@ -0,0 +1,60 @@ +package monstersirenfetch_test + +import ( + "encoding/json" + "reflect" + "strconv" + "testing" + "unsafe" + + . "git.gensokyo.uk/yonah/monstersirenfetch" +) + +func TestStringInt(t *testing.T) { + testCases := []struct { + name string + wantErr error + data string + val int + }{ + {"valid", nil, `"3735928559"`, 0xdeadbeef}, + {"invalid json", newSyntaxError("unexpected end of JSON input", 11), `"3735928559`, -1}, + {"invalid number", &strconv.NumError{Func: "Atoi", Num: ":3735928559", Err: strconv.ErrSyntax}, `":3735928559"`, -1}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.wantErr == nil { + t.Run("marshal", func(t *testing.T) { + v := StringInt(tc.val) + if got, err := json.Marshal(&v); err != nil { + t.Fatalf("Marshal: error = %v", err) + } else if string(got) != tc.data { + t.Errorf("Marshal: %s, want %s", string(got), tc.data) + } + }) + } + + t.Run("unmarshal", func(t *testing.T) { + var got StringInt + err := json.Unmarshal([]byte(tc.data), &got) + + if !reflect.DeepEqual(err, tc.wantErr) { + t.Errorf("Unmarshal: error = %v, want %v", err, tc.wantErr) + } + + if tc.wantErr == nil { + if int(got) != tc.val { + t.Errorf("Unmarshal: %d, want %d", got, tc.val) + } + } + }) + }) + } +} + +func newSyntaxError(msg string, offset int64) *json.SyntaxError { + e := &json.SyntaxError{Offset: offset} + msgV := reflect.ValueOf(e).Elem().FieldByName("msg") + reflect.NewAt(msgV.Type(), unsafe.Pointer(msgV.UnsafeAddr())).Elem().SetString(msg) + return e +}