fancy_ip/
lib.rs

1//! Adds a fancy way to generate IP addresses and socket addresses from its
2//! string representation
3//!
4//! This library aims to replace the use of `parse()` or `new()` functions for
5//! initializing an IP address using a macro call. This approach allows the
6//! emission of compile-time errors when an address is malformed and the use of
7//! human-readable addresses in const contexts.
8//!
9//! # Using in `#[no_std]` contexts
10//!
11//! This library can be used in `#[no_std]` contexts by using the `core`
12//! implementation of addresses instead of the `std` implementation.
13//!
14//! > ⚠️ Address in `core` is currently an unstable feature.
15//! >
16//! > In order to use this feature, you must use the nightly toolchain and
17//! > enable the `ip_in_core` in `main.rs` or `lib.rs` as is:
18//! > ```ignore
19//! > #![feature(ip_in_core)]
20//! > ```
21//! >
22//! > No external IP address provider is planned to be supported. If you want to
23//! > use `fancy-ip` in `#[no_std]` context with the stable or beta toolchain:
24//! > be patient.
25//!
26//! In order to use fancy-ip in `no_std` contexts, you must add this library in
27//! your `Cargo.toml` disabling the default features:
28//! ```toml
29//! fancy-ip = { version = "0.1", default_features = false }
30//! ```
31
32#![crate_type = "proc-macro"]
33extern crate proc_macro;
34
35mod arg_parser;
36
37use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
38use std::str::FromStr;
39
40use proc_macro::{Span, TokenStream};
41
42use arg_parser::ArgParser;
43use proc_macro_error::{abort, proc_macro_error};
44
45#[cfg(feature = "std")]
46const OBJECT_PREFIX: &'static str = "std::net";
47#[cfg(not(feature = "std"))]
48const OBJECT_PREFIX: &'static str = "core::net";
49
50fn generate_ipv4_stream(addr: &Ipv4Addr) -> TokenStream {
51    let [a, b, c, d] = addr.octets();
52
53    format!("{OBJECT_PREFIX}::Ipv4Addr::new({a}, {b}, {c}, {d})")
54        .parse()
55        .unwrap()
56}
57
58fn generate_ipv4_socket_stream(socket: &SocketAddrV4) -> TokenStream {
59    let addr = socket.ip();
60    let port = socket.port();
61
62    let ip_stream = generate_ipv4_stream(addr);
63
64    format!("{OBJECT_PREFIX}::SocketAddrV4::new({ip_stream},{port})")
65        .parse()
66        .unwrap()
67}
68
69fn generate_ipv6_stream(addr: &Ipv6Addr) -> TokenStream {
70    let [a, b, c, d, e, f, g, h] = addr.segments();
71
72    format!("{OBJECT_PREFIX}::Ipv6Addr::new({a}, {b}, {c}, {d}, {e}, {f}, {g}, {h})")
73        .parse()
74        .unwrap()
75}
76
77fn generate_ipv6_socket_stream(socket: &SocketAddrV6) -> TokenStream {
78    let addr = socket.ip();
79    let port = socket.port();
80    let flow_info = socket.flowinfo();
81    let scope_id = socket.scope_id();
82
83    let ip_stream = generate_ipv6_stream(addr);
84
85    format!("{OBJECT_PREFIX}::SocketAddrV6::new({ip_stream},{port},{flow_info},{scope_id})")
86        .parse()
87        .unwrap()
88}
89
90fn generate_ip_stream(addr: &IpAddr) -> TokenStream {
91    match addr {
92        IpAddr::V4(ip) => {
93            let ip_stream = generate_ipv4_stream(ip);
94
95            format!("{OBJECT_PREFIX}::IpAddr::V4({ip_stream})")
96                .parse()
97                .unwrap()
98        }
99        IpAddr::V6(ip) => {
100            let ip_stream = generate_ipv6_stream(ip);
101
102            format!("{OBJECT_PREFIX}::IpAddr::V6({ip_stream})")
103                .parse()
104                .unwrap()
105        }
106    }
107}
108
109fn generate_ip_socket_stream(socket: &SocketAddr) -> TokenStream {
110    match socket {
111        SocketAddr::V4(socket) => {
112            let socket_stream = generate_ipv4_socket_stream(socket);
113
114            format!("{OBJECT_PREFIX}::SocketAddr::V4({socket_stream})")
115                .parse()
116                .unwrap()
117        }
118        SocketAddr::V6(socket) => {
119            let socket_stream = generate_ipv6_socket_stream(socket);
120
121            format!("{OBJECT_PREFIX}::SocketAddr::V6({socket_stream})")
122                .parse()
123                .unwrap()
124        }
125    }
126}
127
128fn report_error<T>(value: Result<T, arg_parser::Error>) -> T {
129    match value {
130        Ok(v) => v,
131        Err(e) => {
132            abort!(e.span(), "{}", e);
133        }
134    }
135}
136
137fn report_too_few_arguments_error(given: usize, expected: usize) -> ! {
138    abort!(
139        Span::call_site(),
140        "Too few argument: Given {}, expected {}",
141        given,
142        expected
143    );
144}
145
146fn report_too_many_arguments_error(span: Span, given: usize, expected: usize) -> ! {
147    abort!(
148        span,
149        "Too many arguments: Given {}, expected {}",
150        given,
151        expected
152    );
153}
154
155/// Generate an IPv4 address from the standard textual representation
156///
157/// # Syntax
158///
159/// This macro works as a function which take only one argument: the string
160/// representation of an IP address
161///
162/// # Example
163///
164/// ```
165/// # use fancy_ip::ipv4;
166///
167/// assert_eq!(ipv4!("192.168.1.5"), std::net::Ipv4Addr::new(192, 168, 1, 5));
168/// ```
169#[proc_macro_error]
170#[proc_macro]
171pub fn ipv4(item: TokenStream) -> TokenStream {
172    let mut parser = ArgParser::from(item);
173
174    let ip = if let Some((v, span)) = report_error(parser.next_string()) {
175        match Ipv4Addr::from_str(v.as_str()) {
176            Ok(v) => v,
177            Err(_) => {
178                abort!(
179                    span,
180                    "The given address `{}` is not a valid IPv4 address",
181                    v
182                );
183            }
184        }
185    } else {
186        report_too_few_arguments_error(0, 1);
187    };
188
189    if let Some(span) = report_error(parser.ignore_next()) {
190        report_too_many_arguments_error(span, parser.count_arguments(), 1);
191    }
192
193    generate_ipv4_stream(&ip)
194}
195
196/// Generate an IPv6 address from the standard textual representation
197///
198/// # Syntax
199///
200/// This macro works as a function which take only one argument: the string
201/// representation of an IP address
202///
203/// # Example
204///
205/// ```
206/// # use fancy_ip::ipv6;
207///
208/// assert_eq!(ipv6!("::1"), std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
209/// ```
210#[proc_macro_error]
211#[proc_macro]
212pub fn ipv6(item: TokenStream) -> TokenStream {
213    let mut parser = ArgParser::from(item);
214
215    let ip = if let Some((v, span)) = report_error(parser.next_string()) {
216        match Ipv6Addr::from_str(v.as_str()) {
217            Ok(v) => v,
218            Err(_) => {
219                abort!(
220                    span,
221                    "The given address `{}` is not a valid IPv6 address",
222                    v
223                );
224            }
225        }
226    } else {
227        report_too_few_arguments_error(0, 1);
228    };
229
230    if let Some(span) = report_error(parser.ignore_next()) {
231        report_too_many_arguments_error(span, parser.count_arguments(), 1);
232    }
233
234    generate_ipv6_stream(&ip)
235}
236
237/// Generate an IP address from the standard textual representation (both
238/// support IPv4 and IPv6)
239///
240/// # Syntax
241///
242/// This macro works as a function which take only one argument: the string
243/// representation of an IP address
244///
245/// # Example
246///
247/// ```
248/// # use fancy_ip::ip;
249///
250/// assert_eq!(ip!("::1"), std::net::IpAddr::V6(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
251/// assert_eq!(ip!("192.168.1.5"), std::net::IpAddr::V4(std::net::Ipv4Addr::new(192, 168, 1, 5)));
252/// ```
253#[proc_macro_error]
254#[proc_macro]
255pub fn ip(item: TokenStream) -> TokenStream {
256    let mut parser = ArgParser::from(item);
257
258    let ip = if let Some((v, span)) = report_error(parser.next_string()) {
259        match IpAddr::from_str(v.as_str()) {
260            Ok(v) => v,
261            Err(_) => {
262                abort!(span, "The given address `{}` is not a valid IP address", v);
263            }
264        }
265    } else {
266        report_too_few_arguments_error(0, 1);
267    };
268
269    if let Some(span) = report_error(parser.ignore_next()) {
270        report_too_many_arguments_error(span, parser.count_arguments(), 1);
271    }
272
273    generate_ip_stream(&ip)
274}
275
276/// Generates a socket address from its string representation
277///
278/// # Syntax
279///
280/// This macro works as a function which take only one argument: the string
281/// representation of a socket address
282///
283/// # Example
284///
285/// ```
286/// # use fancy_ip::socketv4;
287///
288/// assert_eq!(socketv4!("192.168.1.5:3000"), std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(192, 168, 1, 5), 3000));
289/// ```
290#[proc_macro_error]
291#[proc_macro]
292pub fn socketv4(item: TokenStream) -> TokenStream {
293    let mut parser = ArgParser::from(item);
294
295    let socket = if let Some((v, span)) = report_error(parser.next_string()) {
296        match SocketAddrV4::from_str(v.as_str()) {
297            Ok(v) => v,
298            Err(_) => {
299                abort!(
300                    span,
301                    "The given address `{}` is not a valid IPv4 socket address",
302                    v
303                );
304            }
305        }
306    } else {
307        report_too_few_arguments_error(0, 1);
308    };
309
310    if let Some(span) = report_error(parser.ignore_next()) {
311        report_too_many_arguments_error(span, parser.count_arguments(), 1);
312    }
313
314    generate_ipv4_socket_stream(&socket)
315}
316
317/// Generates a socket address from its string representation
318///
319/// # Syntax
320///
321/// This macro works as a function which take only one argument: the string
322/// representation of a socket address
323///
324/// # Example
325///
326/// ```
327/// # use fancy_ip::socketv6;
328///
329/// assert_eq!(socketv6!("[::1]:3000"), std::net::SocketAddrV6::new(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 3000, 0, 0));
330/// assert_eq!(socketv6!("[::]:8080", 58, 30), std::net::SocketAddrV6::new(std::net::Ipv6Addr::UNSPECIFIED, 8080, 58, 30));
331/// ```
332#[proc_macro_error]
333#[proc_macro]
334pub fn socketv6(item: TokenStream) -> TokenStream {
335    let mut parser = ArgParser::from(item);
336
337    let mut socket = if let Some((v, span)) = report_error(parser.next_string()) {
338        match SocketAddrV6::from_str(v.as_str()) {
339            Ok(v) => v,
340            Err(_) => {
341                abort!(
342                    span,
343                    "The given address `{}` is not a valid IPv6 socket address",
344                    v
345                );
346            }
347        }
348    } else {
349        report_too_few_arguments_error(0, 1);
350    };
351
352    if let Some((flow_info, _)) = report_error(parser.next_integer()) {
353        socket.set_flowinfo(flow_info);
354    }
355
356    if let Some((scope_id, _)) = report_error(parser.next_integer()) {
357        socket.set_scope_id(scope_id)
358    }
359
360    if let Some(span) = report_error(parser.ignore_next()) {
361        report_too_many_arguments_error(span, parser.count_arguments(), 3);
362    }
363
364    generate_ipv6_socket_stream(&socket)
365}
366
367/// Generates a socket address from its string representation
368///
369/// # Syntax
370///
371/// This macro works as a function which take only one argument: the string
372/// representation of a socket address
373///
374/// # Example
375///
376/// ```
377/// # use fancy_ip::socket;
378///
379/// assert_eq!(socket!("[::1]:3000"), std::net::SocketAddr::V6(std::net::SocketAddrV6::new(std::net::Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 3000, 0, 0)));
380/// assert_eq!(socket!("192.168.1.5:3000"), std::net::SocketAddr::V4(std::net::SocketAddrV4::new(std::net::Ipv4Addr::new(192, 168, 1, 5), 3000)));
381/// ```
382#[proc_macro_error]
383#[proc_macro]
384pub fn socket(item: TokenStream) -> TokenStream {
385    let mut parser = ArgParser::from(item);
386
387    let socket = if let Some((v, span)) = report_error(parser.next_string()) {
388        match SocketAddr::from_str(v.as_str()) {
389            Ok(v) => v,
390            Err(_) => {
391                abort!(
392                    span,
393                    "The given address `{}` is not a valid socket address",
394                    v
395                );
396            }
397        }
398    } else {
399        report_too_few_arguments_error(0, 1);
400    };
401
402    if let Some(span) = report_error(parser.ignore_next()) {
403        report_too_many_arguments_error(span, parser.count_arguments(), 1);
404    }
405
406    generate_ip_socket_stream(&socket)
407}