summaryrefslogtreecommitdiffstats
path: root/include/cepis/models.hpp
blob: faa2ddecfcd55ac9416e9ee589f0b36d6bb2e220 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
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