diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2025-12-19 12:20:56 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2025-12-22 02:13:00 +0000 |
| commit | 8fb7048c5117ccb592deb5e8e4a62027e6d399cf (patch) | |
| tree | 08c26b5b08d7fc70f7d2d067dcc3fec7d17b537b /dashboard/app | |
| parent | e14dbeb99dc80832252033b9ed81b85b13bef5c4 (diff) | |
dashboard/app: add typed handler middleware
Remove duplicated code related to request deserialization using middleware.
Diffstat (limited to 'dashboard/app')
| -rw-r--r-- | dashboard/app/api.go | 174 | ||||
| -rw-r--r-- | dashboard/app/reporting_external.go | 35 |
2 files changed, 59 insertions, 150 deletions
diff --git a/dashboard/app/api.go b/dashboard/app/api.go index 49c524c14..1e7d655c4 100644 --- a/dashboard/app/api.go +++ b/dashboard/app/api.go @@ -46,20 +46,20 @@ func initAPIHandlers() { } var apiHandlers = map[string]APIHandler{ - "log_error": apiLogError, - "job_poll": apiJobPoll, - "job_reset": apiJobReset, - "job_done": apiJobDone, - "reporting_poll_bugs": apiReportingPollBugs, - "reporting_poll_notifs": apiReportingPollNotifications, - "reporting_poll_closed": apiReportingPollClosed, - "reporting_update": apiReportingUpdate, - "new_test_job": apiNewTestJob, - "needed_assets": apiNeededAssetsList, - "load_full_bug": apiLoadFullBug, - "save_discussion": apiSaveDiscussion, - "create_upload_url": apiCreateUploadURL, - "send_email": apiSendEmail, + "log_error": typedHandler(apiLogError), + "job_poll": typedHandler(apiJobPoll), + "job_reset": typedHandler(apiJobReset), + "job_done": typedHandler(apiJobDone), + "reporting_poll_bugs": typedHandler(apiReportingPollBugs), + "reporting_poll_notifs": typedHandler(apiReportingPollNotifications), + "reporting_poll_closed": typedHandler(apiReportingPollClosed), + "reporting_update": typedHandler(apiReportingUpdate), + "new_test_job": typedHandler(apiNewTestJob), + "needed_assets": typedHandler(apiNeededAssetsList), + "load_full_bug": typedHandler(apiLoadFullBug), + "save_discussion": typedHandler(apiSaveDiscussion), + "create_upload_url": typedHandler(apiCreateUploadURL), + "send_email": typedHandler(apiSendEmail), "save_coverage": gcsPayloadHandler(apiSaveCoverage), "upload_build": nsHandler(apiUploadBuild), "builder_poll": nsHandler(apiBuilderPoll), @@ -79,7 +79,6 @@ var apiHandlers = map[string]APIHandler{ type JSONHandler func(c context.Context, r *http.Request) (interface{}, error) type APIHandler func(c context.Context, payload io.Reader) (interface{}, error) -type APINamespaceHandler func(c context.Context, ns string, payload io.Reader) (interface{}, error) const ( maxReproPerBug = 10 @@ -206,30 +205,34 @@ func gcsPayloadHandler(handler APIHandler) APIHandler { } } -func nsHandler(handler APINamespaceHandler) APIHandler { - return func(c context.Context, payload io.Reader) (interface{}, error) { - ns := contextNamespace(c) +func nsHandler[Req any](handler func(context.Context, string, *Req) (any, error)) APIHandler { + return typedHandler(func(ctx context.Context, req *Req) (any, error) { + ns := contextNamespace(ctx) if ns == "" { return nil, fmt.Errorf("must be called within a namespace") } - return handler(c, ns, payload) - } + return handler(ctx, ns, req) + }) } -func apiLogError(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.LogEntry) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) +func typedHandler[Req any](handler func(context.Context, *Req) (any, error)) APIHandler { + return func(ctx context.Context, payload io.Reader) (interface{}, error) { + req := new(Req) + if payload != nil { + if err := json.NewDecoder(payload).Decode(req); err != nil { + return nil, fmt.Errorf("failed to unmarshal request %T: %w", req, err) + } + } + return handler(ctx, req) } +} + +func apiLogError(c context.Context, req *dashapi.LogEntry) (interface{}, error) { log.Errorf(c, "%v: %v", req.Name, req.Text) return nil, nil } -func apiBuilderPoll(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.BuilderPollReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiBuilderPoll(c context.Context, ns string, req *dashapi.BuilderPollReq) (interface{}, error) { bugs, _, err := loadAllBugs(c, func(query *db.Query) *db.Query { return query.Filter("Namespace=", ns). Filter("Status<", BugStatusFixed) @@ -274,7 +277,7 @@ func reportEmail(c context.Context, ns string) string { return "" } -func apiCommitPoll(c context.Context, ns string, payload io.Reader) (interface{}, error) { +func apiCommitPoll(c context.Context, ns string, req *any) (interface{}, error) { resp := &dashapi.CommitPollResp{ ReportEmail: reportEmail(c, ns), } @@ -340,11 +343,7 @@ func pollBackportCommits(c context.Context, ns string, count int) ([]string, err return backportTitles, nil } -func apiUploadCommits(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.CommitPollResultReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiUploadCommits(c context.Context, ns string, req *dashapi.CommitPollResultReq) (interface{}, error) { // This adds fixing commits to bugs. err := addCommitsToBugs(c, ns, "", nil, req.Commits) if err != nil { @@ -445,46 +444,28 @@ func addCommitInfoToBugImpl(c context.Context, bug *Bug, com dashapi.Commit) (bo return changed, nil } -func apiJobPoll(c context.Context, payload io.Reader) (interface{}, error) { +func apiJobPoll(c context.Context, req *dashapi.JobPollReq) (interface{}, error) { if stop, err := emergentlyStopped(c); err != nil || stop { // The bot's operation was aborted. Don't accept new crash reports. return &dashapi.JobPollResp{}, err } - req := new(dashapi.JobPollReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } if len(req.Managers) == 0 { return nil, fmt.Errorf("no managers") } return pollPendingJobs(c, req.Managers) } -// nolint: dupl -func apiJobDone(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.JobDoneReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiJobDone(c context.Context, req *dashapi.JobDoneReq) (interface{}, error) { err := doneJob(c, req) return nil, err } -// nolint: dupl -func apiJobReset(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.JobResetReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiJobReset(c context.Context, req *dashapi.JobResetReq) (interface{}, error) { err := resetJobs(c, req) return nil, err } -func apiUploadBuild(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.Build) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiUploadBuild(c context.Context, ns string, req *dashapi.Build) (interface{}, error) { now := timeNow(c) _, isNewBuild, err := uploadBuild(c, now, ns, req, BuildNormal) if err != nil { @@ -751,11 +732,7 @@ func managerList(c context.Context, ns string) ([]string, error) { return managers, nil } -func apiReportBuildError(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.BuildErrorReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiReportBuildError(c context.Context, ns string, req *dashapi.BuildErrorReq) (interface{}, error) { now := timeNow(c) build, _, err := uploadBuild(c, now, ns, &req.Build, BuildFailed) if err != nil { @@ -785,15 +762,11 @@ const ( suppressedReportTitle = "suppressed report" ) -func apiReportCrash(c context.Context, ns string, payload io.Reader) (interface{}, error) { +func apiReportCrash(c context.Context, ns string, req *dashapi.Crash) (interface{}, error) { if stop, err := emergentlyStopped(c); err != nil || stop { // The bot's operation was aborted. Don't accept new crash reports. return &dashapi.ReportCrashResp{}, err } - req := new(dashapi.Crash) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } build, err := loadBuild(c, ns, req.BuildID) if err != nil { return nil, err @@ -1094,13 +1067,8 @@ func purgeOldCrashes(c context.Context, bug *Bug, bugKey *db.Key) { log.Infof(c, "deleted %v crashes for bug %q", deleted, bug.Title) } -func apiReportFailedRepro(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.CrashID) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiReportFailedRepro(c context.Context, ns string, req *dashapi.CrashID) (interface{}, error) { req.Title = canonicalizeCrashTitle(req.Title, req.Corrupted, req.Suppressed) - bug, err := findExistingBugForCrash(c, ns, []string{req.Title}) if err != nil { return nil, err @@ -1166,11 +1134,7 @@ func saveReproAttempt(c context.Context, bug *Bug, build *Build, log []byte) err return nil } -func apiNeedRepro(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.CrashID) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiNeedRepro(c context.Context, ns string, req *dashapi.CrashID) (interface{}, error) { if req.Corrupted { resp := &dashapi.NeedReproResp{ NeedRepro: false, @@ -1218,11 +1182,7 @@ func normalizeCrashTitle(title string) string { return strings.TrimSpace(limitLength(title, maxTextLen)) } -func apiManagerStats(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.ManagerStatsReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiManagerStats(c context.Context, ns string, req *dashapi.ManagerStatsReq) (interface{}, error) { now := timeNow(c) err := updateManager(c, ns, req.Name, func(mgr *Manager, stats *ManagerStats) error { mgr.Link = req.Addr @@ -1243,11 +1203,7 @@ func apiManagerStats(c context.Context, ns string, payload io.Reader) (interface return nil, err } -func apiUpdateReport(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.UpdateReportReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiUpdateReport(c context.Context, ns string, req *dashapi.UpdateReportReq) (interface{}, error) { bug := new(Bug) bugKey := db.NewKey(c, "Bug", req.BugID, 0, nil) if err := db.Get(c, bugKey, bug); err != nil { @@ -1273,7 +1229,7 @@ func apiUpdateReport(c context.Context, ns string, payload io.Reader) (interface return nil, runInTransaction(c, tx, nil) } -func apiBugList(c context.Context, ns string, payload io.Reader) (interface{}, error) { +func apiBugList(c context.Context, ns string, req *any) (interface{}, error) { keys, err := db.NewQuery("Bug"). Filter("Namespace=", ns). KeysOnly(). @@ -1288,11 +1244,7 @@ func apiBugList(c context.Context, ns string, payload io.Reader) (interface{}, e return resp, nil } -func apiLoadBug(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.LoadBugReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiLoadBug(c context.Context, ns string, req *dashapi.LoadBugReq) (interface{}, error) { bug := new(Bug) bugKey := db.NewKey(c, "Bug", req.ID, 0, nil) 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 return loadBugReport(c, bug) } -func apiLoadFullBug(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.LoadFullBugReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiLoadFullBug(c context.Context, req *dashapi.LoadFullBugReq) (interface{}, error) { bug, bugKey, err := findBugByReportingID(c, req.BugID) if err != nil { 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) { return createBugReport(c, bug, crash, crashKey, bugReporting, reporting) } -func apiAddBuildAssets(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.AddBuildAssetsReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiAddBuildAssets(c context.Context, ns string, req *dashapi.AddBuildAssetsReq) (interface{}, error) { assets := []Asset{} for i, toAdd := range req.Assets { asset, err := parseIncomingAsset(c, toAdd, ns) @@ -1379,7 +1323,7 @@ func parseIncomingAsset(c context.Context, newAsset dashapi.NewAsset, ns string) }, nil } -func apiNeededAssetsList(c context.Context, payload io.Reader) (interface{}, error) { +func apiNeededAssetsList(c context.Context, req *any) (interface{}, error) { return queryNeededAssets(c) } @@ -1760,11 +1704,7 @@ func handleRefreshSubsystems(w http.ResponseWriter, r *http.Request) { } } -func apiSaveDiscussion(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.SaveDiscussionReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiSaveDiscussion(c context.Context, req *dashapi.SaveDiscussionReq) (interface{}, error) { d := req.Discussion newBugIDs := []string{} for _, id := range d.BugIDs { @@ -1803,11 +1743,7 @@ func recordEmergencyStop(c context.Context) error { // Share crash logs for non-reproduced bugs with syz-managers. // In future, this can also take care of repro exchange between instances // in the place of syz-hub. -func apiLogToReproduce(c context.Context, ns string, payload io.Reader) (interface{}, error) { - req := new(dashapi.LogToReproReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiLogToReproduce(c context.Context, ns string, req *dashapi.LogToReproReq) (interface{}, error) { build, err := loadBuild(c, ns, req.BuildID) if err != nil { return nil, err @@ -1927,7 +1863,7 @@ func takeReproTask(c context.Context, ns, manager string) ([]byte, error) { return log, err } -func apiCreateUploadURL(c context.Context, payload io.Reader) (interface{}, error) { +func apiCreateUploadURL(c context.Context, req *any) (interface{}, error) { bucket := getConfig(c).UploadBucket if bucket == "" { return nil, errors.New("not configured") @@ -1935,11 +1871,7 @@ func apiCreateUploadURL(c context.Context, payload io.Reader) (interface{}, erro return fmt.Sprintf("%s/%s.upload", bucket, uuid.New().String()), nil } -func apiSendEmail(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.SendEmailReq) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiSendEmail(c context.Context, req *dashapi.SendEmailReq) (interface{}, error) { var headers mail.Header if req.InReplyTo != "" { headers = mail.Header{"In-Reply-To": []string{req.InReplyTo}} diff --git a/dashboard/app/reporting_external.go b/dashboard/app/reporting_external.go index d603a7107..8dd4eb444 100644 --- a/dashboard/app/reporting_external.go +++ b/dashboard/app/reporting_external.go @@ -5,10 +5,7 @@ package main import ( "context" - "encoding/json" "errors" - "fmt" - "io" "github.com/google/syzkaller/dashboard/dashapi" "google.golang.org/appengine/v2/log" @@ -18,14 +15,10 @@ import ( // The external system is meant to poll for new bugs with apiReportingPoll, // and report back bug status updates with apiReportingUpdate. -func apiReportingPollBugs(c context.Context, payload io.Reader) (interface{}, error) { +func apiReportingPollBugs(c context.Context, req *dashapi.PollBugsRequest) (interface{}, error) { if stop, err := emergentlyStopped(c); err != nil || stop { return &dashapi.PollBugsResponse{}, err } - req := new(dashapi.PollBugsRequest) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } reports := reportingPollBugs(c, req.Type) resp := &dashapi.PollBugsResponse{ Reports: reports, @@ -38,15 +31,11 @@ func apiReportingPollBugs(c context.Context, payload io.Reader) (interface{}, er return resp, nil } -func apiReportingPollNotifications(c context.Context, payload io.Reader, -) (interface{}, error) { +func apiReportingPollNotifications(c context.Context, req *dashapi.PollNotificationsRequest) ( + interface{}, error) { if stop, err := emergentlyStopped(c); err != nil || stop { return &dashapi.PollNotificationsResponse{}, err } - req := new(dashapi.PollNotificationsRequest) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } notifs := reportingPollNotifications(c, req.Type) resp := &dashapi.PollNotificationsResponse{ Notifications: notifs, @@ -54,14 +43,10 @@ func apiReportingPollNotifications(c context.Context, payload io.Reader, return resp, nil } -func apiReportingPollClosed(c context.Context, payload io.Reader) (interface{}, error) { +func apiReportingPollClosed(c context.Context, req *dashapi.PollClosedRequest) (interface{}, error) { if stop, err := emergentlyStopped(c); err != nil || stop { return &dashapi.PollClosedResponse{}, err } - req := new(dashapi.PollClosedRequest) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } ids, err := reportingPollClosed(c, req.IDs) if err != nil { return nil, err @@ -72,11 +57,7 @@ func apiReportingPollClosed(c context.Context, payload io.Reader) (interface{}, return resp, nil } -func apiReportingUpdate(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.BugUpdate) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiReportingUpdate(c context.Context, req *dashapi.BugUpdate) (interface{}, error) { if req.JobID != "" { resp := &dashapi.BugUpdateReply{ OK: true, @@ -97,11 +78,7 @@ func apiReportingUpdate(c context.Context, payload io.Reader) (interface{}, erro }, nil } -func apiNewTestJob(c context.Context, payload io.Reader) (interface{}, error) { - req := new(dashapi.TestPatchRequest) - if err := json.NewDecoder(payload).Decode(req); err != nil { - return nil, fmt.Errorf("failed to unmarshal request: %w", err) - } +func apiNewTestJob(c context.Context, req *dashapi.TestPatchRequest) (interface{}, error) { resp := &dashapi.TestPatchReply{} err := handleExternalTestRequest(c, req) if err != nil { |
