1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::net::IpAddr;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
8pub enum IpKind {
9 V4,
11 V6,
13 Unknown,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct IpParts {
20 pub input: String,
22 pub kind: IpKind,
24}
25
26fn parse_ip(input: &str) -> Option<IpAddr> {
27 let trimmed = input.trim();
28
29 if trimmed.is_empty() {
30 return None;
31 }
32
33 trimmed.parse().ok()
34}
35
36pub fn is_ip(input: &str) -> bool {
38 parse_ip(input).is_some()
39}
40
41pub fn is_ipv4(input: &str) -> bool {
43 matches!(parse_ip(input), Some(IpAddr::V4(_)))
44}
45
46pub fn is_ipv6(input: &str) -> bool {
48 matches!(parse_ip(input), Some(IpAddr::V6(_)))
49}
50
51pub fn detect_ip_kind(input: &str) -> IpKind {
53 match parse_ip(input) {
54 Some(IpAddr::V4(_)) => IpKind::V4,
55 Some(IpAddr::V6(_)) => IpKind::V6,
56 None => IpKind::Unknown,
57 }
58}
59
60pub fn normalize_ip(input: &str) -> Option<String> {
62 parse_ip(input).map(|address| address.to_string())
63}
64
65pub fn is_loopback_ip(input: &str) -> bool {
67 parse_ip(input).is_some_and(|address| address.is_loopback())
68}
69
70pub fn is_unspecified_ip(input: &str) -> bool {
72 parse_ip(input).is_some_and(|address| address.is_unspecified())
73}
74
75pub fn is_private_ip(input: &str) -> bool {
77 match parse_ip(input) {
78 Some(IpAddr::V4(address)) => address.is_private(),
79 Some(IpAddr::V6(address)) => address.is_unique_local(),
80 None => false,
81 }
82}
83
84pub fn is_link_local_ip(input: &str) -> bool {
86 match parse_ip(input) {
87 Some(IpAddr::V4(address)) => address.is_link_local(),
88 Some(IpAddr::V6(address)) => address.is_unicast_link_local(),
89 None => false,
90 }
91}
92
93pub fn is_multicast_ip(input: &str) -> bool {
95 parse_ip(input).is_some_and(|address| address.is_multicast())
96}