Skip to content

Commit 49bbd6e

Browse files
committed
webhook: use new json package
Signed-off-by: Hank Donnay <[email protected]>
1 parent 8cccca6 commit 49bbd6e

File tree

3 files changed

+164
-10
lines changed

3 files changed

+164
-10
lines changed

notifier/webhook/benchmarks_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package webhook
2+
3+
import (
4+
stdjson "encoding/json"
5+
"io"
6+
"net/url"
7+
"testing"
8+
9+
"github.com/google/uuid"
10+
11+
"github.com/quay/clair/v4/internal/codec"
12+
"github.com/quay/clair/v4/internal/json"
13+
"github.com/quay/clair/v4/notifier"
14+
)
15+
16+
func BenchmarkEncodingJSON(b *testing.B) {
17+
enc := stdjson.NewEncoder(io.Discard)
18+
obj := notifier.Callback{
19+
NotificationID: uuid.New(),
20+
}
21+
if err := obj.Callback.UnmarshalBinary([]byte("http://example.com/")); err != nil {
22+
b.Fatal(err)
23+
}
24+
25+
b.ReportAllocs()
26+
b.ResetTimer()
27+
for i := 0; i < b.N; i++ {
28+
err := enc.Encode(&obj)
29+
if err != nil {
30+
b.Error(err)
31+
}
32+
}
33+
}
34+
35+
func BenchmarkCodecJSON(b *testing.B) {
36+
enc := codec.GetEncoder(io.Discard)
37+
obj := notifier.Callback{
38+
NotificationID: uuid.New(),
39+
}
40+
if err := obj.Callback.UnmarshalBinary([]byte("http://example.com/")); err != nil {
41+
b.Fatal(err)
42+
}
43+
44+
b.ReportAllocs()
45+
b.ResetTimer()
46+
for i := 0; i < b.N; i++ {
47+
err := enc.Encode(&obj)
48+
if err != nil {
49+
b.Error(err)
50+
}
51+
}
52+
}
53+
54+
func BenchmarkExperimentalJSON(b *testing.B) {
55+
id := uuid.New()
56+
url, err := url.Parse("http://example.com")
57+
if err != nil {
58+
b.Fatal(err)
59+
}
60+
obj := callbackRequest{
61+
ID: &id,
62+
URL: url,
63+
}
64+
b.ReportAllocs()
65+
b.ResetTimer()
66+
for i := 0; i < b.N; i++ {
67+
err := json.MarshalWrite(io.Discard, &obj, options())
68+
if err != nil {
69+
b.Error(err)
70+
}
71+
}
72+
}

notifier/webhook/deliverer.go

