#include #include #include #include #include #include #include #if (__cpp_lib_chrono < 201907L) # include #endif namespace scwx::provider { static const std::string logPrefix_ = "scwx::provider::iem_api_provider"; static const auto logger_ = util::Logger::Create(logPrefix_); static const std::string kBaseUrl_ = "https://mesonet.agron.iastate.edu/api/1"; static const std::string kListNwsTextProductsEndpoint_ = "/nws/afos/list.json"; static const std::string kNwsTextProductEndpoint_ = "/nwstext/"; class IemApiProvider::Impl { public: explicit Impl() = default; ~Impl() = default; Impl(const Impl&) = delete; Impl& operator=(const Impl&) = delete; Impl(const Impl&&) = delete; Impl& operator=(const Impl&&) = delete; }; IemApiProvider::IemApiProvider() : p(std::make_unique()) {} IemApiProvider::~IemApiProvider() = default; IemApiProvider::IemApiProvider(IemApiProvider&&) noexcept = default; IemApiProvider& IemApiProvider::operator=(IemApiProvider&&) noexcept = default; boost::outcome_v2::result> IemApiProvider::ListTextProducts(std::chrono::sys_time date, std::optional cccc, std::optional pil) { using namespace std::chrono; #if (__cpp_lib_chrono >= 201907L) namespace df = std; static constexpr std::string_view kDateFormat {"{:%Y-%m-%d}"}; #else using namespace date; namespace df = date; # define kDateFormat "%Y-%m-%d" #endif auto parameters = cpr::Parameters {{"date", df::format(kDateFormat, date)}}; // WMO Source Code if (cccc.has_value()) { parameters.Add({"cccc", std::string {cccc.value()}}); } // AFOS / AWIPS ID / 3-6 length identifier if (pil.has_value()) { parameters.Add({"pil", std::string {pil.value()}}); } auto response = cpr::Get(cpr::Url {kBaseUrl_ + kListNwsTextProductsEndpoint_}, network::cpr::GetHeader(), parameters); boost::json::value json = util::json::ReadJsonString(response.text); std::vector textProducts {}; if (response.status_code == cpr::status::HTTP_OK) { try { // Get AFOS list from response auto entries = boost::json::value_to(json); for (auto& entry : entries.data_) { textProducts.push_back(entry.productId_); } logger_->trace("Found {} products", entries.data_.size()); } catch (const std::exception& ex) { // Unexpected bad response logger_->warn("Error parsing JSON: {}", ex.what()); return boost::system::errc::make_error_code( boost::system::errc::bad_message); } } else if (response.status_code == cpr::status::HTTP_BAD_REQUEST && json != nullptr) { try { // Log bad request details auto badRequest = boost::json::value_to(json); logger_->warn("ListTextProducts bad request: {}", badRequest.detail_); } catch (const std::exception& ex) { // Unexpected bad response logger_->warn("Error parsing bad response: {}", ex.what()); } return boost::system::errc::make_error_code( boost::system::errc::invalid_argument); } else if (response.status_code == cpr::status::HTTP_UNPROCESSABLE_ENTITY && json != nullptr) { try { // Log validation error details auto error = boost::json::value_to(json); logger_->warn("ListTextProducts validation error: {}", error.detail_.at(0).msg_); } catch (const std::exception& ex) { // Unexpected bad response logger_->warn("Error parsing validation error: {}", ex.what()); } return boost::system::errc::make_error_code( boost::system::errc::no_message_available); } else { logger_->warn("Could not list text products: {}", response.status_line); return boost::system::errc::make_error_code( boost::system::errc::no_message); } return textProducts; } std::vector> IemApiProvider::LoadTextProducts(const std::vector& textProducts) { auto parameters = cpr::Parameters {{"nolimit", "true"}}; std::vector> asyncResponses {}; asyncResponses.reserve(textProducts.size()); const std::string endpointUrl = kBaseUrl_ + kNwsTextProductEndpoint_; for (auto& productId : textProducts) { asyncResponses.emplace_back( productId, cpr::GetAsync(cpr::Url {endpointUrl + productId}, network::cpr::GetHeader(), parameters)); } std::vector> textProductFiles; for (auto& asyncResponse : asyncResponses) { auto response = asyncResponse.second.get(); if (response.status_code == cpr::status::HTTP_OK) { // Load file auto& productId = asyncResponse.first; std::shared_ptr textProductFile { std::make_shared()}; std::istringstream responseBody {response.text}; if (textProductFile->LoadData(productId, responseBody)) { textProductFiles.push_back(textProductFile); } } else { logger_->warn("Could not load text product: {} ({})", asyncResponse.first, response.status_line); } } return textProductFiles; } } // namespace scwx::provider