github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/reporter/api.go (about) 1 // Copyright 2025 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package reporter 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "net/http/httptest" 11 "testing" 12 13 "github.com/google/syzkaller/syz-cluster/pkg/api" 14 "github.com/google/syzkaller/syz-cluster/pkg/app" 15 "github.com/google/syzkaller/syz-cluster/pkg/service" 16 ) 17 18 type APIServer struct { 19 reportService *service.ReportService 20 discussionService *service.DiscussionService 21 } 22 23 func NewAPIServer(env *app.AppEnvironment) *APIServer { 24 return &APIServer{ 25 reportService: service.NewReportService(env), 26 discussionService: service.NewDiscussionService(env), 27 } 28 } 29 30 func (s *APIServer) Mux() *http.ServeMux { 31 mux := http.NewServeMux() 32 mux.HandleFunc("/reports/{report_id}/upstream", s.upstreamReport) 33 mux.HandleFunc("/reports/{report_id}/confirm", s.confirmReport) 34 mux.HandleFunc("/reports/{report_id}/invalidate", s.invalidateReport) 35 mux.HandleFunc("/reports/record_reply", s.recordReply) 36 mux.HandleFunc("/reports/last_reply", s.lastReply) 37 mux.HandleFunc("/reports", s.nextReports) 38 return mux 39 } 40 41 // nolint: dupl 42 func (s *APIServer) upstreamReport(w http.ResponseWriter, r *http.Request) { 43 req := api.ParseJSON[api.UpstreamReportReq](w, r) 44 if req == nil { 45 return 46 } 47 // TODO: journal the action. 48 err := s.reportService.Upstream(r.Context(), r.PathValue("report_id"), req) 49 reply[interface{}](w, nil, err) 50 } 51 52 func (s *APIServer) invalidateReport(w http.ResponseWriter, r *http.Request) { 53 // TODO: journal the action. 54 err := s.reportService.Invalidate(r.Context(), r.PathValue("report_id")) 55 reply[interface{}](w, nil, err) 56 } 57 58 func (s *APIServer) nextReports(w http.ResponseWriter, r *http.Request) { 59 resp, err := s.reportService.Next(r.Context(), r.FormValue("reporter")) 60 reply(w, resp, err) 61 } 62 63 func (s *APIServer) confirmReport(w http.ResponseWriter, r *http.Request) { 64 err := s.reportService.Confirm(r.Context(), r.PathValue("report_id")) 65 reply[interface{}](w, nil, err) 66 } 67 68 func (s *APIServer) recordReply(w http.ResponseWriter, r *http.Request) { 69 req := api.ParseJSON[api.RecordReplyReq](w, r) 70 if req == nil { 71 return 72 } 73 resp, err := s.discussionService.RecordReply(r.Context(), req) 74 reply(w, resp, err) 75 } 76 77 func (s *APIServer) lastReply(w http.ResponseWriter, r *http.Request) { 78 resp, err := s.discussionService.LastReply(r.Context(), r.PathValue("reporter")) 79 reply(w, resp, err) 80 } 81 82 func reply[T any](w http.ResponseWriter, obj T, err error) { 83 if errors.Is(err, service.ErrReportNotFound) { 84 http.Error(w, fmt.Sprint(err), http.StatusNotFound) 85 return 86 } else if errors.Is(err, service.ErrNotOnModeration) { 87 http.Error(w, fmt.Sprint(err), http.StatusBadRequest) 88 return 89 } else if err != nil { 90 http.Error(w, fmt.Sprint(err), http.StatusInternalServerError) 91 return 92 } 93 api.ReplyJSON[T](w, obj) 94 } 95 96 func TestServer(t *testing.T, env *app.AppEnvironment) *api.ReporterClient { 97 apiServer := NewAPIServer(env) 98 server := httptest.NewServer(apiServer.Mux()) 99 t.Cleanup(server.Close) 100 return api.NewReporterClient(server.URL) 101 }