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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
//! addrezz — generic, RFC-compliant address parsing with confident defaults.
//!
//!
//! # Usage
//!
//! ```toml
//! [dependencies]
//! addrezz = "0.1"
//! ```
//!
//! ## Parsing
//!
//! Parsing addresses and hosts with varying level of completeness is possible, with minimal
//! guesswork to keep track of under the hood.
//!
//! One assumption is feature-dependent, specifically, all URLs without specified schemes are
//! assumed to be `https://` by default. To change this behavior, add the `default_http` feature.
//!
//! ```toml
//! addrezz = { version = "0.1", features = ["default_http"]}
//! ```
//!
//! ```
//! use addrezz::{Addr, Scheme, Host};
//!
//! let full_url = Addr::parse("https://user@example.com:8443/v1/items?q=rust#top").unwrap();
//! assert_eq!(full_url.scheme, Scheme::Https);
//! assert_eq!(full_url.port, Some(8443));
//! assert_eq!(full_url.path, "/v1/items");
//!
//! let hostname = Addr::parse("example.com").unwrap();
//! assert_eq!(hostname.scheme, Scheme::Https);
//!
//!
//! // Git remote with SCP-style address
//! let scp_addr = Addr::parse("git@github.com:rust-lang/rust").unwrap();
//! assert_eq!(scp_addr.scheme, Scheme::Ssh);
//! assert_eq!(scp_addr.path, "/rust-lang/rust");
//!
//! let v4 = Addr::parse("http://127.0.0.1:8080/").unwrap();
//! assert!(matches!(v4.host, Host::Ipv4(_)));
//! let v6 = Addr::parse("https://[::1]:9200").unwrap();
//! assert!(matches!(v6.host, Host::Ipv6(_)));
//! ```
//!
//! ## Schemes and their IANA semantics
//!
//! ```
//! use addrezz::Addr;
//!
//! let pg = Addr::parse("postgres://db.internal/app").unwrap();
//! assert_eq!(pg.port, None); // No port was explicitly provided
//! assert_eq!(pg.effective_port(), Some(5432)); // Scheme-derived fallback
//!
//! let websock = Addr::parse("wss://gateway.example.com").unwrap();
//! assert!(websock.scheme.is_secure()); // Scheme-derived security properties
//! ```
//!
//! ## URL path and query parameters
//!
//! ```
//! use addrezz::Addr;
//!
//! let a = Addr::parse("https://example.com/foo/bar%20baz?key=val&a=b%20c").unwrap();
//!
//! let segments: Vec<_> = a.path_segments().collect();
//! assert_eq!(segments, vec!["foo", "bar baz"]); // percent-decoded path segments
//!
//! let qparams: Vec<_> = a.query_pairs().collect();
//! assert_eq!(qparams[0], ("key".into(), "val".into()));
//! ```
//!
//! ## Properties
//!
//! You can use [`Host::is_local`] to determine if the host is a local IPv4/IPv6 address, or if it
//! has a `.local`/`.localhost` extension.
//!
//! You can use [`Origin::same_origin`] to compare whether two addresses share an origin, useful for
//! web application security policies:
//!
//! ```
//! use addrezz::Addr;
//!
//! let a = Addr::parse("https://example.com/").unwrap();
//! let b = Addr::parse("https://example.com:443/other").unwrap();
//!
//! assert!(a.origin().same_origin(&b.origin()));
//! ```
//!
//! # Optional Features
//!
//! ## Feature flags
//!
//! | Feature | Default | What it adds |
//! |---|---|---|
//! | `default_http` | | bare hosts default to `http` (mutually exclusive with above) |
//! | `macros` | yes | the compile-time `addrezz!` / `addrezz_vec!` proc macros |
//! | `redact` | yes | `Debug` for `Userinfo` hides the password ([docs](docs/REDACT.md)) |
//! | `serde` | | `Serialize` / `Deserialize` on all public types |
//! | `url` | | conversions to/from `url::Url` |
//! | `http` | | conversions to `http::Uri`, `Authority`, `HeaderValue` |
//! | `reqwest` | | conversions to `reqwest::Url` (implies `http`) |
//! | `resolve` | | blocking DNS via `getaddrinfo` ([docs](docs/RESOLVE.md)) |
//! | `resolve_async` | | async DNS via `hickory-resolver` ([docs](docs/RESOLVE.md)) |
//! | `whois` | | whois lookups via `whoizz` ([docs](docs/WHOIS.md)) |
//! | `psl` | | public suffix / eTLD+1 via `psl` ([docs](docs/PSL.md)) |
//! | `ipnet` | | CIDR membership tests via `ipnet` ([docs](docs/IPNET.md)) |
//! | `arbitrary` | | `arbitrary::Arbitrary` for fuzzing ([docs](docs/ARBITRARY.md)) |
//! | `proptest` | | `proptest::Arbitrary` strategy ([docs](docs/PROPTEST.md)) |
//!
//!
//! ## `macros` feature
//!
//! Allows the use of both compile-time macros ([addrezz!] and [addrezz_vec!]), and runtime macros
//! ([addr!] and [try_addr!]).
//!
//! Compile-time macros ensure, that the [Addr] is correct on successful compilation, while runtime
//! macros allow different levels of validation at runtime, i.e. [addr!] panics on failure, while
//! [try_addr!] returns a result.
//!
//! Compile-time macros accept both string and token inputs (not quoted values), while runtime
//! macros accept not just quoted strings, but also non-literals (variables, references).
//!
//! **Note**: If the URL includes the scheme, only the quoted form will parse, as `scheme://` will
//! parse as a comment start before the macro could take effect.
//!
//! ```
//! use addrezz::{addrezz, addrezz_vec};
//!
//! let a = addrezz!("ssh://git@github.com/rust-lang/rust");
//! let b = addrezz! { git@github.com:rust-lang/rust };
//!
//! assert_eq!(a, b);
//!
//! let mirrors = addrezz_vec!(
//! "https://m1.example.com",
//! "https://m2.example.com",
//! "https://m3.example.com",
//! );
//! assert_eq!(mirrors.len(), 3);
//! ```
//!
//! ```compile_fail
//! use addrezz::addrezz;
//!
//! let fail = addrezz!("://fail_at_compile")
//! ```
//!
//! ```
//! use addrezz::{addr, try_addr};
//!
//! let a = addr!("https://example.com/health");
//! assert_eq!(a.scheme.as_str(), "https");
//!
//! let raw = std::env::var("ENDPOINT").unwrap_or_else(|_| "https://example.com".into());
//! let a = addr!(&raw);
//!
//! match try_addr!("not a url:::") {
//! Ok(url) => println!("parsed {url}"),
//! Err(e) => eprintln!("bad address: {e}"),
//! }
//! ```
//!
//!
//! ## `serde` feature
//!
//! Every public type derives `Serialize` / `Deserialize` behind the `serde`
//! feature. `Addr` serializes as a struct, so the components stay addressable in
//! the serialized form.
//!
//! ```no_run
//! use addrezz::Addr;
//!
//! let a = Addr::parse("https://user:secret@api.example.com:8443/v1?q=1").unwrap();
//! let json = serde_json::to_string_pretty(&a).unwrap();
//! // {
//! // "scheme": "Https",
//! // "userinfo": { "username": "user", "password": "secret" },
//! // "host": { "Domain": "api.example.com" },
//! // "port": 8443,
//! // "path": "/v1",
//! // "query": "q=1",
//! // "fragment": null
//! // }
//!
//! let back: Addr = serde_json::from_str(&json).unwrap();
//! assert_eq!(a, back);
//! ```
//!
//! ## `url` feature
//!
//! ```no_run
//! use addrezz::Addr;
//! use url::Url;
//!
//! let a = Addr::parse("https://example.com/a/b?c=d").unwrap();
//!
//! let u: Url = a.clone().try_into().unwrap();
//! assert_eq!(u.as_str(), "https://example.com/a/b?x=1");
//!
//! let back: Addr = (&u).try_into().unwrap();
//! assert_eq!(a, back);
//! ```
//!
//! ## `http` feature
//!
//! Allows conversion from [`Addr`] to [`http::Uri`], [`http::uri::Authority`],
//! [`http::header::HOST`]-header's [`http::HeaderValue`]:
//!
//! ```no_run
//! use addrezz::Addr;
//! use http::{Uri, HeaderValue, uri::Authority};
//!
//! let a = Addr::parse("https://user@example.com:8443/v1/items").unwrap();
//!
//! let uri: Uri = (&a).try_into().unwrap();
//! let auth: Authority = (&a).try_into().unwrap();
//!
//! // For the 'Host' header value, we only keep the port intact when it's specified explicitly and
//! // differs from the schema default. If it matches the default, we strip it.
//! let host: HeaderValue = (&a).try_into().unwrap();
//! assert_eq!(host.to_str().unwrap(), "example.com:8443");
//! ```
//!
//! ## `reqwest` feature
//!
//! Allows conversion of [`Addr`] to [`reqwest::Url`] (and also enables `http` feature).
//!
//! This feature is purely convenience to reduce direct dependencies, since [`reqwest::Url`] is a
//! re-export of [`url::Url`], so if direct deps only specify `reqwest`, this should be more
//! convenient to use.
//!
//! ```no_run
//! use addrezz::Addr;
//!
//! # async fn req() -> Result<(), reqwest::Error> {
//!
//! let a = Addr::parse("https://api.example.com/search?q=rust").unwrap();
//!
//! let url: reqwest::Url = a.try_into().unwrap();
//! let resp = reqwest::Client::new().get(url).send().await?;
//! # Ok(())
//! # }
//! ```
//!
//! ## `sqlx` feature
//!
//! [`Addr`] implements [`sqlx::Type`], [sqlx::Encode], [sqlx::Decode], and is stored as `TEXT` or
//! `VARCHAR`.
//!
//! The stored [String] is decoded back through [Addr::parse]. Any backend that stores type [String]
//! can handle [Addr].
//!
//! ```toml
//! addrezz = { version = "0.1", features = ["sqlx"] }
//! ```
//!
//! ```rust
//! use addrezz::Addr;
//!
//! # async fn run(pool: sqlx::PgPool) -> Result<(), sqlx::Error> {
//! let endpoint = Addr::parse("https://hooks.example.com/incoming").unwrap();
//!
//! sqlx::query("INSERT INTO webhooks (url) VALUES ($1)")
//! .bind(&endpoint)
//! .execute(&pool)
//! .await?;
//!
//! let row: (Addr,) = sqlx::query_as("SELECT url FROM webhooks LIMIT 1")
//! .fetch_one(&pool)
//! .await?;
//! assert_eq!(row.0.host.to_string(), "hooks.example.com");
//! # Ok(())
//! # }
//! ```
pub use ;
// Re-export the declarative macros from core.
pub use ;
pub use ;
/// Path the proc macros expand to. Not part of the public API.
pub use addrezz_core as __private;
pub use ;