diff --git a/bin/evening_detective_server b/bin/evening_detective_server index 0cf2b46..9ea0e33 100755 Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ diff --git a/internal/app/team_registration.go b/internal/app/team_registration.go index f3f11bb..e12dc9f 100644 --- a/internal/app/team_registration.go +++ b/internal/app/team_registration.go @@ -1,6 +1,7 @@ package app import ( + "bytes" "context" "crypto/subtle" "encoding/json" @@ -39,9 +40,8 @@ func TeamRegistrationHandler(token string, register func(context.Context, int, s return } decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16<<10)) - decoder.DisallowUnknownFields() - var registration game_service.Registration - if err := decoder.Decode(®istration); err != nil { + var payload json.RawMessage + if err := decoder.Decode(&payload); err != nil { fail(http.StatusBadRequest, fmt.Sprintf("Некорректный JSON регистрации: %v", err)) return } @@ -54,6 +54,11 @@ func TeamRegistrationHandler(token string, register func(context.Context, int, s } return } + registration, rpcID, isRPC, err := decodeRegistrationPayload(payload) + if err != nil { + fail(http.StatusBadRequest, fmt.Sprintf("Некорректный JSON регистрации: %v", err)) + return + } ctx, cancel := context.WithTimeout(r.Context(), 4*time.Second) defer cancel() id, err := register(ctx, int(gameID), deliveryID, registration) @@ -65,7 +70,44 @@ func TeamRegistrationHandler(token string, register func(context.Context, int, s case err != nil: fail(http.StatusInternalServerError, "Не удалось зарегистрировать команду") default: - _ = json.NewEncoder(w).Encode(map[string]int{"id": id}) + if isRPC { + _ = json.NewEncoder(w).Encode(map[string]any{"jsonrpc": "2.0", "result": map[string]int{"id": id}, "id": rpcID}) + } else { + _ = json.NewEncoder(w).Encode(map[string]int{"id": id}) + } } }) } + +func decodeRegistrationPayload(payload json.RawMessage) (game_service.Registration, json.RawMessage, bool, error) { + var fields map[string]json.RawMessage + if err := json.Unmarshal(payload, &fields); err != nil { + return game_service.Registration{}, nil, false, err + } + if rawRPC, ok := fields["jsonrpc"]; ok { + var version string + if err := json.Unmarshal(rawRPC, &version); err != nil || version != "2.0" { + return game_service.Registration{}, nil, true, errors.New(`поле "jsonrpc" должно быть равно "2.0"`) + } + params, ok := fields["params"] + if !ok { + return game_service.Registration{}, nil, true, errors.New(`в JSON-RPC запросе отсутствует поле "params"`) + } + var registration game_service.Registration + if err := decodeStrict(params, ®istration); err != nil { + return game_service.Registration{}, nil, true, fmt.Errorf("поле params: %w", err) + } + return registration, fields["id"], true, nil + } + var registration game_service.Registration + if err := decodeStrict(payload, ®istration); err != nil { + return game_service.Registration{}, nil, false, err + } + return registration, nil, false, nil +} + +func decodeStrict(data []byte, target any) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + return decoder.Decode(target) +} diff --git a/internal/app/team_registration_test.go b/internal/app/team_registration_test.go index 7d8ba4a..12981b4 100644 --- a/internal/app/team_registration_test.go +++ b/internal/app/team_registration_test.go @@ -29,6 +29,7 @@ func TestTeamRegistrationHandler(t *testing.T) { {"malformed", "secret", "Bearer secret", "42", "delivery", `{`, nil, 400, false}, {"extra object", "secret", "Bearer secret", "42", "delivery", valid + ` {}`, nil, 400, false}, {"unknown field", "secret", "Bearer secret", "42", "delivery", `{"extra":1}`, nil, 400, false}, + {"jsonrpc", "secret", "Bearer secret", "42", "delivery", `{"jsonrpc":"2.0","method":"register","params":{"name":"Команда","email":"captain@example.org","participantsCount":5},"id":1}`, nil, 200, true}, {"too large", "secret", "Bearer secret", "42", "delivery", `{"name":"` + strings.Repeat("a", 17000) + `"}`, nil, 400, false}, {"invalid fields", "secret", "Bearer secret", "42", "delivery", valid, game_service.ErrInvalidRegistration, 400, true}, {"closed", "secret", "Bearer secret", "42", "delivery", valid, teams_repo.ErrRegistrationClosed, 409, true}, @@ -55,7 +56,7 @@ func TestTeamRegistrationHandler(t *testing.T) { if res.Code != tc.want || called != tc.called { t.Fatalf("status=%d called=%v body=%s", res.Code, called, res.Body.String()) } - if tc.want == 200 && res.Body.String() != "{\"id\":7}\n" { + if tc.name == "success" && res.Body.String() != "{\"id\":7}\n" { t.Fatal(res.Body.String()) } if strings.Contains(res.Body.String(), "private database detail") { @@ -70,6 +71,9 @@ func TestTeamRegistrationHandler(t *testing.T) { if tc.name == "extra object" && !strings.Contains(res.Body.String(), "лишние данные") { t.Fatalf("extra data reason missing: %s", res.Body.String()) } + if tc.name == "jsonrpc" && !strings.Contains(res.Body.String(), `"jsonrpc":"2.0"`) { + t.Fatalf("JSON-RPC response missing: %s", res.Body.String()) + } }) } }