cpr icon indicating copy to clipboard operation
cpr copied to clipboard

Enhance "template metaprogramming magic" for HTTP methods

Open GitSparTV opened this issue 1 year ago • 2 comments
trafficstars

Is your feature request related to a problem?

Library provides good experience by providing variadic template interface in HTTP methods, however, you can't template-ly select HTTP method to call.

For example, you have myGet function:

template <class... Ts>
cpr::Response myGet(Ts&&... ts) {
    return cpr::Get(cpr::Bearer{"ACCESS_TOKEN"}, cpr::Header{{"Content-Type", "application/json; charset=utf-8"}});
}

It's great, the headers will always be in the request. However, you also need the same wrapper for POST method. I can duplicate code, but it's lame.

The other way is to provide the function to myGet (ignore the name):

template <typename F, class... Ts>
cpr::Response myGet(F f, Ts&&... ts) {
    return f(std::forward<Ts>(ts)..., cpr::Bearer{"ACCESS_TOKEN"}, cpr::Header{{"Content-Type", "application/json; charset=utf-8"}});
}

The solution will not make you happy:

    cpr::Response r = myGet(cpr::Post<cpr::Url, cpr::Body, cpr::Bearer, cpr::Header>, cpr::Url{"http://www.httpbin.org/post?a=b"}, cpr::Body{"{\"a\": true}"});

You have to specify template parameters for cpr::Post and it looks worse than making a duplicate.

Possible Solution

Add HTTPMethod and a universal template to make a request.

#include <cpr/cpr.h>

namespace cpr {

enum class HTTPMethod {
    kGet,
    kHead,
    kPost,
    kPut,
    kDelete,
    kOptions,
    kPatch,
};

template <HTTPMethod method, typename... Ts>
Response Request(Ts&&... ts) {
    cpr::Session session;
    cpr::priv::set_option(session, std::forward<Ts>(ts)...);

    using enum HTTPMethod;

    if constexpr (method == kGet) {
        return session.Get();
    } else if constexpr (method == kHead) {
        return session.Head();
    } else if constexpr (method == kPost) {
        return session.Post();
    } else if constexpr (method == kPut) {
        return session.Put();
    } else if constexpr (method == kDelete) {
        return session.Delete();
    } else if constexpr (method == kOptions) {
        return session.Options();
    } else if constexpr (method == kPatch) {
        return session.Patch();
    } else {
        static_assert(method == kGet, "Unknown method");
    }
}

}

template <cpr::HTTPMethod method, class... Ts>
cpr::Response MyResponse(Ts&&... ts) {
    return Request<method>(std::forward<Ts>(ts)..., cpr::Bearer{"ACCESS_TOKEN"}, cpr::Header{{"Content-Type", "application/json; charset=utf-8"}});
}

int main() {
    cpr::Response r = MyResponse<cpr::HTTPMethod::kPost>(cpr::Url{"http://www.httpbin.org/post?a=b"}, cpr::Body{"{\"a\": true}"});

    std::cout << r.text << std::endl;
}

Alternatives

  • compile-time if
  • duplicated code for each method

Additional Context

No response

GitSparTV avatar Sep 30 '24 11:09 GitSparTV

@GitSparTV I like this approach. Would you like to create a PR for this?

COM8 avatar Sep 30 '24 14:09 COM8

@COM8

Sure

GitSparTV avatar Sep 30 '24 14:09 GitSparTV