diff options
Diffstat (limited to 'include/cepis/models.hpp')
| -rw-r--r-- | include/cepis/models.hpp | 120 |
1 files changed, 120 insertions, 0 deletions
diff --git a/include/cepis/models.hpp b/include/cepis/models.hpp new file mode 100644 index 0000000..faa2dde --- /dev/null +++ b/include/cepis/models.hpp @@ -0,0 +1,120 @@ +#ifndef CEPIS_MODELS_HPP +#define CEPIS_MODELS_HPP + +#include <cstdint> +#include <optional> +#include <string> + +#include "cepis/json.hpp" + +/// The data the API returns, as values. +/// +/// **Field names are the API's own, in Portuguese** — `logradouro`, `bairro`, +/// `localidade`. Everything else in this library is English; the fields are the +/// one deliberate exception. +/// +/// The reason is fidelity: what you read in the JSON at https://cep.is/01001000 +/// is exactly what you access on the object. Translating to `street`/`city` +/// would mean maintaining a field-by-field mapping and forcing anyone reading +/// the API docs alongside this library to translate back in their head. The +/// values are Portuguese either way — they come from the Correios database. +namespace cepis { + +/// The CEP's coordinate, when the database has one. +/// +/// The point is the **median** of the coordinates of that CEP's addresses, not +/// the mean: one badly geocoded address 40 km away shifts a mean and does not +/// move a median. +/// +/// Always check `precisao` before using `lat`/`lon` for distance: +/// +/// - `"logradouro"`: the point is a stretch of street. `raio_m` lands around +/// 100–150 m. Good for maps and for distance. +/// - `"localidade"`: the CEP *is* the whole municipality (a city-wide general +/// CEP), and the point is its centroid. `raio_m` runs from 7 km to 40 km. +/// Computing shipping from this is wrong by tens of kilometres. +/// +/// `raio_m` is the radius containing 90% of that CEP's addresses. It exists +/// precisely so that error cannot pass unnoticed. +struct Geo { + double lat = 0.0; + double lon = 0.0; + std::int64_t raio_m = 0; + std::string precisao; + + /// `true` when the coordinate points at a street, not a municipality. + /// + /// Shorthand for `precisao == "logradouro"`, which is the check to make + /// before using the point to measure distance. + [[nodiscard]] bool is_precise() const noexcept { return precisao == "logradouro"; } +}; + +/// A CEP and everything the database knows about it. +/// +/// Fields the database does not have for that CEP come back as empty strings, +/// never absent — that is what the API returns, and changing it here would +/// create a difference between reading the JSON and reading this library. +/// +/// `geo` is the exception: it is `std::nullopt` when there is no coordinate. +/// There the difference is real, because the API **omits** the whole field in +/// that case (about 43% of CEPs) rather than sending null. +struct Address { + std::string cep; + std::string logradouro; + std::string complemento; + std::string unidade; + std::string bairro; + std::string localidade; + std::string uf; + std::string estado; + std::string regiao; + std::string ibge; + std::string gia; + std::string ddd; + std::string siafi; + std::optional<Geo> geo; + + /// The response exactly as it arrived, uninterpreted, so a field the API + /// grows before this library is updated is not lost. + Json raw; + + /// The CEP with its mask: `01001-000`. + /// + /// The native contract always returns it unmasked; formatting is + /// presentation, and it lives here so it does not become a repeated + /// concatenation in every project that uses this. + [[nodiscard]] std::string formatted_cep() const; + + /// Builds from the object the API returned. + /// + /// Unknown fields are ignored during construction but preserved in `raw`. A + /// new field in the API therefore cannot break this library. + /// + /// A malformed `geo` block yields an empty `geo` rather than propagating the + /// error: it is not worth losing the whole address over, since the rest of + /// the response is still useful. + static Address from_json(const Json& data); +}; + +/// The response of `GET /health`. +/// +/// Good for two things: checking that the API answers, and knowing **which +/// version of the database** is live. The DNE database changes every two +/// weeks; `dne` identifies it. +struct Health { + std::string status; + std::string dne; + std::int64_t ceps = 0; + std::int64_t geo = 0; + std::string base_gerada_em; + + /// `true` when the API reports itself healthy. + [[nodiscard]] bool ok() const noexcept { return status == "ok"; } + + /// Builds from the object the API returned. + static Health from_json(const Json& data); +}; + +} // namespace cepis + +#endif // CEPIS_MODELS_HPP |