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  }