+45-10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package webhook
33
import (
44
"context"
55
"errors"
6+
"io"
67
"net/http"
78
"net/url"
89
"sync"
@@ -12,9 +13,9 @@ import (
1213
"github.com/quay/zlog"
1314

1415
clairerror "github.com/quay/clair/v4/clair-error"
15-
"github.com/quay/clair/v4/internal/codec"
1616
"github.com/quay/clair/v4/internal/httputil"
17-
"github.com/quay/clair/v4/notifier"
17+
"github.com/quay/clair/v4/internal/json"
18+
"github.com/quay/clair/v4/internal/json/jsontext"
1819
)
1920

2021
// SignedOnce is used to print a deprecation notice, but only once per run.
@@ -67,26 +68,60 @@ func (d *Deliverer) Name() string {
6768
return "webhook"
6869
}
6970

71+
var options = sync.OnceValue(func() json.Options {
72+
return json.WithMarshalers(json.MarshalFuncV2(marshalCallback))
73+
})
74+
75+
func marshalCallback(enc *jsontext.Encoder, cb *callbackRequest, opts json.Options) error {
76+
if err := enc.WriteToken(jsontext.ObjectStart); err != nil {
77+
return err
78+
}
79+
if err := enc.WriteToken(jsontext.String(`callback`)); err != nil {
80+
return err
81+
}
82+
if err := enc.WriteToken(jsontext.String(cb.URL.String())); err != nil {
83+
return err
84+
}
85+
if err := enc.WriteToken(jsontext.String(`notification_id`)); err != nil {
86+
return err
87+
}
88+
if err := enc.WriteToken(jsontext.String(cb.ID.String())); err != nil {
89+
return err
90+
}
91+
return enc.WriteToken(jsontext.ObjectEnd)
92+
}
93+
94+
type callbackRequest struct {
95+
ID *uuid.UUID
96+
URL *url.URL
97+
}
98+
7099
// Deliver implements the notifier.Deliverer interface.
71100
//
72-
// Deliver POSTS a webhook data structure to the configured target.
101+
// Deliver POSTs a webhook data structure to the configured target.
73102
func (d *Deliverer) Deliver(ctx context.Context, nID uuid.UUID) error {
74103
ctx = zlog.ContextWithValues(ctx,
75104
"component", "notifier/webhook/Deliverer.Deliver",
76105
"notification_id", nID.String(),
77106
)
78107

79-
callback, err := d.callback.Parse(nID.String())
108+
url, err := d.callback.Parse(nID.String())
80109
if err != nil {
81110
return err
82111
}
83-
84-
wh := notifier.Callback{
85-
NotificationID: nID,
86-
Callback: *callback,
112+
cb := callbackRequest{
113+
ID: &nID,
114+
URL: url,
87115
}
88116

89-
req, err := httputil.NewRequestWithContext(ctx, http.MethodPost, d.target.String(), codec.JSONReader(&wh))
117+
rd, wr := io.Pipe()
118+
defer rd.Close()
119+
go func() {
120+
err := json.MarshalWrite(wr, &cb, options())
121+
wr.CloseWithError(err)
122+
}()
123+
124+
req, err := httputil.NewRequestWithContext(ctx, http.MethodPost, d.target.String(), rd)
90125
if err != nil {
91126
return err
92127
}
@@ -102,7 +137,7 @@ func (d *Deliverer) Deliver(ctx context.Context, nID uuid.UUID) error {
102137
}
103138

104139
zlog.Info(ctx).
105-
Stringer("callback", callback).
140+
Stringer("callback", url).
106141
Stringer("target", d.target).
107142
Msg("dispatching webhook")
108143

notifier/webhook/deliverer_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http/httptest"
88
"net/url"
99
"path"
10+
"strings"
1011
"sync"
1112
"testing"
1213

@@ -15,6 +16,7 @@ import (
1516
"github.com/quay/clair/config"
1617
"github.com/quay/zlog"
1718

19+
json2 "github.com/quay/clair/v4/internal/json"
1820
"github.com/quay/clair/v4/notifier"
1921
)
2022

@@ -81,3 +83,48 @@ func TestDeliverer(t *testing.T) {
8183
t.Fatalf("got: %v, wanted: %v", got, want)
8284
}
8385
}
86+
87+
func TestMarshal(t *testing.T) {
88+
// Check that the "v0" format (no specific media type) is identical to
89+
// stdlib output.
90+
t.Run("V0", func(t *testing.T) {
91+
var (
92+
callback = "http://clair-notifier/notifier/api/v1/notification/"
93+
noteID = uuid.New()
94+
)
95+
96+
want := func() string {
97+
v := notifier.Callback{
98+
NotificationID: noteID,
99+
}
100+
if err := v.Callback.UnmarshalBinary([]byte(callback)); err != nil {
101+
t.Error(err)
102+
}
103+
b, err := json.Marshal(&v)
104+
if err != nil {
105+
t.Error(err)
106+
}
107+
return string(b)
108+
}()
109+
110+
got := func() string {
111+
url, err := url.Parse(callback)
112+
if err != nil {
113+
t.Error(err)
114+
}
115+
var b strings.Builder
116+
cb := callbackRequest{
117+
ID: &noteID,
118+
URL: url,
119+
}
120+
if err := json2.MarshalWrite(&b, &cb, options()); err != nil {
121+
t.Error(err)
122+
}
123+
return b.String()
124+
}()
125+
126+
if !cmp.Equal(got, want) {
127+
t.Error(cmp.Diff(got, want))
128+
}
129+
})
130+
}

0 commit comments

Comments
 (0)