Stiffstream / restinio

Cross-platform, efficient, customizable, and robust asynchronous HTTP(S)/WebSocket server C++ library with the right balance between performance and ease of use
Other
1.15k stars 93 forks source link

Extending response_builder_t<restinio_controlled_output_t> #122

Open prince-chrismc opened 4 years ago

prince-chrismc commented 4 years ago

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

eao197 commented 4 years ago

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();
}
prince-chrismc commented 4 years ago

Typically templates are not bothered by inheritance. :thinking:

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