summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
blob: 84262d7b3c3f11d37e818a2fe8d53f2236aaa4dd (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
//! 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}"),
//! }
//! ```
//!
//! 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, 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()
}