summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
blob: 686520fb46ea7be737e1a18a764c4c711625638a (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
//! Rust client for the public CEP API at [cep.is](https://cep.is). Official
//! Correios data (DNE), refreshed every two weeks.
//!
//! Quick use — for a script, the free functions are enough:
//!
//! ```no_run
//! let address = cepis::lookup("01001-000")?;
//! println!("{} {}", address.localidade, address.uf); // São Paulo SP
//! # Ok::<_, cepis::Error>(())
//! ```
//!
//! In an application — build a [`Client`] once and reuse it:
//!
//! ```no_run
//! use std::time::Duration;
//!
//! let client = cepis::Client::builder()
//!     .timeout(Duration::from_secs(5))
//!     .total_timeout(Some(Duration::from_secs(15)))
//!     .build();
//!
//! let address = client.lookup("01001000")?;
//! # Ok::<_, cepis::Error>(())
//! ```
//!
//! Pointing at a local instance (the API repo brings one up with `just up`):
//!
//! ```no_run
//! let client = cepis::Client::builder()
//!     .base_url("http://localhost:8080")
//!     .build();
//! # let _ = client;
//! ```
//!
//! Errors are one type, split by decision — whether retrying helps or not:
//!
//! ```no_run
//! # let cep_from_form = "01001000";
//! match cepis::lookup(cep_from_form) {
//!     Ok(address) => println!("{}", address.localidade),
//!     Err(cepis::Error::NotFound { .. }) => {} // ask the user for another CEP
//!     Err(err) if err.is_retriable() => {}     // network, server, rate limit
//!     Err(err) => eprintln!("{err}"),
//! }
//! ```
//!
//! A CEP that does not exist is rarely a random number — usually someone knew
//! the city and guessed the digits. So [`Error::NotFound`] carries the
//! municipality that CEP would have belonged to, when the API can name one:
//!
//! ```no_run
//! # let cep_from_form = "25845000";
//! if let Err(err @ cepis::Error::NotFound { .. }) = cepis::lookup(cep_from_form) {
//!     match err.faixa() {
//!         // "no such CEP in Areal/RJ" — still not found, just better phrased
//!         Some(faixa) => println!("no such CEP in {}", faixa.municipality()),
//!         None => println!("no such CEP"),
//!     }
//! }
//! ```
//!
//! Note on field names: the API surface is English, but the **data fields keep
//! the API's own Portuguese names** (`logradouro`, `bairro`, `localidade`).
//! What you read in the JSON is what you access on the value.
//!
//! The free functions share one default client, built on first use. It keeps no
//! state between calls and is safe to share across threads. Anyone needing
//! different settings should build their own [`Client`].

#![forbid(unsafe_code)]
#![warn(missing_docs)]

mod client;
mod csv;
mod error;
mod model;
mod retry;

pub use crate::client::{
    Client, ClientBuilder, DEFAULT_URL, MAX_BATCH, RETRIABLE_STATUS, normalize_cep,
};
pub use crate::error::{Error, Result};
pub use crate::model::{Address, Faixa, Geo, Health};

/// The version of this library, as sent in the `User-Agent`.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

use std::sync::OnceLock;

fn default_client() -> &'static Client {
    static DEFAULT: OnceLock<Client> = OnceLock::new();
    DEFAULT.get_or_init(Client::new)
}

/// Look up a CEP using the default configuration.
///
/// Shorthand for `Client::new().lookup(cep)`; see [`Client::lookup`].
pub fn lookup(cep: &str) -> Result<Address> {
    default_client().lookup(cep)
}

/// Look up many CEPs in one batch, using the default configuration.
///
/// Shorthand for [`Client::lookup_many`] — read that one, especially the
/// warning that batches carry no coordinate.
pub fn lookup_many(ceps: &[impl AsRef<str>]) -> Result<Vec<Option<Address>>> {
    default_client().lookup_many(ceps)
}

/// Query the API's health using the default configuration.
///
/// Shorthand for [`Client::health`].
pub fn health() -> Result<Health> {
    default_client().health()
}