jgaa / restc-cpp

Modern C++ REST Client library
MIT License
605 stars 95 forks source link

Error 404 Fetching JSON data from Movie API #51

Closed thefiend closed 6 years ago

thefiend commented 6 years ago

Hi, I am trying to fetch movie information from a Movie API. However, I get

libc++abi.dylib: terminating with uncaught exception of type restc_cpp::HttpNotFoundException: Request failed with HTTP error: 404 Not Found

trying to do so and wish to know how do I change the struct to fit the API.

E.g.

screen shot 2018-03-11 at 3 42 57 pm
#include <iostream>

#include <boost/lexical_cast.hpp>
#include <boost/fusion/adapted.hpp>

#include "restc-cpp/restc-cpp.h"
#include "restc-cpp/RequestBuilder.h"

using namespace std;
using namespace restc_cpp;

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;
};

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
        (int, total_results)
        (int, total_pages)
)

int main() {
    auto rest_client = RestClient::Create();
    Movie found_movies = rest_client->ProcessWithPromiseT<Movie>([&](Context& ctx) {

                Movie movie;

                SerializeFromJson(movie,
                                  RequestBuilder(ctx)
                                          .Get("https://api.themoviedb.org/3/search/movie?api_key=30ac911b5a4841e65d905a53b31396ae&query=Jack+Reacher")
                                          .Header("X-Client", "RESTC_CPP")
                                          .Header("X-Client-Purpose", "Testing")
                                          .Execute());

                return movie;
            }).get();

    cout << "Number of Results " << found_movies.total_results << ", Page: " << found_movies.page << endl;
}
jgaa commented 6 years ago

I'll have a look at it

jgaa commented 6 years ago

The url you provide will be encoded, and that will prevent the server from decoding your authentication token.

Try

                            SerializeFromJson(movie,
                                  RequestBuilder(ctx)
                                          .Get("http://api.themoviedb.org/3/search/movie")
                                          .Argument("api_key", "30ac911b5a4841e65d905a53b31396ae")
                                          .Argument("query", "Jack+Reacher")
                                          .Header("X-Client", "RESTC_CPP")
                                          .Header("X-Client-Purpose", "Testing")
                                          .Execute());

The Argument() functions will make sure that the arguments are sent correctly.

You should 'git pull' restc-cpp, as I found and fixed a bug wen I checked this. You may also want to get a new authentication token :)

thefiend commented 6 years ago

I have pulled and installed the restc-cpp library and used the code above. However, I am getting an error from SerializeJson.

Assertion failed: (false), function DoRecurseToMember, file /usr/local/include/restc-cpp/SerializeJson.h, line 704.

Edit: It is due to me trying to add the struct of results and it seems to be the wrong way of adding it.

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;
//    string results = {}; //this is causing the issue
};

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
        (int, total_results)
        (int, total_pages)
//        (string, results) //this is causing the issue
)

This is what I have for now:

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;

    union results {
        struct {
            int vote_count;
            int id;
            bool video;
            string title;
            string popularity;
            string poster_path;
            string original_language;
            string original_title;
            string overview;
            string release_date;
        };
    };
};

but I am not sure how to edit

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
                (int, total_results)
                (int, total_pages)
//        (string, results)
)
jgaa commented 6 years ago

I would put the results in a structure and then and then add it as:

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;
    std::vector<Results> results;
}

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
                (int, total_results)
                (int, total_pages)
        (std::vector<Results>, results)
)

Make sure that Movie has the same member, as you map directly from C++ data-type and name.

You can have a look at this header to see some more complex mappings.

thefiend commented 6 years ago

I am getting Process finished with exit code 11 from this

struct Results {
    int vote_count= 0;
    int id= 0;
    bool video;
    double vote_average;
    string title;
    double popularity;
    string poster_path;
    string original_language;
    string original_title;
    bool adult;
    string overview;
    string release_date;
};

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;
    std::vector<Results> results;
};

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
                (int, total_results)
                (int, total_pages)
                (std::vector<Results>, results)
)
jgaa commented 6 years ago

I'll take a look

jgaa commented 6 years ago

Since you are serializing Results, you need to declare that as well :)

