broker: settings that parse but fail validation answer 400, not 500

handleOperationException mapped every JFJochException but WrongDAQState to 500, so a /start whose
module placement does not name exactly the detector's modules (one left out, named twice, or not the
detector's) came back as an internal error although the API documents 400 for invalid input. The input
categories (InputParameterInvalid/AboveMax/BelowMin, WrongNumber, WrongUnit) now answer 400 with the
message as text, as a parsing failure does; everything else stays 500. Test:
JFJochBrokerHttp_StartModulePlacementMismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqXYdJKPUFLoeAchjzv3x4
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-11-03 10:42:01 +01:00
1 parent 96633cea6b
commit 7a9c73999e
2 files changed
+74

No files matched your search

+14
View File
@@ -136,6 +136,20 @@ JFJochBrokerHttp::HttpError JFJochBrokerHttp::handleOperationException(const std
throw;
} catch (const WrongDAQStateException &) {
return {500, error_message_json(ex.what(), "WrongDAQState"), "application/json"};
} catch (const JFJochException &e) {
// Settings that parse but do not fit what they are checked against (a module placement naming
// modules the detector does not have, a value out of range) are the request's fault, as a parsing
// failure is: 400, as the API documents it.
switch (e.Category()) {
case JFJochExceptionCategory::InputParameterInvalid:
case JFJochExceptionCategory::InputParameterAboveMax:
case JFJochExceptionCategory::InputParameterBelowMin:
case JFJochExceptionCategory::WrongNumber:
case JFJochExceptionCategory::WrongUnit:
return {400, e.what(), "text/plain"};
default:
return {500, error_message_json(ex.what(), "Other"), "application/json"};
}
} catch (const std::exception &) {
return {500, error_message_json(ex.what(), "Other"), "application/json"};
}
+60
View File
@@ -8,6 +8,7 @@
#include <thread>
#include "../broker/JFJochBrokerHttp.h"
#include "../common/DetectorModules.h"
// The tokens given to /start guard the endpoints that expose the dataset, and nothing else, for as
// long as that dataset is the current one. Runs the real HTTP layer against a broker with no
@@ -159,3 +160,62 @@ TEST_CASE("JFJochBrokerHttp_BearerTokens", "[broker]") {
}
}
// A module placement that does not name exactly the detector's modules is the request's fault: /start
// answers 400, whether a module is left out, placed twice or not the detector's at all.
TEST_CASE("JFJochBrokerHttp_StartModulePlacementMismatch", "[broker]") {
DiffractionExperiment experiment;
SpotFindingSettings spot_finding;
JFJochBrokerHttp broker(experiment, spot_finding);
broker.AddDetectorSetup(DetJF4M());
httplib::Server server;
broker.attach(server);
const int port = server.bind_to_any_port("127.0.0.1");
REQUIRE(port > 0);
struct ServerThread {
httplib::Server &server;
std::thread thread;
explicit ServerThread(httplib::Server &s) : server(s), thread([&s] { s.listen_after_bind(); }) {}
~ServerThread() { server.stop(); thread.join(); }
} server_thread(server);
server.wait_until_ready();
httplib::Client client("127.0.0.1", port);
REQUIRE(client.Post("/initialize")->status == 200);
const auto assembled = ModulesAsImaged(DetJF4M().GetGeometry()).Get();
REQUIRE(assembled.size() == 8);
const auto entry = [](const DetectorModule &m) {
return nlohmann::json{{"box_x", m.x0}, {"box_y", m.y0}, {"box_width", m.width}, {"box_height", m.height},
{"plane_x", m.ox}, {"plane_y", m.oy}, {"x_step_x", 1.0}, {"x_step_y", 0.0},
{"y_step_x", 0.0}, {"y_step_y", 1.0}};
};
const auto start = [&](const nlohmann::json &placement) {
nlohmann::json body = {{"beam_x_pxl", 1000}, {"beam_y_pxl", 1000},
{"detector_distance_mm", 100}, {"incident_energy_keV", 12.4},
{"images_per_trigger", 1}, {"ntrigger", 1},
{"file_prefix", "placement_run"}, {"module_placement", placement}};
auto res = client.Post("/start", body.dump(), "application/json");
REQUIRE(res);
return res->status;
};
nlohmann::json missing = nlohmann::json::array();
for (size_t m = 1; m < assembled.size(); m++)
missing.push_back(entry(assembled[m]));
CHECK(start(missing) == 400);
nlohmann::json twice = missing;
twice.push_back(entry(assembled[1]));
CHECK(start(twice) == 400);
DetectorModule elsewhere = assembled[0];
elsewhere.y0 += 1;
nlohmann::json unknown = missing;
unknown.push_back(entry(elsewhere));
CHECK(start(unknown) == 400);
// Nothing was started.
CHECK(nlohmann::json::parse(client.Get("/status")->body).at("state") == "Idle");
}