Skip to content

Commit 542f7e5

Browse files
committed
cpe: add sql.Scanner interface
I think the original reason for omitting this was to try to avoid the `sql/driver.Value` dependency, but that's not really a big deal. This also add some additional testing, since code coverage is a thing the CI suite harasses me about, now. Signed-off-by: Hank Donnay <[email protected]>
1 parent e12cec6 commit 542f7e5

File tree

6 files changed

+235
-68
lines changed

6 files changed

+235
-68
lines changed

toolkit/types/cpe/json.go

-24
This file was deleted.

toolkit/types/cpe/marshaling.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cpe
2+
3+
import (
4+
"database/sql/driver"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
)
9+
10+
// MarshalText implements [encoding.TextMarshaler].
11+
func (w *WFN) MarshalText() ([]byte, error) {
12+
switch err := w.Valid(); {
13+
case err == nil:
14+
case errors.Is(err, ErrUnset):
15+
return []byte{}, nil
16+
default:
17+
return nil, err
18+
}
19+
return []byte(w.BindFS()), nil
20+
}
21+
22+
// UnmarshalText implements [encoding.TextUnmarshaler].
23+
func (w *WFN) UnmarshalText(b []byte) (err error) {
24+
if len(b) == 0 {
25+
return nil
26+
}
27+
*w, err = Unbind(string(b))
28+
return err
29+
}
30+
31+
// Scan implements [sql.Scanner].
32+
func (w *WFN) Scan(src interface{}) (err error) {
33+
switch src.(type) {
34+
case []byte:
35+
s := string(src.([]byte))
36+
s = strings.ToValidUTF8(s, "�")
37+
*w, err = Unbind(s)
38+
case string:
39+
*w, err = Unbind(src.(string))
40+
default:
41+
return fmt.Errorf("cpe: unable to Scan from type %T", src)
42+
}
43+
return err
44+
}
45+
46+
// Value implements [driver.Valuer].
47+
func (w *WFN) Value() (driver.Value, error) {
48+
switch err := w.Valid(); {
49+
case err == nil:
50+
case errors.Is(err, ErrUnset):
51+
return "", nil
52+
default:
53+
return nil, err
54+
}
55+
return w.BindFS(), nil
56+
}

toolkit/types/cpe/marshaling_test.go

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package cpe
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
)
8+
9+
func TestMarshal(t *testing.T) {
10+
t.Parallel()
11+
var names = []string{
12+
`cpe:2.3:a:foo\\bar:big\$money:2010:*:*:*:special:ipod_touch:80gb:*`,
13+
`cpe:2.3:a:foo\\bar:big\$money_2010:*:*:*:*:special:ipod_touch:80gb:*`,
14+
`cpe:2.3:a:hp:insight:7.4.0.1570:-:*:*:online:win2003:x64:*`,
15+
`cpe:2.3:a:hp:insight_diagnostics:7.4.0.1570:-:*:*:online:win2003:x64:*`,
16+
`cpe:2.3:a:hp:openview_network_manager:7.51:*:*:*:*:linux:*:*`,
17+
`cpe:2.3:a:microsoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*`,
18+
`cpe:2.3:a:microsoft:internet_explorer:8.*:sp?:*:*:*:*:*:*`,
19+
`cpe:2.3:a:microsoft:internet_explorer:8.\*:sp?:*:*:*:*:*:*`,
20+
}
21+
t.Run("JSON", func(t *testing.T) {
22+
for _, n := range names {
23+
var wfn WFN
24+
if err := wfn.UnmarshalText([]byte(n)); err != nil {
25+
t.Error(err)
26+
}
27+
b, err := wfn.MarshalText()
28+
if err != nil {
29+
t.Error(err)
30+
}
31+
if got, want := string(b), n; got != want {
32+
t.Error(cmp.Diff(got, want))
33+
}
34+
}
35+
t.Run("Unset", func(t *testing.T) {
36+
b, err := new(WFN).MarshalText()
37+
if err != nil {
38+
t.Error(err)
39+
}
40+
if b == nil {
41+
t.Error("return value unexpectedly nil")
42+
}
43+
if len(b) != 0 {
44+
t.Error("return value unexpectedly long")
45+
}
46+
})
47+
t.Run("Nil", func(t *testing.T) {
48+
if err := new(WFN).UnmarshalText(nil); err != nil {
49+
t.Error(err)
50+
}
51+
})
52+
t.Run("Error", func(t *testing.T) {
53+
var wfn WFN
54+
wfn.Attr[Part].Kind = ValueSet
55+
wfn.Attr[Part].V = "x"
56+
b, err := wfn.MarshalText()
57+
t.Log(err)
58+
if err == nil {
59+
t.Error("return error unexpectedly nil")
60+
}
61+
if b != nil {
62+
t.Error("return value unexpectedly not-nil")
63+
}
64+
})
65+
})
66+
t.Run("SQL", func(t *testing.T) {
67+
for _, n := range names {
68+
var wfn WFN
69+
if err := wfn.Scan(n); err != nil {
70+
t.Error(err)
71+
}
72+
if err := new(WFN).Scan([]byte(n)); err != nil {
73+
t.Error(err)
74+
}
75+
v, err := wfn.Value()
76+
if err != nil {
77+
t.Error(err)
78+
}
79+
if got, want := v.(string), n; got != want {
80+
t.Error(cmp.Diff(got, want))
81+
}
82+
}
83+
t.Run("Unset", func(t *testing.T) {
84+
v, err := new(WFN).Value()
85+
if err != nil {
86+
t.Error(err)
87+
}
88+
if v == nil {
89+
t.Error("return value unexpectedly nil")
90+
}
91+
if len(v.(string)) != 0 {
92+
t.Error("return value unexpectedly long")
93+
}
94+
})
95+
t.Run("Error", func(t *testing.T) {
96+
var wfn WFN
97+
wfn.Attr[Part].Kind = ValueSet
98+
wfn.Attr[Part].V = "x"
99+
b, err := wfn.Value()
100+
t.Log(err)
101+
if err == nil {
102+
t.Error("return error unexpectedly nil")
103+
}
104+
if b != nil {
105+
t.Error("return value unexpectedly not-nil")
106+
}
107+
})
108+
t.Run("Other", func(t *testing.T) {
109+
err := new(WFN).Scan(nil)
110+
t.Log(err)
111+
if err == nil {
112+
t.Error("return value unexpectedly nil")
113+
}
114+
})
115+
})
116+
t.Run("Stringer", func(t *testing.T) {
117+
for _, n := range names {
118+
var wfn WFN
119+
if err := wfn.UnmarshalText([]byte(n)); err != nil {
120+
t.Error(err)
121+
}
122+
if got, want := wfn.String(), n; got != want {
123+
t.Error(cmp.Diff(got, want))
124+
}
125+
}
126+
})
127+
}

