Skip to content

Commit 84252cb

Browse files
committed
feat(gateway): migrate all handlers to typed router
All 14 handler files migrate from raw void(httplib::Request, Response) signatures to typed Result<TResponse>(TypedRequest [, TBody]): - health, discovery (18 routes), lock (5 + 201/Location), trigger (6 incl. SSE), cyclic_subscription (6 incl. SSE), auth (3 + OAuth2 error renderer), update (8 incl. 202/Location), log (3 typed fan-out), script (8 incl. multipart upload, HATEOAS _links typed), bulkdata (11 incl. binary_download + multipart_upload), config (5 incl. del_alternates<NoContent, ConfigurationDeleteMultiStatus> for 204/207), operation (7 incl. post_alternates for sync/async, plugin passthrough for free-form body), data (5 incl. opaque DataValue), fault (6 + SSE incl. del_alternates and X-Medkit-Local-Only attachment). rest_server.cpp setup_routes is now exclusively typed reg.get<T>/ post<TB,T>/put<TB,T>/del<T>/patch<TB,T>/sse<TEvent>/binary_download/ multipart_upload<T>/static_asset/docs_endpoint. The deprecated raw overloads on RouteRegistry are removed. Issue #338 final compliance: per-item wire shape for fault list and configuration list endpoints is now enforced by JsonReader<T> (closes the "schema declares Items[T] but wire is raw json" gap). Forwarded path through aggregation: validators that proxy to a peer return Forwarded; the framework wrapper detects an internal sentinel ErrorInfo and skips the response write so the proxied response stays intact. Wire format byte-identical with the legacy direct-write forwarded path. operation_handlers plugin branch passes the raw request body to OperationProvider::execute_operation rather than the typed ExecutionCreateRequest envelope, preserving the pre-migration ABI for plugin operations that read vendor-specific top-level keys (UDS session_type / reset_type, OPC-UA acknowledge_fault arguments). Tests: - test_*_handlers.cpp rewritten to assert against Result<T> return values rather than http response state. - Integration tests run unchanged - wire format byte-identical.
1 parent 2a52385 commit 84252cb

49 files changed

Lines changed: 8921 additions & 7731 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/auth_handlers.hpp

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 bburda
1+
// Copyright 2026 bburda
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -14,7 +14,9 @@
1414

1515
#pragma once
1616

17+
#include "ros2_medkit_gateway/dto/auth.hpp"
1718
#include "ros2_medkit_gateway/http/handlers/handler_context.hpp"
19+
#include "ros2_medkit_gateway/http/typed_router.hpp"
1820

