diff options
author | Arsentiy Milchakov <milcars@mapswithme.com> | 2018-06-19 17:16:50 +0300 |
---|---|---|
committer | Vlad Mihaylenko <vxmihaylenko@gmail.com> | 2018-06-21 15:25:28 +0300 |
commit | b6c0b3c25e178bc749aeaab38c6d4cda066461df (patch) | |
tree | 10861624c0b5789da9bb65712c82e98e39be69ee /partners_api | |
parent | b6fc1f76f4e6db9d4661c45cd77a92441a5e55bc (diff) |
[booking] added method to retrieve detailed info about hotel rooms (blockAvailability). GetMinPrice method is refactored.
Diffstat (limited to 'partners_api')
-rw-r--r-- | partners_api/CMakeLists.txt | 2 | ||||
-rw-r--r-- | partners_api/booking_api.cpp | 160 | ||||
-rw-r--r-- | partners_api/booking_api.hpp | 61 | ||||
-rw-r--r-- | partners_api/booking_availability_params.cpp | 6 | ||||
-rw-r--r-- | partners_api/booking_availability_params.hpp | 2 | ||||
-rw-r--r-- | partners_api/booking_block_params.cpp | 60 | ||||
-rw-r--r-- | partners_api/booking_block_params.hpp | 33 | ||||
-rw-r--r-- | partners_api/booking_params_base.hpp | 16 | ||||
-rw-r--r-- | partners_api/partners_api_tests/booking_tests.cpp | 73 |
9 files changed, 322 insertions, 91 deletions
diff --git a/partners_api/CMakeLists.txt b/partners_api/CMakeLists.txt index b8a474c7f3..d7f0e3f53a 100644 --- a/partners_api/CMakeLists.txt +++ b/partners_api/CMakeLists.txt @@ -13,6 +13,8 @@ set( booking_api.hpp booking_availability_params.cpp booking_availability_params.hpp + booking_block_params.cpp + booking_block_params.hpp booking_params_base.hpp facebook_ads.cpp facebook_ads.hpp diff --git a/partners_api/booking_api.cpp b/partners_api/booking_api.cpp index dd4732d364..ad92a1548d 100644 --- a/partners_api/booking_api.cpp +++ b/partners_api/booking_api.cpp @@ -30,7 +30,7 @@ using namespace std::chrono; namespace { string const kBookingApiBaseUrlV1 = "https://distribution-xml.booking.com/json/bookings"; -string const kBookingApiBaseUrlV2 = "https://distribution-xml.booking.com/2.0/json/"; +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/"; @@ -231,43 +231,123 @@ void FillHotelInfo(string const & src, HotelInfo & info) info.m_reviews = ParseReviews(reviewsArray); } -void FillPriceAndCurrency(string const & src, string const & currency, string & minPrice, - string & priceCurrency) +void FillPriceAndCurrency(json_t * src, string const & currency, BlockInfo & result) { - my::Json root(src.c_str()); - if (!json_is_array(root.get())) - MYTHROW(my::Json::Exception, ("The answer must contain a json array.")); - size_t const rootSize = json_array_size(root.get()); + FromJSONObject(src, "currency", result.m_currency); + FromJSONObject(src, "price", result.m_minPrice); - if (rootSize == 0) + if (currency.empty() || result.m_currency == currency) return; - // Read default hotel price and currency. - auto obj = json_array_get(root.get(), 0); - FromJSONObject(obj, "min_price", minPrice); - FromJSONObject(obj, "currency_code", priceCurrency); - - if (currency.empty() || priceCurrency == currency) + // Try to get price in requested currency. + json_t * other = json_object_get(src, "other_currency"); + if (!json_is_object(other)) return; - // Try to get price in requested currency. - json_t * arr = json_object_get(obj, "other_currency"); - if (arr == nullptr || !json_is_array(arr)) + string otherCurrency; + FromJSONObject(other, "currency", otherCurrency); + if (otherCurrency == currency) + { + result.m_currency = otherCurrency; + FromJSONObject(other, "price", result.m_minPrice); return; + } +} + +BlockInfo MakeBlock(json_t * src, string const & currency) +{ + BlockInfo result; + + FromJSONObject(src, "block_id", result.m_blockId); + FromJSONObject(src, "name", result.m_name); + FromJSONObject(src, "room_description", result.m_description); + FromJSONObject(src, "max_occupancy", result.m_maxOccupancy); + FromJSONObject(src, "breakfast_included", result.m_breakfastIncluded); + FromJSONObject(src, "deposit_required", result.m_depositRequired); - size_t sz = json_array_size(arr); - string code; + auto minPriceRoot = json_object_get(src, "min_price"); + if (!json_is_object(minPriceRoot)) + MYTHROW(my::Json::Exception, ("The min_price must contain a json object.")); + + FillPriceAndCurrency(minPriceRoot, currency, result); + + auto photosArray = json_object_get(src, "photos"); + size_t sz = json_array_size(photosArray); + string photoUrl; for (size_t i = 0; i < sz; ++i) { - auto el = json_array_get(arr, i); - FromJSONObject(el, "currency_code", code); - if (code == currency) + auto photoItem = json_array_get(photosArray, i); + FromJSONObject(photoItem, "url_original", photoUrl); + result.m_photos.emplace_back(photoUrl); + } + + Deals deals; + bool lastMinuteDeal = false; + FromJSONObjectOptionalField(src, "is_last_minute_deal", lastMinuteDeal); + if (lastMinuteDeal) + { + deals.m_types.emplace_back(Deals::Type::LastMinute); + FromJSONObject(src, "last_minute_deal_percentage", deals.m_discount); + } + bool smartDeal = false; + FromJSONObjectOptionalField(src, "is_smart_deal", smartDeal); + if (smartDeal) + deals.m_types.emplace_back(Deals::Type::Smart); + + string refundableUntil; + auto refundableUntilObject = json_object_get(src, "refundable_until"); + if (json_is_string(refundableUntilObject)) + { + FromJSON(refundableUntilObject, refundableUntil); + + if (!refundableUntil.empty()) { - priceCurrency = code; - FromJSONObject(el, "min_price", minPrice); - break; + istringstream ss(refundableUntil); + tm t = {}; + ss >> base::get_time(&t, "%Y-%m-%d %H:%M:%S"); + if (ss.fail()) + LOG(LWARNING, ("Incorrect refundable_until date =", refundableUntil)); + else + result.m_refundableUntil = system_clock::from_time_t(mktime(&t)); } } + + return result; +} + +void FillBlocks(string const & src, string const & currency, Blocks & blocks) +{ + my::Json root(src.c_str()); + if (!json_is_object(root.get())) + MYTHROW(my::Json::Exception, ("The answer must contain a json object.")); + + auto rootArray = json_object_get(root.get(), "result"); + if (!json_is_array(rootArray)) + MYTHROW(my::Json::Exception, ("The \"result\" field must contain a json array.")); + + size_t const rootSize = json_array_size(rootArray); + ASSERT_LESS(rootSize, 2, ("Several hotels is not supported in this method")); + if (rootSize == 0) + return; + + auto rootItem = json_array_get(rootArray, 0); + if (!json_is_object(rootItem)) + MYTHROW(my::Json::Exception, ("The root item must contain a json object.")); + + auto blocksArray = json_object_get(rootItem, "block"); + if (!json_is_array(blocksArray)) + MYTHROW(my::Json::Exception, ("The \"block\" field must contain a json array.")); + + size_t const blocksSize = json_array_size(blocksArray); + for (size_t i = 0; i < blocksSize; ++i) + { + auto block = json_array_get(blocksArray, i); + + if (!json_is_object(block)) + MYTHROW(my::Json::Exception, ("The block item must contain a json object.")); + + blocks.Add(MakeBlock(block, currency)); + } } void FillHotelIds(string const & src, vector<std::string> & ids) @@ -345,6 +425,14 @@ bool RawApi::HotelAvailability(AvailabilityParams const & params, string & resul return RunSimpleHttpRequest(true, url, result); } +// static +bool RawApi::BlockAvailability(BlockParams const & params, string & result) +{ + string url = MakeApiUrlV2("blockAvailability", params.Get()); + + return RunSimpleHttpRequest(true, url, result); +} + string Api::GetBookHotelUrl(string const & baseUrl) const { ASSERT(!baseUrl.empty(), ()); @@ -407,31 +495,29 @@ string Api::ApplyAvailabilityParams(string const & url, AvailabilityParams const return ApplyAvailabilityParamsUniversal(url, params); } -void Api::GetMinPrice(string const & hotelId, string const & currency, - GetMinPriceCallback const & fn) const +void Api::GetBlockAvailability(BlockParams const & params, + BlockAvailabilityCallback const & fn) const { - GetPlatform().RunTask(Platform::Thread::Network, [hotelId, currency, fn]() + GetPlatform().RunTask(Platform::Thread::Network, [params, fn]() { - string minPrice; - string priceCurrency; string httpResult; - if (!RawApi::GetHotelAvailability(hotelId, currency, httpResult)) + if (!RawApi::BlockAvailability(params, httpResult)) { - fn(hotelId, minPrice, priceCurrency); + fn(params.m_hotelId, {}); return; } + Blocks blocks; try { - FillPriceAndCurrency(httpResult, currency, minPrice, priceCurrency); + FillBlocks(httpResult, params.m_currency, blocks); } catch (my::Json::Exception const & e) { LOG(LERROR, (e.Msg())); - minPrice.clear(); - priceCurrency.clear(); + blocks = {}; } - fn(hotelId, minPrice, priceCurrency); + fn(params.m_hotelId, blocks); }); } @@ -456,7 +542,7 @@ void Api::GetHotelInfo(string const & hotelId, string const & lang, } catch (my::Json::Exception const & e) { - LOG(LERROR, ("Failed to parse json:", hotelId, result, e.what())); + LOG(LINFO, ("Failed to parse json:", hotelId, result, e.what())); ClearHotelInfo(info); } diff --git a/partners_api/booking_api.hpp b/partners_api/booking_api.hpp index 5c4302c3b6..f35d295003 100644 --- a/partners_api/booking_api.hpp +++ b/partners_api/booking_api.hpp @@ -1,6 +1,7 @@ #pragma once #include "partners_api/booking_availability_params.hpp" +#include "partners_api/booking_block_params.hpp" #include "platform/safe_callback.hpp" @@ -48,6 +49,59 @@ struct HotelInfo uint32_t m_scoreCount = 0; }; +struct Deals +{ + enum Type + { + /// Good price. + Smart, + /// Sale with discount in percent from base price. + LastMinute + }; + + std::vector<Type> m_types; + uint8_t m_discount = 0; +}; + +struct BlockInfo +{ + static double constexpr kIncorrectPrice = std::numeric_limits<double>::max(); + std::string m_blockId; + std::string m_name; + std::string m_description; + uint8_t m_maxOccupancy = 0; + double m_minPrice = kIncorrectPrice; + std::string m_currency; + std::vector<std::string> m_photos; + Deals m_deals; + std::chrono::time_point<std::chrono::system_clock> m_refundableUntil; + bool m_breakfastIncluded = false; + bool m_depositRequired = false; +}; + +struct Blocks +{ + void Add(BlockInfo && block) + { + if (block.m_minPrice < m_totalMinPrice) + { + m_totalMinPrice = block.m_minPrice; + m_currency = block.m_currency; + } + if (block.m_deals.m_discount > m_maxDiscount) + m_maxDiscount = block.m_deals.m_discount; + + m_blocks.emplace_back(block); + } + + double m_totalMinPrice = BlockInfo::kIncorrectPrice; + std::string m_currency; + + uint8_t m_maxDiscount = 0; + + std::vector<BlockInfo> m_blocks; +}; + class RawApi { public: @@ -56,9 +110,11 @@ public: 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); + static bool BlockAvailability(BlockParams const & params, string & result); }; -using GetMinPriceCallback = platform::SafeCallback<void(std::string const & hotelId, std::string const & price, std::string const & currency)>; +using BlockAvailabilityCallback = + platform::SafeCallback<void(std::string const & hotelId, Blocks const & blocks)>; using GetHotelInfoCallback = platform::SafeCallback<void(HotelInfo const & hotelInfo)>; // NOTE: this callback will be called on the network thread. using GetHotelAvailabilityCallback = std::function<void(std::vector<std::string> hotelIds)>; @@ -79,8 +135,7 @@ public: /// Real-time information methods (used for retrieving rapidly changing information). /// These methods send requests directly to Booking. - void GetMinPrice(std::string const & hotelId, std::string const & currency, - GetMinPriceCallback const & fn) const; + void GetBlockAvailability(BlockParams const & params, BlockAvailabilityCallback const & fn) const; /// NOTE: callback will be called on the network thread. void GetHotelAvailability(AvailabilityParams const & params, diff --git a/partners_api/booking_availability_params.cpp b/partners_api/booking_availability_params.cpp index 341e4d32da..506b764a13 100644 --- a/partners_api/booking_availability_params.cpp +++ b/partners_api/booking_availability_params.cpp @@ -1,5 +1,4 @@ #include "partners_api/booking_availability_params.hpp" -#include "partners_api/utils.hpp" #include "base/string_utils.hpp" @@ -9,11 +8,6 @@ using namespace base; namespace { -std::string FormatTime(booking::AvailabilityParams::Time p) -{ - return partners_api::FormatTime(p, "%Y-%m-%d"); -} - bool IsAcceptedByFilter(booking::AvailabilityParams::UrlFilter const & filter, std::string const & value) { diff --git a/partners_api/booking_availability_params.hpp b/partners_api/booking_availability_params.hpp index 3f3bc26db9..7d29c742aa 100644 --- a/partners_api/booking_availability_params.hpp +++ b/partners_api/booking_availability_params.hpp @@ -37,8 +37,6 @@ struct AvailabilityParams : public ParamsBase int8_t m_ageOfChild = kNoChildren; }; - using Clock = std::chrono::system_clock; - using Time = Clock::time_point; using Hotels = std::vector<std::string>; using Rooms = std::vector<Room>; using Stars = std::vector<std::string>; diff --git a/partners_api/booking_block_params.cpp b/partners_api/booking_block_params.cpp new file mode 100644 index 0000000000..bcd98b4548 --- /dev/null +++ b/partners_api/booking_block_params.cpp @@ -0,0 +1,60 @@ +#include "partners_api/booking_block_params.hpp" + +#include "base/string_utils.hpp" + +using namespace base; + +namespace booking +{ +// static +BlockParams BlockParams::MakeDefault() +{ + BlockParams result; + // Use tomorrow and day after tomorrow by default. + result.m_checkin = Clock::now() + std::chrono::hours(24); + result.m_checkout = Clock::now() + std::chrono::hours(48); + // Information about sales by default. + result.m_extras = {"deal_smart", "deal_lastm", "photos"}; + + return result; +} + +url::Params BlockParams::Get() const +{ + url::Params params = {{"hotel_ids", m_hotelId}, + {"checkin", FormatTime(m_checkin)}, + {"checkout", FormatTime(m_checkout)}}; + + if (!m_currency.empty()) + params.emplace_back("currency", m_currency); + + if (!m_extras.empty()) + params.emplace_back("extras", strings::JoinStrings(m_extras, ',')); + + if (!m_language.empty()) + params.emplace_back("language", m_language); + + return params; +} + +bool BlockParams::IsEmpty() const +{ + return m_checkin == Time() || m_checkout == Time(); +} + +bool BlockParams::Equals(ParamsBase const & rhs) const +{ + return rhs.Equals(*this); +} + +bool BlockParams::Equals(BlockParams const & rhs) const +{ + return m_checkin == rhs.m_checkin && m_checkout == rhs.m_checkout && + m_currency == rhs.m_currency && m_extras == rhs.m_extras && m_language == rhs.m_language; +} + +void BlockParams::Set(ParamsBase const & src) +{ + src.CopyTo(*this); +} +} // namepace booking
\ No newline at end of file diff --git a/partners_api/booking_block_params.hpp b/partners_api/booking_block_params.hpp new file mode 100644 index 0000000000..836cfbfc49 --- /dev/null +++ b/partners_api/booking_block_params.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "partners_api/booking_params_base.hpp" + +#include "base/url_helpers.hpp" + +#include <string> +#include <vector> + +namespace booking +{ +struct BlockParams : public ParamsBase +{ + using Extras = std::vector<std::string>; + + static BlockParams MakeDefault(); + + base::url::Params Get() const; + + // ParamsBase overrides: + bool IsEmpty() const override; + bool Equals(ParamsBase const & rhs) const override; + bool Equals(BlockParams const & lhs) const override; + void Set(ParamsBase const & src) override; + + std::string m_hotelId; + std::string m_currency; + Time m_checkin; + Time m_checkout; + Extras m_extras; + std::string m_language; +}; +} // namespce booking diff --git a/partners_api/booking_params_base.hpp b/partners_api/booking_params_base.hpp index a740197b34..c2c275662b 100644 --- a/partners_api/booking_params_base.hpp +++ b/partners_api/booking_params_base.hpp @@ -1,5 +1,7 @@ #pragma once +#include "partners_api/utils.hpp" + #include "base/macros.hpp" #include <exception> @@ -7,9 +9,18 @@ namespace booking { struct AvailabilityParams; +struct BlockParams; struct ParamsBase { + using Clock = std::chrono::system_clock; + using Time = Clock::time_point; + + static std::string FormatTime(Time p) + { + return partners_api::FormatTime(p, "%Y-%m-%d"); + } + virtual ~ParamsBase() = default; virtual bool IsEmpty() const = 0; @@ -21,6 +32,11 @@ struct ParamsBase return false; } + virtual bool Equals(BlockParams const & lhs) const + { + return false; + } + template <typename T> void CopyTo(T & dest) const { diff --git a/partners_api/partners_api_tests/booking_tests.cpp b/partners_api/partners_api_tests/booking_tests.cpp index fdf741e2b8..5491a9c21a 100644 --- a/partners_api/partners_api_tests/booking_tests.cpp +++ b/partners_api/partners_api_tests/booking_tests.cpp @@ -16,7 +16,7 @@ class AsyncGuiThreadBooking : public AsyncGuiThread public: AsyncGuiThreadBooking() { - SetBookingUrlForTesting("http://localhost:34568/booking/min_price"); + SetBookingUrlForTesting("http://localhost:34568/booking"); } ~AsyncGuiThreadBooking() override @@ -55,66 +55,53 @@ UNIT_TEST(Booking_HotelAvailability) LOG(LINFO, (result)); } -UNIT_CLASS_TEST(AsyncGuiThreadBooking, Booking_GetMinPrice) +UNIT_CLASS_TEST(AsyncGuiThreadBooking, Booking_GetBlockAvailability) { - string const kHotelId = "0"; // Internal hotel id for testing. + auto params = BlockParams::MakeDefault(); + params.m_hotelId = "0"; // Internal hotel id for testing. Api api; { - string price; + double price = std::numeric_limits<double>::max(); string currency; string hotelId; - api.GetMinPrice(kHotelId, "" /* default currency */, - [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) { - hotelId = id; - price = val; - currency = curr; - testing::Notify(); - }); + api.GetBlockAvailability(params, [&hotelId, &price, ¤cy](std::string const & id, + Blocks const & blocks) + { + hotelId = id; + price = blocks.m_totalMinPrice; + currency = blocks.m_currency; + testing::Notify(); + }); testing::Wait(); - TEST_EQUAL(hotelId, kHotelId, ()); - TEST(!price.empty(), ()); + TEST_EQUAL(hotelId, params.m_hotelId, ()); + TEST_NOT_EQUAL(price, std::numeric_limits<double>::max(), ()); TEST(!currency.empty(), ()); - TEST_EQUAL(currency, "USD", ()); + TEST_EQUAL(currency, "EUR", ()); } { - string price; + auto params = BlockParams::MakeDefault(); + params.m_hotelId = "0"; // Internal hotel id for testing. + params.m_currency = "RUB"; + double price = std::numeric_limits<double>::max(); string currency; string hotelId; - api.GetMinPrice(kHotelId, "RUB", [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) - { - hotelId = id; - price = val; - currency = curr; - testing::Notify(); - }); + api.GetBlockAvailability(params, [&hotelId, &price, ¤cy](std::string const & id, + Blocks const & blocks) + { + hotelId = id; + price = blocks.m_totalMinPrice; + currency = blocks.m_currency; + testing::Notify(); + }); testing::Wait(); - TEST_EQUAL(hotelId, kHotelId, ()); - TEST(!price.empty(), ()); + TEST_EQUAL(hotelId, params.m_hotelId, ()); + TEST_NOT_EQUAL(price, std::numeric_limits<double>::max(), ()); TEST(!currency.empty(), ()); TEST_EQUAL(currency, "RUB", ()); } - - { - string price; - string currency; - string hotelId; - api.GetMinPrice(kHotelId, "ISK", [&hotelId, &price, ¤cy](string const & id, string const & val, string const & curr) - { - hotelId = id; - price = val; - currency = curr; - testing::Notify(); - }); - testing::Wait(); - - TEST_EQUAL(hotelId, kHotelId, ()); - TEST(!price.empty(), ()); - TEST(!currency.empty(), ()); - TEST_EQUAL(currency, "ISK", ()); - } } UNIT_CLASS_TEST(AsyncGuiThread, GetHotelInfo) |