const_addrs/
lib.rs

1//! A set of macros for creating networking types from a string literal.
2//!
3//! ```rust
4//! use std::net::{Ipv4Addr, IpAddr};
5//! use const_addrs::ip;
6//!
7//! # fn main() {
8//! let a = ip!("192.168.1.1");
9//! let b = IpAddr::V4(Ipv4Addr::new(192,168,1,1));
10//! assert_eq!(a, b);
11//!  # }
12//! ```
13//!
14//! Turning invalid strings into compile-time errors:
15//! ```text
16//! error: invalid IPv4 address syntax
17//!   --> bad.rs:10:18
18//!    |
19//! 10 |     let a = ip4!("192.1681.1");
20//!    |                  ^^^^^^^^^^^^
21//! ```
22//!
23//! These macros will parse the string passed to them using its type's [`FromStr`] implementation.
24//! See the documentation for each type for formatting details. The macro generated code will use
25//! the `const` constructor(s) for the types, adding no runtime overhead.
26//!
27//! For example:
28//! ```rust
29//! # use std::net::SocketAddr;
30//! # use const_addrs::sock;
31//! #
32//! # fn main() {
33//! let val = sock!("192.168.1.1:500");
34//! # // copied from below for doctest verification
35//! # let val2 = ::std::net::SocketAddr::V4(::std::net::SocketAddrV4::new(
36//! #     ::std::net::Ipv4Addr::new(192u8, 168u8, 1u8, 1u8),
37//! #     500u16,
38//! # ));
39//! # assert_eq!(val, val2)
40//! # }
41//! ```
42//! expands to:
43//! ```rust
44//! let val = ::std::net::SocketAddr::V4(::std::net::SocketAddrV4::new(
45//!     ::std::net::Ipv4Addr::new(192u8, 168u8, 1u8, 1u8),
46//!     500u16,
47//! ));
48//! ```
49#![cfg_attr(feature = "document-features", doc = "\n## Features")]
50#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
51#![cfg_attr(docsrs, feature(doc_cfg))]
52
53use proc_macro::TokenStream;
54
55use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
56use std::str::FromStr;
57
58use proc_macro_error::{abort_call_site, emit_error, proc_macro_error};
59use quote::quote;
60use syn::{parse, LitStr};
61
62// generate each proc_macro.
63//
64// the dummy value is used to make sure the emitted error is the only one generated when
65// type::from_str errors (e.g `let ip = ip!("foo");` expanding to `let ip = ; `
66//
67// the closure in `$body` will generate tokens to call the type's const constructor
68// which is used in the proc_macro itself.
69macro_rules! make_macro {
70    ($macro_name:ident: $ty:ty =? $dummy:expr; $generate_fn:ident => $body:expr; $tyname:expr; $feat:expr) => {
71        #[cfg_attr(docsrs, doc(cfg(feature = $feat)))]
72        #[doc = "generates [`"]
73        #[doc = $tyname]
74        #[doc = "`]"]
75        #[proc_macro]
76        #[proc_macro_error]
77        pub fn $macro_name(input: TokenStream) -> TokenStream {
78            let inval = match parse::<LitStr>(input) {
79                Ok(ls) => ls,
80                Err(e) => {
81                    abort_call_site!(
82                        e.to_string();
83                        help = "Use the string literal for this type's FromStr impl";
84                    );
85                }
86            };
87
88            // try to parse type
89            let val = match <$ty>::from_str(inval.value().as_str()) {
90                // if good, turn it into the right token stream
91                Ok(v) => v,
92                Err(e) => {
93                    // otherwise emit the parse error with the parser's error message
94                    // as well as make the compile error show the invalid string
95                    emit_error!(inval, e);
96                    // return dummy value of the right type to squash subsequent errors
97                    $dummy
98                }
99            };
100            $generate_fn(val).into()
101        }
102
103        fn $generate_fn(input: $ty) -> proc_macro2::TokenStream {
104            Some(input).map($body).unwrap()
105        }
106    };
107    ($macro_name:ident: $ty:ty =? $dummy:expr; $generate_fn:ident => $body:expr) => {
108        make_macro!($macro_name: $ty =? $dummy; $generate_fn => $body; stringify!($ty); "default");
109    };
110    ($macro_name:ident: $ty:ty =? $dummy:expr; $generate_fn:ident => $body:expr; $feat:expr) => {
111        make_macro!($macro_name: $ty =? $dummy; $generate_fn => $body; stringify!($ty); $feat);
112    }
113}
114
115// IP types
116
117make_macro! {
118    ip: IpAddr =? IpAddr::V4(Ipv4Addr::UNSPECIFIED);
119    ipaddr_tokens => |input| match input {
120        IpAddr::V4(ip) => {
121            let inner = ip4_tokens(ip);
122            quote! { ::std::net::IpAddr::V4(#inner) }
123        }
124        IpAddr::V6(ip) => {
125            let inner = ip6_tokens(ip);
126            quote! { ::std::net::IpAddr::V6(#inner) }
127        }
128    }
129}
130
131make_macro! {
132    ip4: Ipv4Addr =? Ipv4Addr::UNSPECIFIED;
133    ip4_tokens => |input| {
134        let octets = input.octets();
135        quote! { ::std::net::Ipv4Addr::new(#(#octets),*) }
136    }
137}
138
139make_macro! {
140    ip6: Ipv6Addr =? Ipv6Addr::UNSPECIFIED;
141    ip6_tokens => |input| {
142        let segments = input.segments();
143        quote! { ::std::net::Ipv6Addr::new(#(#segments),*) }
144    }
145}
146
147// SocketAddr types
148
149make_macro! {
150    sock: SocketAddr =? SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));
151    sockaddr_tokens => |addr| {
152        match addr {
153            SocketAddr::V4(sock) => {
154                let inner = sock4_tokens(sock);
155                quote! { ::std::net::SocketAddr::V4(#inner) }
156            }
157            SocketAddr::V6(sock) => {
158                let inner = sock6_tokens(sock);
159                quote! { ::std::net::SocketAddr::V6(#inner) }
160            }
161        }
162    }
163}
164
165make_macro! {
166    sock4: SocketAddrV4 =? SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0);
167    sock4_tokens => |input| {
168        let ip = ip4_tokens(*input.ip());
169        let port = input.port();
170        quote! { ::std::net::SocketAddrV4::new(#ip, #port) }
171    }
172}
173
174make_macro! {
175    sock6: SocketAddrV6 =? SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0);
176    sock6_tokens => |addr| {
177        let ip = ip6_tokens(*addr.ip());
178        let port = addr.port();
179        quote! { ::std::net::SocketAddrV6::new(#ip, #port, 0, 0) }
180    }
181}
182
183// IpNetwork types
184
185cfg_if::cfg_if! {
186    if #[cfg(feature = "ipnet")] {
187        use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
188
189        make_macro!{
190            net: IpNetwork =? IpNetwork::V4(Ipv4Network::new(Ipv4Addr::UNSPECIFIED, 0).unwrap());
191            net_tokens => |net| match net {
192                IpNetwork::V4(net) => {
193                    let inner = net4_tokens(net);
194                    quote! { ipnetwork::IpNetwork::V4(#inner) }
195                }
196                IpNetwork::V6(net) => {
197                    let inner = net6_tokens(net);
198                    quote! { ipnetwork::IpNetwork::V6(#inner) }
199                }
200            };
201            "ipnet"
202        }
203
204        make_macro!{
205            net4: Ipv4Network =? Ipv4Network::new(Ipv4Addr::UNSPECIFIED, 0).unwrap();
206            net4_tokens => |net| {
207                let ip = ip4_tokens(net.ip());
208                let prefix = net.prefix();
209                quote! { ipnetwork::Ipv4Network::new_checked(#ip, #prefix).unwrap() }
210            };
211            "ipnet"
212        }
213
214
215        make_macro!{
216            net6: Ipv6Network =? Ipv6Network::new(Ipv6Addr::UNSPECIFIED, 0).unwrap();
217            net6_tokens => |net| {
218                let ip = ip6_tokens(net.ip());
219                let prefix = net.prefix();
220                quote! { ipnetwork::Ipv6Network::new_checked(#ip, #prefix).unwrap() }
221            };
222            "ipnet"
223        }
224    }
225}
226
227// MacAddr types
228
229cfg_if::cfg_if! {
230    if #[cfg(feature = "mac")] {
231        use macaddr::{MacAddr, MacAddr6, MacAddr8};
232
233        make_macro!{
234            mac: MacAddr =? MacAddr::V6(MacAddr6::from([0x00; 6]));
235            mac_tokens => |addr| match addr {
236                MacAddr::V6(addr) => {
237                    let inner = mac6_tokens(addr);
238                    quote! { macaddr::MacAddr::V6(#inner) }
239                }
240                MacAddr::V8(addr) => {
241                    let inner = mac8_tokens(addr);
242                    quote! { macaddr::MacAddr::V8(#inner) }
243                }
244            };
245            "mac"
246        }
247
248        make_macro! {
249            mac6: MacAddr6 =? MacAddr6::from([0x00; 6]);
250            mac6_tokens => |addr| {
251                let bytes = addr.into_array();
252                quote! { macaddr::MacAddr6::new(#(#bytes),*) }
253            };
254            "mac"
255        }
256
257        make_macro!{
258            mac8: MacAddr8 =? MacAddr8::from([0x00; 8]);
259            mac8_tokens => |addr| {
260                let bytes = addr.into_array();
261                quote! { macaddr::MacAddr8::new(#(#bytes),*) }
262            };
263            "mac"
264        }
265    }
266}
267
268#[cfg(test)]
269mod tests {
270    #[test]
271    fn compilation() {
272        let t = trybuild::TestCases::new();
273
274        t.compile_fail("tests/fail/ip.rs");
275        t.compile_fail("tests/fail/ip4.rs");
276        t.compile_fail("tests/fail/ip6.rs");
277        t.compile_fail("tests/fail/sock.rs");
278        t.compile_fail("tests/fail/sock4.rs");
279        t.compile_fail("tests/fail/sock6.rs");
280        t.compile_fail("tests/fail/net.rs");
281        t.compile_fail("tests/fail/net4.rs");
282        t.compile_fail("tests/fail/net6.rs");
283        t.compile_fail("tests/fail/mac.rs");
284        t.compile_fail("tests/fail/mac6.rs");
285        t.compile_fail("tests/fail/mac8.rs");
286
287        t.pass("tests/pass/ip.rs");
288        t.pass("tests/pass/ip4.rs");
289        t.pass("tests/pass/ip6.rs");
290        t.pass("tests/pass/sock.rs");
291        t.pass("tests/pass/sock4.rs");
292        t.pass("tests/pass/sock6.rs");
293        t.pass("tests/pass/net.rs");
294        t.pass("tests/pass/net4.rs");
295        t.pass("tests/pass/net6.rs");
296        t.pass("tests/pass/mac.rs");
297        t.pass("tests/pass/mac6.rs");
298        t.pass("tests/pass/mac8.rs");
299    }
300}