Skip to content

Commit 719a4aa

Browse files
authored
Merge pull request #33 from upfluence/sp/json-type
Add serialyser in JSON
2 parents c575cb6 + 2a06b6a commit 719a4aa

File tree

5 files changed

+212
-3
lines changed

5 files changed

+212
-3
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-20.04
1313
strategy:
1414
matrix:
15-
go: [ '1.16.x', '1.15.x', '1.14.x' ]
15+
go: [ '1.18.x', '1.17.x', '1.16.x' ]
1616
services:
1717
postgres:
1818
image: postgres

.github/workflows/lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
- name: Install Go
1111
uses: actions/setup-go@v2
1212
with:
13-
go-version: 1.16.x
13+
go-version: 1.18.x
1414
- name: Check out code
1515
uses: actions/checkout@v1
1616
- name: golanci-lint

go.mod

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/upfluence/sql
22

3-
go 1.16
3+
go 1.18
44

55
require (
66
github.com/lib/pq v1.4.0
@@ -9,3 +9,9 @@ require (
99
github.com/upfluence/errors v0.2.2
1010
github.com/upfluence/log v0.0.4
1111
)
12+
13+
require (
14+
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
17+
)

sqltypes/json.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package sqltypes
2+
3+
import (
4+
"database/sql/driver"
5+
"encoding/json"
6+
7+
"github.com/upfluence/errors"
8+
)
9+
10+
type JSONValue struct {
11+
Data interface{}
12+
Valid bool
13+
}
14+
15+
func (jv *JSONValue) Scan(v interface{}) error {
16+
var err error
17+
18+
jv.Valid = false
19+
20+
switch vv := v.(type) {
21+
case nil:
22+
return nil
23+
case []byte:
24+
err = json.Unmarshal(vv, &jv.Data)
25+
case string:
26+
err = json.Unmarshal([]byte(vv), &jv.Data)
27+
default:
28+
err = errors.Wrap(errInvalidType, "expecting a byte slice or string")
29+
}
30+
31+
if err != nil {
32+
return err
33+
}
34+
35+
jv.Valid = true
36+
return nil
37+
}
38+
39+
func (jv JSONValue) Value() (driver.Value, error) {
40+
if !jv.Valid {
41+
return nil, nil
42+
}
43+
44+
return json.Marshal(jv.Data)
45+
}

sqltypes/json_test.go

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package sqltypes
2+
3+
import (
4+
"database/sql"
5+
"database/sql/driver"
6+
"encoding/json"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/upfluence/errors/errtest"
11+
)
12+
13+
type testPayload struct {
14+
Key string `json:"key"`
15+
}
16+
17+
func TestScan(t *testing.T) {
18+
for _, tt := range []struct {
19+
name string
20+
21+
value interface{}
22+
want JSONValue
23+
wantErr errtest.ErrorAssertion
24+
}{
25+
{
26+
name: "wrong value type",
27+
value: 1,
28+
want: JSONValue{Data: &testPayload{}},
29+
wantErr: errtest.ErrorCause(errInvalidType),
30+
},
31+
{
32+
name: "invalid json",
33+
value: "test",
34+
want: JSONValue{Data: &testPayload{}},
35+
wantErr: errtest.ErrorAssertionFunc(
36+
func(tb testing.TB, err error) {
37+
assert.IsType(t, &json.SyntaxError{}, err)
38+
},
39+
),
40+
},
41+
{
42+
name: "nil",
43+
want: JSONValue{Data: &testPayload{}},
44+
wantErr: errtest.NoError(),
45+
},
46+
{
47+
name: "success",
48+
value: `{"key":"foo"}`,
49+
want: JSONValue{Data: &testPayload{Key: "foo"}, Valid: true},
50+
wantErr: errtest.NoError(),
51+
},
52+
} {
53+
t.Run(tt.name, func(t *testing.T) {
54+
var jv = JSONValue{Data: &testPayload{}}
55+
56+
err := jv.Scan(tt.value)
57+
tt.wantErr.Assert(t, err)
58+
assert.Equal(t, tt.want, jv)
59+
})
60+
}
61+
}
62+
63+
func TestShadowScan(t *testing.T) {
64+
for _, tt := range []struct {
65+
name string
66+
67+
scanner sql.Scanner
68+
value interface{}
69+
want interface{}
70+
}{
71+
{
72+
name: "override object",
73+
scanner: &JSONValue{
74+
Data: &testPayload{Key: "foo"},
75+
},
76+
value: `{"key":"bar"}`,
77+
want: &JSONValue{
78+
Data: &testPayload{Key: "bar"},
79+
Valid: true,
80+
},
81+
},
82+
{
83+
name: "override object with nil becomes invalid",
84+
scanner: &JSONValue{
85+
Data: &testPayload{Key: "foo"},
86+
Valid: true,
87+
},
88+
want: &JSONValue{
89+
Data: &testPayload{Key: "foo"},
90+
},
91+
},
92+
{
93+
name: "override map with nil becomes invalid",
94+
scanner: &JSONValue{
95+
Data: map[string]interface{}{"foo": 1},
96+
},
97+
want: &JSONValue{
98+
Data: map[string]interface{}{"foo": 1},
99+
Valid: false,
100+
},
101+
},
102+
} {
103+
t.Run(tt.name, func(t *testing.T) {
104+
err := tt.scanner.Scan(tt.value)
105+
assert.NoError(t, err)
106+
assert.Equal(t, tt.want, tt.scanner)
107+
})
108+
}
109+
}
110+
111+
func TestValue(t *testing.T) {
112+
for _, tt := range []struct {
113+
name string
114+
115+
value driver.Valuer
116+
want interface{}
117+
wantErr error
118+
}{
119+
{
120+
name: "invalid data",
121+
value: JSONValue{
122+
Data: testPayload{},
123+
},
124+
},
125+
{
126+
name: "string",
127+
value: JSONValue{
128+
Data: "test",
129+
Valid: true,
130+
},
131+
want: []byte(`"test"`),
132+
},
133+
{
134+
name: "number",
135+
value: JSONValue{
136+
Data: 1,
137+
Valid: true,
138+
},
139+
want: []byte(`1`),
140+
},
141+
{
142+
name: "object",
143+
value: JSONValue{
144+
Data: testPayload{
145+
Key: "foo",
146+
},
147+
Valid: true,
148+
},
149+
want: []byte(`{"key":"foo"}`),
150+
},
151+
} {
152+
t.Run(tt.name, func(t *testing.T) {
153+
v, err := tt.value.Value()
154+
assert.Equal(t, tt.wantErr, err)
155+
assert.Equal(t, tt.want, v)
156+
})
157+
}
158+
}

0 commit comments

Comments
 (0)