mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-11-03 19:10:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			185 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
#include <scwx/provider/iem_api_provider.ipp>
 | 
						|
#include <scwx/util/json.hpp>
 | 
						|
#include <scwx/util/logger.hpp>
 | 
						|
 | 
						|
#include <boost/json.hpp>
 | 
						|
#include <cpr/cpr.h>
 | 
						|
#include <range/v3/iterator/operations.hpp>
 | 
						|
#include <range/v3/range/conversion.hpp>
 | 
						|
#include <range/v3/view/cartesian_product.hpp>
 | 
						|
#include <range/v3/view/single.hpp>
 | 
						|
 | 
						|
#if (__cpp_lib_chrono < 201907L)
 | 
						|
#   include <date/date.h>
 | 
						|
#endif
 | 
						|
 | 
						|
namespace scwx::provider
 | 
						|
{
 | 
						|
 | 
						|
static const std::string logPrefix_ = "scwx::provider::iem_api_provider";
 | 
						|
 | 
						|
const std::shared_ptr<spdlog::logger> IemApiProvider::logger_ =
 | 
						|
   util::Logger::Create(logPrefix_);
 | 
						|
 | 
						|
const std::string IemApiProvider::kBaseUrl_ =
 | 
						|
   "https://mesonet.agron.iastate.edu/api/1";
 | 
						|
 | 
						|
const std::string IemApiProvider::kListNwsTextProductsEndpoint_ =
 | 
						|
   "/nws/afos/list.json";
 | 
						|
const std::string IemApiProvider::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<Impl>()) {}
 | 
						|
IemApiProvider::~IemApiProvider() = default;
 | 
						|
 | 
						|
IemApiProvider::IemApiProvider(IemApiProvider&&) noexcept            = default;
 | 
						|
IemApiProvider& IemApiProvider::operator=(IemApiProvider&&) noexcept = default;
 | 
						|
 | 
						|
boost::outcome_v2::result<std::vector<types::iem::AfosEntry>>
 | 
						|
IemApiProvider::ListTextProducts(std::chrono::sys_days           date,
 | 
						|
                                 std::optional<std::string_view> optionalCccc,
 | 
						|
                                 std::optional<std::string_view> optionalPil)
 | 
						|
{
 | 
						|
   const std::string_view cccc =
 | 
						|
      optionalCccc.has_value() ? optionalCccc.value() : std::string_view {};
 | 
						|
   const std::string_view pil =
 | 
						|
      optionalPil.has_value() ? optionalPil.value() : std::string_view {};
 | 
						|
 | 
						|
   const auto dateArray = std::array {date};
 | 
						|
   const auto ccccArray = std::array {cccc};
 | 
						|
   const auto pilArray  = std::array {pil};
 | 
						|
 | 
						|
   return ListTextProducts(dateArray, ccccArray, pilArray);
 | 
						|
}
 | 
						|
 | 
						|
boost::outcome_v2::result<std::vector<types::iem::AfosEntry>>
 | 
						|
IemApiProvider::ProcessTextProductLists(
 | 
						|
   std::vector<cpr::AsyncResponse>& asyncResponses)
 | 
						|
{
 | 
						|
   std::vector<types::iem::AfosEntry> textProducts {};
 | 
						|
 | 
						|
   for (auto& asyncResponse : asyncResponses)
 | 
						|
   {
 | 
						|
      auto response = asyncResponse.get();
 | 
						|
 | 
						|
      const boost::json::value json = util::json::ReadJsonString(response.text);
 | 
						|
 | 
						|
      if (response.status_code == cpr::status::HTTP_OK)
 | 
						|
      {
 | 
						|
         try
 | 
						|
         {
 | 
						|
            // Get AFOS list from response
 | 
						|
            auto entries = boost::json::value_to<types::iem::AfosList>(json);
 | 
						|
            textProducts.insert(textProducts.end(),
 | 
						|
                                std::make_move_iterator(entries.data_.begin()),
 | 
						|
                                std::make_move_iterator(entries.data_.end()));
 | 
						|
         }
 | 
						|
         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<types::iem::BadRequest>(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<types::iem::ValidationError>(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);
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   logger_->debug("Found {} products", textProducts.size());
 | 
						|
 | 
						|
   return textProducts;
 | 
						|
}
 | 
						|
 | 
						|
std::vector<std::shared_ptr<awips::TextProductFile>>
 | 
						|
IemApiProvider::ProcessTextProductFiles(
 | 
						|
   std::vector<std::pair<std::string, cpr::AsyncResponse>>& asyncResponses)
 | 
						|
{
 | 
						|
   std::vector<std::shared_ptr<awips::TextProductFile>> textProductFiles;
 | 
						|
 | 
						|
   for (auto& asyncResponse : asyncResponses)
 | 
						|
   {
 | 
						|
      auto response = asyncResponse.second.get();
 | 
						|
 | 
						|
      if (response.status_code == cpr::status::HTTP_OK)
 | 
						|
      {
 | 
						|
         // Load file
 | 
						|
         auto& productId = asyncResponse.first;
 | 
						|
         const std::shared_ptr<awips::TextProductFile> textProductFile {
 | 
						|
            std::make_shared<awips::TextProductFile>()};
 | 
						|
         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);
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   logger_->debug("Loaded {} text products", textProductFiles.size());
 | 
						|
 | 
						|
   return textProductFiles;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace scwx::provider
 |