BOOST_FUSION_ADAPT_STRUCT(
    Results,
    (int, vote_count)
    (int, id)
    (bool, video)
    (double, vote_average)
    (string, title)
    (double, popularity)
    (string, poster_path)
    (string, original_language)
    (string, original_title)
    (bool, adult)
    (string, overview)
    (string, release_date)
)

I have committed an assert for that case, and a fix where restc-cpp needs to ignore an array (genre_ids). So you should 'git poll' before testing again.

thefiend commented 6 years ago

Awesome! Got it to work. Thank you very much. Really appreciate it. :D

thefiend commented 6 years ago

I tried querying other movies such as Titanic and Black Panther and got this exception. Assertion failed: (current_name_.empty()), function DoKey, file /usr/local/include/restc-cpp/SerializeJson.h, line 1024.

jgaa commented 6 years ago

That's interesting. Can you share with me the request URL's or the json payloads from this requests? (curl can give you the payload from the command-line).

thefiend commented 6 years ago

It is the same as above just that I changed .Argument("query", "Jack+Reacher") to .Argument("query", "titanic")

Edit: I realised it is because genre_ids and backdrop_path are not set in struct.

struct Results {
    int vote_count= 0;
    int id= 0;
    bool video;
    double vote_average;
    string title;
    double popularity;
    string poster_path;
    string original_language;
    string original_title;
    std::vector<int> genre_ids;
    string backdrop_path;
    bool adult;
    string overview;
    string release_date;
};

struct Movie {
    int page = 0;
    int total_results = 0;
    int total_pages = 0;
    std::vector<Results> results;
};

BOOST_FUSION_ADAPT_STRUCT(
        Results,
        (int, vote_count)
                (int, id)
                (bool, video)
                (double, vote_average)
                (string, title)
                (double, popularity)
                (string, poster_path)
                (string, original_language)
                (string, original_title)
                (std::vector<int>, genre_ids)
                (string, backdrop_path)
                (bool, adult)
                (string, overview)
                (string, release_date)
)

BOOST_FUSION_ADAPT_STRUCT(
        Movie,
        (int, page)
                (int, total_results)
                (int, total_pages)
                (std::vector<Results>, results)
)

I added genre_ids and backdrop_path in struct and it works for query of Jack+Reacher but not spiderman or titanic. It causes the error:

Assertion failed: (currentname.empty()), function DoKey, file /usr/local/include/restc-cpp/SerializeJson.h, line 1024.

jgaa commented 6 years ago

I'll take a look at it tomorrow. It might be a bug.

thefiend commented 6 years ago

Did I declare the array genre_ids wrongly? Update: It seems to work for batman but does not work for query of spiderman Assertion failed: (current_name_.empty()), function DoKey, file /usr/local/include/restc-cpp/SerializeJson.h, line 1024.

jgaa commented 6 years ago

I can reproduce it. Trying to figure out what's triggering the error.

jgaa commented 6 years ago

You found another bug! I have committed a fix.

This list could actually be implemented as an iterator, where you loop over the content. That would allow huge result-sets. I have implemented that for one project, and been thinking about doing it in a more general fashion. If I get time in the near future, I'll see if I can use this movie API as one of the examples for such a general feature.

Thank you for digging out these bugs btw. If this was a commercial project there would have been a QA person spending considerable time figuring out how to crash the library - but with free, open source, I have to relay on my own imagination and real life cases reported by users.

thefiend commented 6 years ago

Great! Really thankful that you made it work. I will be trying to get it to work with Twitter API next, to pull tweets from Twitter. Any idea how to authorize request on twitter API with this library?

It should work if I Add_Headers() the following for authentication? OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog", oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg", oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1318622958", oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb", oauth_version="1.0"

jgaa commented 6 years ago

I don't really understand why these services keeps using OAuth. It's been broken (insecure) for a long time.

Anyway, using it requires cryptographic signing of headers and content in a very specific way. I think it should be done by restc-cpp as the process is complex and will take time to comprehend an implement. It's documented here: https://developer.twitter.com/en/docs/basics/authentication/guides/authorizing-a-request

The general OAuth 1.0 protocol is documented here: https://oauth.net/core/1.0/#anchor9

I don't have time to implement this right away, but I'll put it on my list.