Skip to content

Commit c829c69

Browse files
committed
dashboard/app: add typed handler middleware
Remove duplicated code related to request deserialization using middleware.
1 parent e14dbeb commit c829c69

File tree

2 files changed

+59
-150
lines changed

2 files changed

+59
-150
lines changed

dashboard/app/api.go

Lines changed: 53 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,20 @@ func initAPIHandlers() {
4646
}
4747

4848
var apiHandlers = map[string]APIHandler{
49-
"log_error": apiLogError,
50-
"job_poll": apiJobPoll,
51-
"job_reset": apiJobReset,
52-
"job_done": apiJobDone,
53-
"reporting_poll_bugs": apiReportingPollBugs,
54-
"reporting_poll_notifs": apiReportingPollNotifications,
55-
"reporting_poll_closed": apiReportingPollClosed,
56-
"reporting_update": apiReportingUpdate,
57-
"new_test_job": apiNewTestJob,
58-
"needed_assets": apiNeededAssetsList,
59-
"load_full_bug": apiLoadFullBug,
60-
"save_discussion": apiSaveDiscussion,
61-
"create_upload_url": apiCreateUploadURL,
62-
"send_email": apiSendEmail,
49+
"log_error": typedHandler(apiLogError),
50+
"job_poll": typedHandler(apiJobPoll),
51+
"job_reset": typedHandler(apiJobReset),
52+
"job_done": typedHandler(apiJobDone),
53+
"reporting_poll_bugs": typedHandler(apiReportingPollBugs),
54+
"reporting_poll_notifs": typedHandler(apiReportingPollNotifications),
55+
"reporting_poll_closed": typedHandler(apiReportingPollClosed),
56+
"reporting_update": typedHandler(apiReportingUpdate),
57+
"new_test_job": typedHandler(apiNewTestJob),
58+
"needed_assets": typedHandler(apiNeededAssetsList),
59+
"load_full_bug": typedHandler(apiLoadFullBug),
60+
"save_discussion": typedHandler(apiSaveDiscussion),
61+
"create_upload_url": typedHandler(apiCreateUploadURL),
62+
"send_email": typedHandler(apiSendEmail),
6363
"save_coverage": gcsPayloadHandler(apiSaveCoverage),
6464
"upload_build": nsHandler(apiUploadBuild),
6565
"builder_poll": nsHandler(apiBuilderPoll),
@@ -79,7 +79,6 @@ var apiHandlers = map[string]APIHandler{
7979

8080
type JSONHandler func(c context.Context, r *http.Request) (interface{}, error)
8181
type APIHandler func(c context.Context, payload io.Reader) (interface{}, error)
82-
type APINamespaceHandler func(c context.Context, ns string, payload io.Reader) (interface{}, error)
8382

8483
const (
8584
maxReproPerBug = 10
@@ -206,30 +205,34 @@ func gcsPayloadHandler(handler APIHandler) APIHandler {
206205
}
207206
}
208207

209-
func nsHandler(handler APINamespaceHandler) APIHandler {
210-
return func(c context.Context, payload io.Reader) (interface{}, error) {
211-
ns := contextNamespace(c)
208+
func nsHandler[Req any](handler func(context.Context, string, *Req) (any, error)) APIHandler {
209+
return typedHandler(func(ctx context.Context, req *Req) (any, error) {
210+
ns := contextNamespace(ctx)
212211
if ns == "" {
213212
return nil, fmt.Errorf("must be called within a namespace")
214213
}
215-
return handler(c, ns, payload)
216-
}
214+
return handler(ctx, ns, req)
215+
})
217216
}
218217

219-
func apiLogError(c context.Context, payload io.Reader) (interface{}, error) {
220-
req := new(dashapi.LogEntry)
221-
if err := json.NewDecoder(payload).Decode(req); err != nil {
222-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
218+
func typedHandler[Req any](handler func(context.Context, *Req) (any, error)) APIHandler {
219+
return func(ctx context.Context, payload io.Reader) (interface{}, error) {
220+
req := new(Req)
221+
if payload != nil {
222+
if err := json.NewDecoder(payload).Decode(req); err != nil {
223+
return nil, fmt.Errorf("failed to unmarshal request %T: %w", req, err)
224+
}
225+
}
226+
return handler(ctx, req)
223227
}
228+
}
229+
230+
func apiLogError(c context.Context, req *dashapi.LogEntry) (interface{}, error) {
224231
log.Errorf(c, "%v: %v", req.Name, req.Text)
225232
return nil, nil
226233
}
227234

228-
func apiBuilderPoll(c context.Context, ns string, payload io.Reader) (interface{}, error) {
229-
req := new(dashapi.BuilderPollReq)
230-
if err := json.NewDecoder(payload).Decode(req); err != nil {
231-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
232-
}
235+
func apiBuilderPoll(c context.Context, ns string, req *dashapi.BuilderPollReq) (interface{}, error) {
233236
bugs, _, err := loadAllBugs(c, func(query *db.Query) *db.Query {
234237
return query.Filter("Namespace=", ns).
235238
Filter("Status<", BugStatusFixed)
@@ -274,7 +277,7 @@ func reportEmail(c context.Context, ns string) string {
274277
return ""
275278
}
276279

277-
func apiCommitPoll(c context.Context, ns string, payload io.Reader) (interface{}, error) {
280+
func apiCommitPoll(c context.Context, ns string, req *any) (interface{}, error) {
278281
resp := &dashapi.CommitPollResp{
279282
ReportEmail: reportEmail(c, ns),
280283
}
@@ -340,11 +343,7 @@ func pollBackportCommits(c context.Context, ns string, count int) ([]string, err
340343
return backportTitles, nil
341344
}
342345

343-
func apiUploadCommits(c context.Context, ns string, payload io.Reader) (interface{}, error) {
344-
req := new(dashapi.CommitPollResultReq)
345-
if err := json.NewDecoder(payload).Decode(req); err != nil {
346-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
347-
}
346+
func apiUploadCommits(c context.Context, ns string, req *dashapi.CommitPollResultReq) (interface{}, error) {
348347
// This adds fixing commits to bugs.
349348
err := addCommitsToBugs(c, ns, "", nil, req.Commits)
350349
if err != nil {
@@ -445,46 +444,28 @@ func addCommitInfoToBugImpl(c context.Context, bug *Bug, com dashapi.Commit) (bo
445444
return changed, nil
446445
}
447446

448-
func apiJobPoll(c context.Context, payload io.Reader) (interface{}, error) {
447+
func apiJobPoll(c context.Context, req *dashapi.JobPollReq) (interface{}, error) {
449448
if stop, err := emergentlyStopped(c); err != nil || stop {
450449
// The bot's operation was aborted. Don't accept new crash reports.
451450
return &dashapi.JobPollResp{}, err
452451
}
453-
req := new(dashapi.JobPollReq)
454-
if err := json.NewDecoder(payload).Decode(req); err != nil {
455-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
456-
}
457452
if len(req.Managers) == 0 {
458453
return nil, fmt.Errorf("no managers")
459454
}
460455
return pollPendingJobs(c, req.Managers)
461456
}
462457

463-
// nolint: dupl
464-
func apiJobDone(c context.Context, payload io.Reader) (interface{}, error) {
465-
req := new(dashapi.JobDoneReq)
466-
if err := json.NewDecoder(payload).Decode(req); err != nil {
467-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
468-
}
458+
func apiJobDone(c context.Context, req *dashapi.JobDoneReq) (interface{}, error) {
469459
err := doneJob(c, req)
470460
return nil, err
471461
}
472462

473-
// nolint: dupl
474-
func apiJobReset(c context.Context, payload io.Reader) (interface{}, error) {
475-
req := new(dashapi.JobResetReq)
476-
if err := json.NewDecoder(payload).Decode(req); err != nil {
477-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
478-
}
463+
func apiJobReset(c context.Context, req *dashapi.JobResetReq) (interface{}, error) {
479464
err := resetJobs(c, req)
480465
return nil, err
481466
}
482467

483-
func apiUploadBuild(c context.Context, ns string, payload io.Reader) (interface{}, error) {
484-
req := new(dashapi.Build)
485-
if err := json.NewDecoder(payload).Decode(req); err != nil {
486-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
487-
}
468+
func apiUploadBuild(c context.Context, ns string, req *dashapi.Build) (interface{}, error) {
488469
now := timeNow(c)
489470
_, isNewBuild, err := uploadBuild(c, now, ns, req, BuildNormal)
490471
if err != nil {
@@ -751,11 +732,7 @@ func managerList(c context.Context, ns string) ([]string, error) {
751732
return managers, nil
752733
}
753734

754-
func apiReportBuildError(c context.Context, ns string, payload io.Reader) (interface{}, error) {
755-
req := new(dashapi.BuildErrorReq)
756-
if err := json.NewDecoder(payload).Decode(req); err != nil {
757-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
758-
}
735+
func apiReportBuildError(c context.Context, ns string, req *dashapi.BuildErrorReq) (interface{}, error) {
759736
now := timeNow(c)
760737
build, _, err := uploadBuild(c, now, ns, &req.Build, BuildFailed)
761738
if err != nil {
@@ -785,15 +762,11 @@ const (
785762
suppressedReportTitle = "suppressed report"
786763
)
787764

788-
func apiReportCrash(c context.Context, ns string, payload io.Reader) (interface{}, error) {
765+
func apiReportCrash(c context.Context, ns string, req *dashapi.Crash) (interface{}, error) {
789766
if stop, err := emergentlyStopped(c); err != nil || stop {
790767
// The bot's operation was aborted. Don't accept new crash reports.
791768
return &dashapi.ReportCrashResp{}, err
792769
}
793-
req := new(dashapi.Crash)
794-
if err := json.NewDecoder(payload).Decode(req); err != nil {
795-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
796-
}
797770
build, err := loadBuild(c, ns, req.BuildID)
798771
if err != nil {
799772
return nil, err
@@ -1094,13 +1067,8 @@ func purgeOldCrashes(c context.Context, bug *Bug, bugKey *db.Key) {
10941067
log.Infof(c, "deleted %v crashes for bug %q", deleted, bug.Title)
10951068
}
10961069

1097-
func apiReportFailedRepro(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1098-
req := new(dashapi.CrashID)
1099-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1100-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1101-
}
1070+
func apiReportFailedRepro(c context.Context, ns string, req *dashapi.CrashID) (interface{}, error) {
11021071
req.Title = canonicalizeCrashTitle(req.Title, req.Corrupted, req.Suppressed)
1103-
11041072
bug, err := findExistingBugForCrash(c, ns, []string{req.Title})
11051073
if err != nil {
11061074
return nil, err
@@ -1166,11 +1134,7 @@ func saveReproAttempt(c context.Context, bug *Bug, build *Build, log []byte) err
11661134
return nil
11671135
}
11681136

1169-
func apiNeedRepro(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1170-
req := new(dashapi.CrashID)
1171-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1172-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1173-
}
1137+
func apiNeedRepro(c context.Context, ns string, req *dashapi.CrashID) (interface{}, error) {
11741138
if req.Corrupted {
11751139
resp := &dashapi.NeedReproResp{
11761140
NeedRepro: false,
@@ -1218,11 +1182,7 @@ func normalizeCrashTitle(title string) string {
12181182
return strings.TrimSpace(limitLength(title, maxTextLen))
12191183
}
12201184

1221-
func apiManagerStats(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1222-
req := new(dashapi.ManagerStatsReq)
1223-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1224-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1225-
}
1185+
func apiManagerStats(c context.Context, ns string, req *dashapi.ManagerStatsReq) (interface{}, error) {
12261186
now := timeNow(c)
12271187
err := updateManager(c, ns, req.Name, func(mgr *Manager, stats *ManagerStats) error {
12281188
mgr.Link = req.Addr
@@ -1243,11 +1203,7 @@ func apiManagerStats(c context.Context, ns string, payload io.Reader) (interface
12431203
return nil, err
12441204
}
12451205

1246-
func apiUpdateReport(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1247-
req := new(dashapi.UpdateReportReq)
1248-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1249-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1250-
}
1206+
func apiUpdateReport(c context.Context, ns string, req *dashapi.UpdateReportReq) (interface{}, error) {
12511207
bug := new(Bug)
12521208
bugKey := db.NewKey(c, "Bug", req.BugID, 0, nil)
12531209
if err := db.Get(c, bugKey, bug); err != nil {
@@ -1273,7 +1229,7 @@ func apiUpdateReport(c context.Context, ns string, payload io.Reader) (interface
12731229
return nil, runInTransaction(c, tx, nil)
12741230
}
12751231

1276-
func apiBugList(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1232+
func apiBugList(c context.Context, ns string, req *any) (interface{}, error) {
12771233
keys, err := db.NewQuery("Bug").
12781234
Filter("Namespace=", ns).
12791235
KeysOnly().
@@ -1288,11 +1244,7 @@ func apiBugList(c context.Context, ns string, payload io.Reader) (interface{}, e
12881244
return resp, nil
12891245
}
12901246

1291-
func apiLoadBug(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1292-
req := new(dashapi.LoadBugReq)
1293-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1294-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1295-
}
1247+
func apiLoadBug(c context.Context, ns string, req *dashapi.LoadBugReq) (interface{}, error) {
12961248
bug := new(Bug)
12971249
bugKey := db.NewKey(c, "Bug", req.ID, 0, nil)
12981250
if err := db.Get(c, bugKey, bug); err != nil {
@@ -1304,11 +1256,7 @@ func apiLoadBug(c context.Context, ns string, payload io.Reader) (interface{}, e
13041256
return loadBugReport(c, bug)
13051257
}
13061258

1307-
func apiLoadFullBug(c context.Context, payload io.Reader) (interface{}, error) {
1308-
req := new(dashapi.LoadFullBugReq)
1309-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1310-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1311-
}
1259+
func apiLoadFullBug(c context.Context, req *dashapi.LoadFullBugReq) (interface{}, error) {
13121260
bug, bugKey, err := findBugByReportingID(c, req.BugID)
13131261
if err != nil {
13141262
return nil, fmt.Errorf("failed to find the bug: %w", err)
@@ -1334,11 +1282,7 @@ func loadBugReport(c context.Context, bug *Bug) (*dashapi.BugReport, error) {
13341282
return createBugReport(c, bug, crash, crashKey, bugReporting, reporting)
13351283
}
13361284

1337-
func apiAddBuildAssets(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1338-
req := new(dashapi.AddBuildAssetsReq)
1339-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1340-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1341-
}
1285+
func apiAddBuildAssets(c context.Context, ns string, req *dashapi.AddBuildAssetsReq) (interface{}, error) {
13421286
assets := []Asset{}
13431287
for i, toAdd := range req.Assets {
13441288
asset, err := parseIncomingAsset(c, toAdd, ns)
@@ -1379,7 +1323,7 @@ func parseIncomingAsset(c context.Context, newAsset dashapi.NewAsset, ns string)
13791323
}, nil
13801324
}
13811325

1382-
func apiNeededAssetsList(c context.Context, payload io.Reader) (interface{}, error) {
1326+
func apiNeededAssetsList(c context.Context, req *any) (interface{}, error) {
13831327
return queryNeededAssets(c)
13841328
}
13851329

@@ -1760,11 +1704,7 @@ func handleRefreshSubsystems(w http.ResponseWriter, r *http.Request) {
17601704
}
17611705
}
17621706

1763-
func apiSaveDiscussion(c context.Context, payload io.Reader) (interface{}, error) {
1764-
req := new(dashapi.SaveDiscussionReq)
1765-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1766-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1767-
}
1707+
func apiSaveDiscussion(c context.Context, req *dashapi.SaveDiscussionReq) (interface{}, error) {
17681708
d := req.Discussion
17691709
newBugIDs := []string{}
17701710
for _, id := range d.BugIDs {
@@ -1803,11 +1743,7 @@ func recordEmergencyStop(c context.Context) error {
18031743
// Share crash logs for non-reproduced bugs with syz-managers.
18041744
// In future, this can also take care of repro exchange between instances
18051745
// in the place of syz-hub.
1806-
func apiLogToReproduce(c context.Context, ns string, payload io.Reader) (interface{}, error) {
1807-
req := new(dashapi.LogToReproReq)
1808-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1809-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1810-
}
1746+
func apiLogToReproduce(c context.Context, ns string, req *dashapi.LogToReproReq) (interface{}, error) {
18111747
build, err := loadBuild(c, ns, req.BuildID)
18121748
if err != nil {
18131749
return nil, err
@@ -1927,19 +1863,15 @@ func takeReproTask(c context.Context, ns, manager string) ([]byte, error) {
19271863
return log, err
19281864
}
19291865

1930-
func apiCreateUploadURL(c context.Context, payload io.Reader) (interface{}, error) {
1866+
func apiCreateUploadURL(c context.Context, req *any) (interface{}, error) {
19311867
bucket := getConfig(c).UploadBucket
19321868
if bucket == "" {
19331869
return nil, errors.New("not configured")
19341870
}
19351871
return fmt.Sprintf("%s/%s.upload", bucket, uuid.New().String()), nil
19361872
}
19371873

1938-
func apiSendEmail(c context.Context, payload io.Reader) (interface{}, error) {
1939-
req := new(dashapi.SendEmailReq)
1940-
if err := json.NewDecoder(payload).Decode(req); err != nil {
1941-
return nil, fmt.Errorf("failed to unmarshal request: %w", err)
1942-
}
1874+
func apiSendEmail(c context.Context, req *dashapi.SendEmailReq) (interface{}, error) {
19431875
var headers mail.Header
19441876
if req.InReplyTo != "" {
19451877
headers = mail.Header{"In-Reply-To": []string{req.InReplyTo}}

0 commit comments

Comments
 (0)