1921
namespace ros2_medkit_gateway {
2022
namespace handlers {
@@ -24,10 +26,25 @@ namespace handlers {
2426
*
2527
* Implements OAuth2-like authentication flow:
2628
* - POST /auth/authorize - Authenticate with client credentials
27-
* - POST /auth/token - Refresh access token
28-
* - POST /auth/revoke - Revoke refresh token
29+
* - POST /auth/token - Refresh access token (RFC 6749 token endpoint)
30+
* - POST /auth/revoke - Revoke refresh token (RFC 7009)
2931
*
30-
* @note These endpoints are only available when authentication is enabled.
32+
* All three handlers follow the PR-403 typed RouteRegistry convention:
33+
*
34+
* http::Result<dto::TResponse> X(const http::TypedRequest & req);
35+
*
36+
* The body is read directly from `req.body` (via the framework escape hatch)
37+
* because the auth endpoints accept BOTH `application/json` AND
38+
* `application/x-www-form-urlencoded` (RFC 6749 §4.1.3). The framework's
39+
* typed-body parser only speaks JSON and would also emit SOVD's
40+
* `invalid-request` (dash) code instead of OAuth2's `invalid_request`
41+
* (underscore). The routes set `.error_renderer(kOAuth2Error)` so any returned
42+
* `ErrorInfo` is rendered as `{error, error_description}` per RFC 6749 §5.2.
43+
*
44+
* @note These endpoints are only registered/exposed when authentication is
45+
* enabled. The handlers still defensively return 404 when `auth.enabled` is
46+
* false, so a misconfiguration that leaves the routes wired without a manager
47+
* does not crash.
3148
*/
3249
class AuthHandlers {
3350
public:
@@ -38,20 +55,17 @@ class AuthHandlers {
3855
explicit AuthHandlers(HandlerContext & ctx) : ctx_(ctx) {
3956
}
4057

41-
/**
42-
* @brief Handle POST /auth/authorize - authenticate with client credentials.
43-
*/
44-
void handle_auth_authorize(const httplib::Request & req, httplib::Response & res);
58+
/// POST /auth/authorize - authenticate with client_credentials grant.
59+
/// Returns an OAuth2 TokenResponse on success or an OAuth2 error on failure.
60+
http::Result<dto::AuthTokenResponse> post_authorize(const http::TypedRequest & req);
4561

46-
/**
47-
* @brief Handle POST /auth/token - refresh access token.
48-
*/
49-
void handle_auth_token(const httplib::Request & req, httplib::Response & res);
62+
/// POST /auth/token - refresh access token via the refresh_token grant.
63+
http::Result<dto::AuthTokenResponse> post_token(const http::TypedRequest & req);
5064

51-
/**
52-
* @brief Handle POST /auth/revoke - revoke refresh token.
53-
*/
54-
void handle_auth_revoke(const httplib::Request & req, httplib::Response & res);
65+
/// POST /auth/revoke - revoke a refresh token (RFC 7009).
66+
/// Always returns 200 + `{"status":"revoked"}` regardless of whether the
67+
/// token existed, to prevent token-enumeration side channels.
68+
http::Result<dto::AuthRevokeResponse> post_revoke(const http::TypedRequest & req);
5569

5670
private:
5771
HandlerContext & ctx_;

src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/bulkdata_handlers.hpp

Lines changed: 25 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,31 @@
1414

1515
#pragma once
1616

17-
#include <httplib.h>
18-
1917
#include <string>
18+
#include <utility>
2019
#include <vector>
2120

21+
#include "ros2_medkit_gateway/dto/bulkdata.hpp"
2222
#include "ros2_medkit_gateway/http/handlers/handler_context.hpp"
23+
#include "ros2_medkit_gateway/http/response_types.hpp"
24+
#include "ros2_medkit_gateway/http/typed_router.hpp"
2325

2426
namespace ros2_medkit_gateway {
2527
namespace handlers {
2628

2729
/**
2830
* @brief HTTP handlers for SOVD bulk-data endpoints.
2931
*
30-
* Provides handlers for listing bulk-data categories, descriptors,
31-
* and downloading bulk-data files (rosbags) for any entity type.
32+
* PR-403 commit 25: 11 bulk-data routes migrated to the typed RouteRegistry
33+
* API. Every handler returns `http::Result<T>` (or a `pair<T,
34+
* ResponseAttachments>` for the upload route that needs to emit 201 +
35+
* Location). The download route uses `reg.binary_download` so it can emit
36+
* `Content-Disposition`, set `supports_ranges`, and supply a chunked content
37+
* provider without touching `httplib::Response`. The upload route uses
38+
* `reg.multipart_upload<BulkDataDescriptor>` so multipart parsing remains
39+
* inside the framework while the handler stays typed. Wire format is
40+
* unchanged byte-for-byte, including the rosbag MIME-type-by-format mapping
41+
* and the Content-Disposition filename sanitisation.
3242
*
3343
* Supports SOVD entity paths:
3444
* - /apps/{id}/bulk-data[/{category}[/{id}]]
@@ -45,65 +55,21 @@ class BulkDataHandlers {
4555
*/
4656
explicit BulkDataHandlers(HandlerContext & ctx);
4757

48-
/**
49-
* @brief GET {entity-path}/bulk-data - List bulk-data categories.
50-
*
51-
* Returns available bulk-data categories for an entity.
52-
* Currently only "rosbags" category is supported.
53-
*
54-
* @param req HTTP request
55-
* @param res HTTP response
56-
*/
57-
void handle_list_categories(const httplib::Request & req, httplib::Response & res);
58+
/// GET /{entity}/bulk-data - list bulk-data categories.
59+
http::Result<dto::BulkDataCategoryList> list_categories(const http::TypedRequest & req);
5860

59-
/**
60-
* @brief GET {entity-path}/bulk-data/{category} - List bulk-data descriptors.
61-
*
62-
* Returns BulkDataDescriptor array for the specified category.
63-
* For "rosbags" category, returns descriptors for all rosbags
64-
* associated with faults from this entity.
65-
*
66-
* @param req HTTP request
67-
* @param res HTTP response
68-
*/
69-
void handle_list_descriptors(const httplib::Request & req, httplib::Response & res);
61+
/// GET /{entity}/bulk-data/{category_id} - list bulk-data descriptors.
62+
http::Result<dto::Collection<dto::BulkDataDescriptor>> list_descriptors(const http::TypedRequest & req);
7063

71-
/**
72-
* @brief GET {entity-path}/bulk-data/{category}/{id} - Download bulk-data file.
73-
*
74-
* Downloads the bulk-data file (rosbag) identified by the ID.
75-
* Validates that the rosbag belongs to the specified entity.
76-
*
77-
* @param req HTTP request
78-
* @param res HTTP response
79-
*/
80-
void handle_download(const httplib::Request & req, httplib::Response & res);
64+
/// GET /{entity}/bulk-data/{category_id}/{file_id} - binary download.
65+
http::Result<http::BinaryResponse> download(const http::TypedRequest & req);
8166

82-
/**
83-
* @brief POST {entity-path}/bulk-data/{category} - Upload bulk-data file.
84-
*
85-
* Accepts multipart/form-data with:
86-
* - "file" (required): the file to upload
87-
* - "description" (optional): text description
88-
* - "metadata" (optional): JSON string with additional metadata
89-
*
90-
* Returns 201 with ItemDescriptor JSON on success.
91-
*
92-
* @param req HTTP request
93-
* @param res HTTP response
94-
*/
95-
void handle_upload(const httplib::Request & req, httplib::Response & res);
67+
/// POST /{entity}/bulk-data/{category_id} - multipart upload, 201 + Location.
68+
http::Result<std::pair<dto::BulkDataDescriptor, http::ResponseAttachments>> upload(const http::TypedRequest & req,
69+
const http::MultipartBody & body);
9670

97-
/**
98-
* @brief DELETE {entity-path}/bulk-data/{category}/{id} - Delete bulk-data file.
99-
*
100-
* Removes an uploaded bulk-data item. Returns 204 on success, 404 if not found.
101-
* Only items uploaded via POST can be deleted (rosbags managed by fault system cannot).
102-
*
103-
* @param req HTTP request
104-
* @param res HTTP response
105-
*/
106-
void handle_delete(const httplib::Request & req, httplib::Response & res);
71+
/// DELETE /{entity}/bulk-data/{category_id}/{file_id} - 204 No Content.
72+
http::Result<http::NoContent> remove(const http::TypedRequest & req);
10773

10874
/**
10975
* @brief Get MIME type for rosbag format.
@@ -125,16 +91,6 @@ class BulkDataHandlers {
12591
*/
12692
std::vector<std::string> get_source_filters(const EntityInfo & entity) const;
12793

128-
/**
129-
* @brief Stream file contents to HTTP response.
130-
* @param res HTTP response to write to
131-
* @param file_path Path to file to stream (can be file or rosbag directory)
132-
* @param content_type MIME type for Content-Type header
133-
* @return true if successful, false if file could not be read
134-
*/
135-
bool stream_file_to_response(httplib::Response & res, const std::string & file_path,
136-
const std::string & content_type);
137-
13894
/**
13995
* @brief Resolve rosbag file path from storage path.
14096
*

src/ros2_medkit_gateway/include/ros2_medkit_gateway/core/http/handlers/config_handlers.hpp

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 bburda
1+
// Copyright 2026 bburda
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -14,7 +14,12 @@
1414

1515
#pragma once
1616

17+
#include <variant>
18+
19+
#include "ros2_medkit_gateway/dto/config.hpp"
1720
#include "ros2_medkit_gateway/http/handlers/handler_context.hpp"
21+
#include "ros2_medkit_gateway/http/response_types.hpp"
22+
#include "ros2_medkit_gateway/http/typed_router.hpp"
1823

1924
namespace ros2_medkit_gateway {
2025
namespace handlers {
@@ -23,45 +28,51 @@ namespace handlers {
2328
* @brief Handlers for configuration (parameter) REST API endpoints.
2429
*
2530
* Provides handlers for:
26-
* - GET /components/{component_id}/configurations - List all parameters
27-
* - GET /components/{component_id}/configurations/{param_name} - Get parameter
28-
* - PUT /components/{component_id}/configurations/{param_name} - Set parameter
29-
* - DELETE /components/{component_id}/configurations/{param_name} - Reset parameter
30-
* - DELETE /components/{component_id}/configurations - Reset all parameters
31+
* - GET /{entity-path}/configurations - List parameters
32+
* - GET /{entity-path}/configurations/{param_name} - Read parameter value
33+
* - PUT /{entity-path}/configurations/{param_name} - Set parameter value
34+
* - DELETE /{entity-path}/configurations/{param_name} - Reset parameter to default
35+
* - DELETE /{entity-path}/configurations - Reset all parameters
36+
*
37+
* PR-403 commit 26: 5 config routes migrated to the typed RouteRegistry API.
38+
* The list endpoint uses the typed `fan_out_collection<ConfigurationMetaData>`
39+
* (commit 7) for peer aggregation: per-item wire shape is now enforced by
40+
* `JsonReader<ConfigurationMetaData>` on every contribution, closing the
41+
* issue #338 gap on this endpoint by lifting the per-item schema enforcement
42+
* from "spec only" to "compile-time + runtime contract".
43+
*
44+
* The delete-all endpoint uses `del_alternates<NoContent,
45+
* ConfigurationDeleteMultiStatus>` so the framework picks 204 on full success
46+
* or 207 on partial success based on which variant alternative the handler
47+
* returned. Wire format is byte-identical for both branches.
3148
*/
3249
class ConfigHandlers {
3350
public:
34-
/**
35-
* @brief Construct configuration handlers with shared context.
36-
* @param ctx The shared handler context
37-
*/
51+
/// Construct configuration handlers with shared context.
3852
explicit ConfigHandlers(HandlerContext & ctx) : ctx_(ctx) {
3953
}
4054

41-
/**
42-
* @brief Handle GET /components/{component_id}/configurations - list all parameters.
43-
*/
44-
void handle_list_configurations(const httplib::Request & req, httplib::Response & res);
55+
/// GET /{entity-path}/configurations - list all parameters with typed
56+
/// per-item parsing and typed fan-out to peers.
57+
http::Result<dto::Collection<dto::ConfigurationMetaData, dto::ConfigListXMedkit>>
58+
list_configurations(const http::TypedRequest & req);
4559

46-
/**
47-
* @brief Handle GET /components/{component_id}/configurations/{param_name}.
48-
*/
49-
void handle_get_configuration(const httplib::Request & req, httplib::Response & res);
60+
/// GET /{entity-path}/configurations/{param_name} - read a single parameter.
61+
http::Result<dto::ConfigurationReadValue> get_configuration(const http::TypedRequest & req);
5062

51-
/**
52-
* @brief Handle PUT /components/{component_id}/configurations/{param_name}.
53-
*/
54-
void handle_set_configuration(const httplib::Request & req, httplib::Response & res);
63+
/// PUT /{entity-path}/configurations/{param_name} - set a single parameter.
64+
http::Result<dto::ConfigurationReadValue> set_configuration(const http::TypedRequest & req,
65+
dto::ConfigurationWriteRequest body);
5566

56-
/**
57-
* @brief Handle DELETE /components/{component_id}/configurations/{param_name}.
58-
*/
59-
void handle_delete_configuration(const httplib::Request & req, httplib::Response & res);
67+
/// DELETE /{entity-path}/configurations/{param_name} - reset a single
68+
/// parameter to its default value, returning 204 No Content on success.
69+
http::Result<http::NoContent> delete_configuration(const http::TypedRequest & req);
6070

61-
/**
62-
* @brief Handle DELETE /components/{component_id}/configurations - reset all.
63-
*/
64-
void handle_delete_all_configurations(const httplib::Request & req, httplib::Response & res);
71+
/// DELETE /{entity-path}/configurations - reset all parameters. Returns
72+
/// `NoContent` (204) on full success, or `ConfigurationDeleteMultiStatus`
73+
/// (207) when one or more per-node reset operations failed.
74+
http::Result<std::variant<http::NoContent, dto::ConfigurationDeleteMultiStatus>>
75+
delete_all_configurations(const http::TypedRequest & req);
6576

6677
private:
6778
HandlerContext & ctx_;

0 commit comments

Comments
 (0)