toolkit/types/cpe/unbind.go

-8
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@ func UnbindURI(s string) (WFN, error) {
7474
}
7575

7676
func (v *Value) unbindURI(b *strings.Builder, s string) {
77-
if b == nil {
78-
b = &strings.Builder{}
79-
b.Grow(len(s))
80-
}
8177
b.Reset()
8278
switch s {
8379
case ``:
@@ -147,10 +143,6 @@ func UnbindFS(s string) (WFN, error) {
147143

148144
// UnbindFS undoes the FS binding and assigns it to v.
149145
func (v *Value) unbindFS(b *strings.Builder, s string) {
150-
if b == nil {
151-
b = &strings.Builder{}
152-
b.Grow(len(s))
153-
}
154146
switch s {
155147
case ``:
156148
v.Kind = ValueUnset

toolkit/types/cpe/wfn.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,18 @@ const (
147147
ValueSet
148148
)
149149

150+
// String implements [fmt.Stringer].
150151
func (v *Value) String() string {
151152
var b strings.Builder
152153
v.bind(&b)
153154
return b.String()
154155
}
155156

157+
// GoString implements [fmt.GoStringer].
158+
func (v *Value) GoString() string {
159+
return fmt.Sprintf(`Value{Kind: %s, V: %#q}`, v.Kind, v.V)
160+
}
161+
156162
// WFN is a well-formed name as defined by the Common Platform Enumeration (CPE)
157163
// spec: https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf
158164
//
@@ -169,7 +175,7 @@ func (w WFN) Valid() error {
169175
unset := 0
170176
for i := 0; i < NumAttr; i++ {
171177
if err := validate(w.Attr[i].V); err != nil {
172-
return fmt.Errorf("cpe: wfn attr %v is invalid: %w", Attribute(i), err)
178+
return fmt.Errorf("cpe: wfn attribute %q is invalid: %w", Attribute(i), err)
173179
}
174180
if v := &w.Attr[i]; v.Kind == ValueUnset {
175181
unset++
@@ -186,7 +192,7 @@ func (w WFN) Valid() error {
186192
if p := w.Attr[int(Part)]; p.Kind == ValueSet {
187193
if len(p.V) != 1 ||
188194
(p.V != app && p.V != os && p.V != hw) {
189-
return fmt.Errorf("cpe: wfn attr %v is invalid: %q is a disallowed value", Part, p.V)
195+
return fmt.Errorf("cpe: wfn attribute %q is invalid: %q is a disallowed value", Part, p.V)
190196
}
191197
}
192198
return nil
@@ -195,10 +201,25 @@ func (w WFN) Valid() error {
195201
// ErrUnset is returned from (WFN).Valid() if it is the zero value.
196202
var ErrUnset = errors.New("cpe: wfn is empty")
197203

204+
// String implements [fmt.Stringer].
198205
func (w WFN) String() string {
199206
return w.BindFS()
200207
}
201208

209+
// GoString implements [fmt.GoStringer].
210+
func (w WFN) GoString() string {
211+
var b strings.Builder
212+
b.WriteString(`WFN{Attr:[NumAttr]Value{`)
213+
for i := 0; i < NumAttr; i++ {
214+
if i != 0 {
215+
b.WriteString(", ")
216+
}
217+
b.WriteString(w.Attr[i].GoString())
218+
}
219+
b.WriteString(`}}`)
220+
return b.String()
221+
}
222+
202223
// These functions are defined in the spec to aid in implementation of other
203224
// algorithms, so they're implemented here in case they're needed.
204225

0 commit comments

Comments
 (0)