libcpr / cpr

C++ Requests: Curl for People, a spiritual port of Python Requests.
https://docs.libcpr.org/
Other
6.58k stars 935 forks source link

Enhance "template metaprogramming magic" for HTTP methods #1117

Open GitSparTV opened 1 month ago

GitSparTV commented 1 month ago

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

Additional Context

No response

COM8 commented 1 month ago

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

GitSparTV commented 1 month ago

@COM8

Sure