#include #include #include #include #include #include #include namespace scwx::provider { static const std::string logPrefix_ = "scwx::provider::iem_warnings_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 IemWarningsProvider::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; std::vector ListTextProducts(std::chrono::sys_time date, std::optional cccc = {}, std::optional pil = {}); }; IemWarningsProvider::IemWarningsProvider() : p(std::make_unique()) {} IemWarningsProvider::~IemWarningsProvider() = default; IemWarningsProvider::IemWarningsProvider(IemWarningsProvider&&) noexcept = default; IemWarningsProvider& IemWarningsProvider::operator=(IemWarningsProvider&&) noexcept = default; std::vector IemWarningsProvider::ListTextProducts( std::chrono::sys_time date, std::optional cccc, std::optional pil) { return p->ListTextProducts(date, cccc, pil); } std::vector IemWarningsProvider::Impl::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()); } } 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()); } } 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()); } } else { logger_->warn("Could not list text products: {}", response.status_line); } return textProducts; } } // namespace scwx::provider