From d24ac54087b9c364224fd2aab4111c2eb038db1c Mon Sep 17 00:00:00 2001 From: Arsentiy Milchakov Date: Wed, 18 Oct 2017 17:08:05 +0300 Subject: [PATCH] [partners_api] booking api hotelAvailability method implementation --- partners_api/booking_api.cpp | 144 +++++++++++++++--- partners_api/booking_api.hpp | 99 ++++++++---- .../partners_api_tests/booking_tests.cpp | 43 ++++++ tools/python/ResponseProvider.py | 4 + tools/python/jsons.py | 22 +++ 5 files changed, 257 insertions(+), 55 deletions(-) diff --git a/partners_api/booking_api.cpp b/partners_api/booking_api.cpp index 2b1cb97bb3..bba059d78a 100644 --- a/partners_api/booking_api.cpp +++ b/partners_api/booking_api.cpp @@ -6,26 +6,29 @@ #include "coding/url_encode.hpp" #include "base/get_time.hpp" -#include "base/gmtime.hpp" #include "base/logging.hpp" #include "base/thread.hpp" -#include "std/initializer_list.hpp" -#include "std/iomanip.hpp" -#include "std/iostream.hpp" -#include "std/sstream.hpp" -#include "std/utility.hpp" +#include +#include +#include +#include +#include +#include #include "3party/jansson/myjansson.hpp" #include "private.h" -namespace -{ using namespace platform; using namespace booking; +using namespace std; +using namespace std::chrono; -string const kBookingApiBaseUrl = "https://distribution-xml.booking.com/json/bookings"; +namespace +{ +string const kBookingApiBaseUrlV1 = "https://distribution-xml.booking.com/json/bookings"; +string const kBookingApiBaseUrlV2 = "https://distribution-xml.booking.com/2.0/json/"; string const kExtendedHotelInfoBaseUrl = "https://hotels.maps.me/getDescription"; string const kPhotoOriginalUrl = "http://aff.bstatic.com/images/hotel/max500/"; string const kPhotoSmallUrl = "http://aff.bstatic.com/images/hotel/max300/"; @@ -44,18 +47,20 @@ bool RunSimpleHttpRequest(bool const needAuth, string const & url, string & resu result = request.ServerResponse(); return true; } + return false; } -string MakeApiUrl(string const & func, initializer_list> const & params) +string MakeApiUrl(string const & baseUrl, string const & func, + vector> const & params) { ASSERT_NOT_EQUAL(params.size(), 0, ()); ostringstream os; if (!g_BookingUrlForTesting.empty()) - os << g_BookingUrlForTesting << "." << func << "?"; + os << g_BookingUrlForTesting << func << "?"; else - os << kBookingApiBaseUrl << "." << func << "?"; + os << baseUrl << func << "?"; bool firstParam = true; for (auto const & param : params) @@ -74,6 +79,33 @@ string MakeApiUrl(string const & func, initializer_list> co return os.str(); } +string MakeApiUrlV1(string const & func, vector> const & params) +{ + return MakeApiUrl(kBookingApiBaseUrlV1, "." + func, params); +} + +string MakeApiUrlV2(string const & func, vector> const & params) +{ + return MakeApiUrl(kBookingApiBaseUrlV2, "/" + func, params); +} + +std::string FormatTime(system_clock::time_point p) +{ + time_t t = duration_cast(p.time_since_epoch()).count(); + ostringstream os; + os << put_time(std::gmtime(&t), "%Y-%m-%d"); + return os.str(); +} + +string FormatByComma(vector const & src) +{ + ostringstream os; + ostream_iterator outIt(os, ","); + copy(src.cbegin(), src.cend(), outIt); + + return os.str(); +} + void ClearHotelInfo(HotelInfo & info) { info.m_hotelId.clear(); @@ -238,26 +270,56 @@ void FillPriceAndCurrency(string const & src, string const & currency, string & } } } + +void FillHotelIds(string const & src, vector & result) +{ + my::Json root(src.c_str()); + auto const resultsArray = json_object_get(root.get(), "result"); + + auto const size = json_array_size(resultsArray); + if (size == 0) + return; + + result.resize(size); + for (size_t i = 0; i < size; ++i) + { + auto const obj = json_array_get(resultsArray, i); + FromJSONObject(obj, "hotel_id", result[i]); + } +} } // namespace namespace booking { +vector> AvailabilityParams::Get() const +{ + vector> result; + + result.push_back({"hotel_ids", FormatByComma(m_hotelIds)}); + result.push_back({"checkin", FormatTime(m_checkin)}); + result.push_back({"checkout", FormatTime(m_checkout)}); + + for (size_t i = 0; i < m_rooms.size(); ++i) + result.push_back({"room" + to_string(i+1), m_rooms[i]}); + + if (m_minReviewScore != 0.0) + result.push_back({"min_review_score", std::to_string(m_minReviewScore)}); + + if (!m_stars.empty()) + result.push_back({"stars", FormatByComma(m_stars)}); + + return result; +} + // static bool RawApi::GetHotelAvailability(string const & hotelId, string const & currency, string & result) { - char dateArrival[12]{}; - char dateDeparture[12]{}; - system_clock::time_point p = system_clock::from_time_t(time(nullptr)); - tm arrival = my::GmTime(system_clock::to_time_t(p)); - tm departure = my::GmTime(system_clock::to_time_t(p + hours(24))); - strftime(dateArrival, sizeof(dateArrival), "%Y-%m-%d", &arrival); - strftime(dateDeparture, sizeof(dateDeparture), "%Y-%m-%d", &departure); - string url = MakeApiUrl("getHotelAvailability", {{"hotel_ids", hotelId}, - {"currency_code", currency}, - {"arrival_date", dateArrival}, - {"departure_date", dateDeparture}}); + string url = MakeApiUrlV1("getHotelAvailability", {{"hotel_ids", hotelId}, + {"currency_code", currency}, + {"arrival_date", FormatTime(p)}, + {"departure_date", FormatTime(p + hours(24))}}); return RunSimpleHttpRequest(true, url, result); } @@ -269,6 +331,14 @@ bool RawApi::GetExtendedInfo(string const & hotelId, string const & lang, string return RunSimpleHttpRequest(false, os.str(), result); } +// static +bool RawApi::HotelAvailability(AvailabilityParams const & params, string & result) +{ + string url = MakeApiUrlV2("hotelAvailability", params.Get()); + + return RunSimpleHttpRequest(true, url, result); +} + string Api::GetBookHotelUrl(string const & baseUrl) const { ASSERT(!baseUrl.empty(), ()); @@ -363,6 +433,34 @@ void Api::GetHotelInfo(string const & hotelId, string const & lang, GetHotelInfo }); } +void Api::GetHotelAvailability(AvailabilityParams const & params, + GetHotelAvailabilityCallback const & fn) +{ + GetPlatform().RunOnNetworkThread([params, fn]() + { + std::vector result; + string httpResult; + if (!RawApi::HotelAvailability(params, httpResult)) + { + fn(result); + return; + } + + try + { + FillHotelIds(httpResult, result); + } + catch (my::Json::Exception const & e) + { + LOG(LINFO, (httpResult)); + LOG(LERROR, (e.Msg())); + result.clear(); + } + + fn(result); + }); +} + void SetBookingUrlForTesting(string const & url) { g_BookingUrlForTesting = url; diff --git a/partners_api/booking_api.hpp b/partners_api/booking_api.hpp index c81acd611c..1cd8bd86b7 100644 --- a/partners_api/booking_api.hpp +++ b/partners_api/booking_api.hpp @@ -2,76 +2,111 @@ #include "platform/safe_callback.hpp" -#include "std/chrono.hpp" -#include "std/function.hpp" -#include "std/shared_ptr.hpp" -#include "std/string.hpp" -#include "std/vector.hpp" +#include +#include +#include +#include +#include namespace booking { struct HotelPhotoUrls { - string m_small; - string m_original; + std::string m_small; + std::string m_original; }; struct HotelReview { /// An issue date. - time_point m_date; + std::chrono::time_point m_date; /// Author's hotel evaluation. float m_score = 0.0; /// Review author name. - string m_author; + std::string m_author; /// Review text. There can be either one or both positive/negative review. - string m_pros; - string m_cons; + std::string m_pros; + std::string m_cons; }; struct HotelFacility { - string m_type; - string m_name; + std::string m_type; + std::string m_name; }; struct HotelInfo { - string m_hotelId; + std::string m_hotelId; - string m_description; - vector m_photos; - vector m_facilities; - vector m_reviews; + std::string m_description; + std::vector m_photos; + std::vector m_facilities; + std::vector m_reviews; float m_score = 0.0; uint32_t m_scoreCount = 0; }; +/// Params for checking availability of hotels. +/// [m_hotelIds], [m_checkin], [m_checkout], [m_rooms] are required. +struct AvailabilityParams +{ + using Time = std::chrono::system_clock::time_point; + + std::vector> Get() const; + + /// Limit the result list to the specified hotels where they have availability for the + /// specified guests and dates. + std::vector m_hotelIds; + /// The arrival date. Must be within 360 days in the future and in the format yyyy-mm-dd. + Time m_checkin; + /// The departure date. Must be later than [m_checkin]. Must be between 1 and 30 days after + /// [m_checkin]. Must be within 360 days in the future and in the format yyyy-mm-dd. + Time m_checkout; + /// Each room is s comma separated array of guests to stay in this room where "A" represents an + /// adult and an integer represents a child. eg room1=A,A,4 would be a room with 2 adults and 1 + /// four year-old child. Child age numbers are 0..17. + std::vector m_rooms; + /// Show only hotels with review_score >= that. min_review_score should be in the range 1 to 10. + /// Values are rounded down: min_review_score 7.8 will result in properties with review scores + /// of 7 and up. + double m_minReviewScore = {}; + /// Limit to hotels with the given number(s) of stars. Supported values 1-5. + vector m_stars; +}; + class RawApi { public: - static bool GetHotelAvailability(string const & hotelId, string const & currency, string & result); - static bool GetExtendedInfo(string const & hotelId, string const & lang, string & result); + // Booking Api v1 methods: + static bool GetHotelAvailability(std::string const & hotelId, std::string const & currency, std::string & result); + static bool GetExtendedInfo(std::string const & hotelId, std::string const & lang, std::string & result); + // Booking Api v2 methods: + static bool HotelAvailability(AvailabilityParams const & params, std::string & result); }; -using GetMinPriceCallback = platform::SafeCallback; +using GetMinPriceCallback = platform::SafeCallback; using GetHotelInfoCallback = platform::SafeCallback; +using GetHotelAvailabilityCallback = platform::SafeCallback hotelIds)>; class Api { public: - string GetBookHotelUrl(string const & baseUrl) const; - string GetDescriptionUrl(string const & baseUrl) const; - string GetHotelReviewsUrl(string const & hotelId, string const & baseUrl) const; - string GetSearchUrl(string const & city, string const & name) const; - // Real-time information methods (used for retriving rapidly changing information). - // These methods send requests directly to Booking. - void GetMinPrice(string const & hotelId, string const & currency, GetMinPriceCallback const & fn); + std::string GetBookHotelUrl(std::string const & baseUrl) const; + std::string GetDescriptionUrl(std::string const & baseUrl) const; + std::string GetHotelReviewsUrl(std::string const & hotelId, std::string const & baseUrl) const; + std::string GetSearchUrl(std::string const & city, std::string const & name) const; + /// Real-time information methods (used for retriving rapidly changing information). + /// These methods send requests directly to Booking. + void GetMinPrice(std::string const & hotelId, std::string const & currency, GetMinPriceCallback const & fn); - // Static information methods (use for information that can be cached). - // These methods use caching server to prevent Booking from being ddossed. - void GetHotelInfo(string const & hotelId, string const & lang, GetHotelInfoCallback const & fn); + /// Static information methods (use for information that can be cached). + /// These methods use caching server to prevent Booking from being ddossed. + void GetHotelInfo(std::string const & hotelId, std::string const & lang, GetHotelInfoCallback const & fn); + + void GetHotelAvailability(AvailabilityParams const & params, + GetHotelAvailabilityCallback const & fn); }; -void SetBookingUrlForTesting(string const & url); +void SetBookingUrlForTesting(std::string const & url); } // namespace booking diff --git a/partners_api/partners_api_tests/booking_tests.cpp b/partners_api/partners_api_tests/booking_tests.cpp index 43755bc77f..bf8e85994d 100644 --- a/partners_api/partners_api_tests/booking_tests.cpp +++ b/partners_api/partners_api_tests/booking_tests.cpp @@ -6,6 +6,8 @@ #include "base/scope_guard.hpp" +#include + using namespace partners_api; namespace @@ -26,6 +28,20 @@ UNIT_TEST(Booking_GetExtendedInfo) TEST(!result.empty(), ()); } +UNIT_TEST(Booking_HotelAvailability) +{ + booking::AvailabilityParams params; + params.m_hotelIds = {"98251"}; + params.m_rooms = {"A,A"}; + params.m_checkin = std::chrono::system_clock::now() + std::chrono::hours(24); + params.m_checkout = std::chrono::system_clock::now() + std::chrono::hours(24 * 7); + params.m_stars = {"4", "5"}; + string result; + TEST(booking::RawApi::HotelAvailability(params, result), ()); + TEST(!result.empty(), ()); + LOG(LINFO, (result)); +} + UNIT_CLASS_TEST(AsyncGuiThread, Booking_GetMinPrice) { booking::SetBookingUrlForTesting("http://localhost:34568/booking/min_price"); @@ -110,4 +126,31 @@ UNIT_CLASS_TEST(AsyncGuiThread, GetHotelInfo) TEST_EQUAL(info.m_facilities.size(), 7, ()); TEST_EQUAL(info.m_reviews.size(), 4, ()); } + +UNIT_CLASS_TEST(AsyncGuiThread, GetHotelAvailability) +{ + booking::SetBookingUrlForTesting("http://localhost:34568/booking/min_price"); + MY_SCOPE_GUARD(cleanup, []() { booking::SetBookingUrlForTesting(""); }); + + booking::AvailabilityParams params; + params.m_hotelIds = {"77615", "10623"}; + params.m_rooms = {"A,A"}; + params.m_checkin = std::chrono::system_clock::now() + std::chrono::hours(24); + params.m_checkout = std::chrono::system_clock::now() + std::chrono::hours(24 * 7); + params.m_stars = {"4"}; + booking::Api api; + std::vector result; + + api.GetHotelAvailability(params, [&result](std::vector const & r) + { + result = r; + testing::Notify(); + }); + testing::Wait(); + + TEST_EQUAL(result.size(), 3, ()); + TEST_EQUAL(result[0], 10623, ()); + TEST_EQUAL(result[1], 10624, ()); + TEST_EQUAL(result[2], 10625, ()); +} } diff --git a/tools/python/ResponseProvider.py b/tools/python/ResponseProvider.py index 56f2e0e9b9..681af67c0f 100644 --- a/tools/python/ResponseProvider.py +++ b/tools/python/ResponseProvider.py @@ -143,6 +143,7 @@ class ResponseProvider: "/partners/price": self.partners_price, "/booking/min_price": self.partners_minprice, "/booking/min_price.getHotelAvailability": self.partners_minprice, + "/booking/min_price/hotelAvailability": self.partners_hotel_availability, "/partners/taxi_info": self.partners_yandex_taxi_info, "/partners/get-offers-in-bbox/": self.partners_rent_nearby, }[url]() @@ -223,6 +224,9 @@ class ResponseProvider: def partners_minprice(self): return Payload(jsons.PARTNERS_MINPRICE) + def partners_hotel_availability(self): + return Payload(jsons.HOTEL_AVAILABILITY) + def partners_yandex_taxi_info(self): return Payload(jsons.PARTNERS_TAXI_INFO) diff --git a/tools/python/jsons.py b/tools/python/jsons.py index 8fd47af932..03040fc966 100644 --- a/tools/python/jsons.py +++ b/tools/python/jsons.py @@ -186,6 +186,28 @@ PARTNERS_TIME = """ } """ +HOTEL_AVAILABILITY = """ +{ + "result": [ + { + "hotel_currency_code": "EUR", + "hotel_id": 10623, + "price": 801 + }, + { + "hotel_currency_code": "USD", + "hotel_id": 10624, + "price": 802 + }, + { + "hotel_currency_code": "RUR", + "hotel_id": 10625, + "price": 803 + } + ] +} +""" + PARTNERS_MINPRICE = """ [ {