restinio icon indicating copy to clipboard operation
restinio copied to clipboard

Extending response_builder_t<restinio_controlled_output_t>

Open prince-chrismc opened this issue 5 years ago • 2 comments

Would it be possible to to extend the template specialization in order to add custom methods and/or default behavior to the constructor?

There's a lot of boiler plate between create_response() and done(), for example:

restinio::request_handling_status_t edit::operator()(const restinio::request_handle_t &req, restinio::router::route_params_t params) {
  auto &user = list_.get(restinio::cast_to<int>(params["id"]));
  user_management::user_modifier(user).apply(nlohmann::json::parse(req->body()));

  return req->create_response()
      .append_header(restinio::http_field::access_control_allow_origin, "*") // Added on every request
      // More CORS Headers...
      .append_header(restinio::http_field::content_type, "application/json") // Always true
      // Date, Last-Modified, ETag, Accept-Patch ... etc...
      .set_body(nlohmann::json(user).dump()) // This is a typical JSON API, it would be nice to have handlers for my custom types
      .done();
}

Now it would be very easy to have...

restinio::request_handling_status_t edit::operator()(const restinio::request_handle_t &req, restinio::router::route_params_t params) {
  auto &user = list_.get(restinio::cast_to<int>(params["id"]));
  user_management::user_modifier(user).apply(nlohmann::json::parse(req->body()));

  return add_edit_hedaers(add_api_headers(add_cors_headers(req->create_response())))
      .set_body(nlohmann::json(user).dump())
      .done();
}

What I would love to have...

restinio::request_handling_status_t edit::operator()(const restinio::request_handle_t &req, restinio::router::route_params_t params) {
  auto &user = list_.get(restinio::cast_to<int>(params["id"]));
  user_management::user_modifier(user).apply(nlohmann::json::parse(req->body()));

  return req->create_response<edit_response_builder>()
      .set_body(user)
      .done();
}

This begs the question

Why is this class marked final?

https://github.com/Stiffstream/restinio/blob/a6a07d147a04192cd0f590492ce6dd321597d54c/dev/restinio/message_builders.hpp#L257-L259

prince-chrismc avatar Aug 10 '20 01:08 prince-chrismc

Response builders use inheritance only to avoid code duplication. Because of that response_builder_t<T> is not designed to be used in class hierarchies.

I think your case can be solved by using your own response-builder class like that:

class edit_response_builder {
  restinio::response_builder_t<restinio::restinio_controlled_output_t> builder_;
...
public:
  edit_response_builder(restinio::request_t & req) : builder_{req.create_response()} {
    add_cors_headers(builder_);
    add_api_headers(builder_);
    add_edit_headers(builder_);
  }
  ...
  edit_response_builder & set_body(std::string body) {
    builder_.set_body(std::move(body));
    return *this;
  }
  auto done() { return builder_.done(); }
};
...
restinio::request_handling_status_t edit::operator()(const restinio::request_handle_t &req, restinio::router::route_params_t params) {
  auto &user = list_.get(restinio::cast_to<int>(params["id"]));
  user_management::user_modifier(user).apply(nlohmann::json::parse(req->body()));

  return edit_response_builder{*req}
      .set_body(user)
      .done();
}

eao197 avatar Aug 10 '20 11:08 eao197

Typically templates are not bothered by inheritance. :thinking:

That's a very interesting solution, I'll give it a shot!

prince-chrismc avatar Aug 10 '20 12:08 prince-chrismc