vrl/stdlib/
is_ipv4.rs

1use crate::compiler::prelude::*;
2use std::net::Ipv4Addr;
3
4fn is_ipv4(value: &Value) -> Resolved {
5    let value_str = value.try_bytes_utf8_lossy()?;
6    Ok(value_str.parse::<Ipv4Addr>().is_ok().into())
7}
8
9#[derive(Clone, Copy, Debug)]
10pub struct IsIpv4;
11
12impl Function for IsIpv4 {
13    fn identifier(&self) -> &'static str {
14        "is_ipv4"
15    }
16
17    fn usage(&self) -> &'static str {
18        indoc! {"
19            Check if the string is a valid IPv4 address or not.
20
21            An [IPv4-mapped](https://datatracker.ietf.org/doc/html/rfc6890) or
22            [IPv4-compatible](https://datatracker.ietf.org/doc/html/rfc6890) IPv6 address is not considered
23            valid for the purpose of this function.
24        "}
25    }
26
27    fn category(&self) -> &'static str {
28        Category::Ip.as_ref()
29    }
30
31    fn return_kind(&self) -> u16 {
32        kind::BOOLEAN
33    }
34
35    fn return_rules(&self) -> &'static [&'static str] {
36        &[
37            "Returns `true` if `value` is a valid IPv4 address.",
38            "Returns `false` if `value` is anything else.",
39        ]
40    }
41
42    fn parameters(&self) -> &'static [Parameter] {
43        const PARAMETERS: &[Parameter] = &[Parameter::required(
44            "value",
45            kind::BYTES,
46            "The IP address to check",
47        )];
48        PARAMETERS
49    }
50
51    fn examples(&self) -> &'static [Example] {
52        &[
53            example! {
54                title: "Valid IPv4 address",
55                source: r#"is_ipv4("10.0.102.37")"#,
56                result: Ok("true"),
57            },
58            example! {
59                title: "Valid IPv6 address",
60                source: r#"is_ipv4("2001:0db8:85a3:0000:0000:8a2e:0370:7334")"#,
61                result: Ok("false"),
62            },
63            example! {
64                title: "Arbitrary string",
65                source: r#"is_ipv4("foobar")"#,
66                result: Ok("false"),
67            },
68        ]
69    }
70
71    fn compile(
72        &self,
73        _state: &TypeState,
74        _ctx: &mut FunctionCompileContext,
75        arguments: ArgumentList,
76    ) -> Compiled {
77        let value = arguments.required("value");
78
79        Ok(IsIpv4Fn { value }.as_expr())
80    }
81}
82
83#[derive(Clone, Debug)]
84struct IsIpv4Fn {
85    value: Box<dyn Expression>,
86}
87
88impl FunctionExpression for IsIpv4Fn {
89    fn resolve(&self, ctx: &mut Context) -> Resolved {
90        self.value.resolve(ctx).and_then(|v| is_ipv4(&v))
91    }
92
93    fn type_def(&self, _: &TypeState) -> TypeDef {
94        TypeDef::boolean().infallible()
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::value;
102
103    test_function![
104        is_ipv4 => IsIpv4;
105
106        not_string {
107            args: func_args![value: value!(42)],
108            want: Err("expected string, got integer"),
109            tdef: TypeDef::boolean().infallible(),
110        }
111
112        random_string {
113            args: func_args![value: value!("foobar")],
114            want: Ok(value!(false)),
115            tdef: TypeDef::boolean().infallible(),
116        }
117
118        ipv4_address_valid {
119            args: func_args![value: value!("1.1.1.1")],
120            want: Ok(value!(true)),
121            tdef: TypeDef::boolean().infallible(),
122        }
123
124        ipv4_address_invalid {
125            args: func_args![value: value!("1.1.1.314")],
126            want: Ok(value!(false)),
127            tdef: TypeDef::boolean().infallible(),
128        }
129
130        ipv6_address {
131            args: func_args![value: value!("2001:0db8:85a3:0000:0000:8a2e:0370:7334")],
132            want: Ok(value!(false)),
133            tdef: TypeDef::boolean().infallible(),
134        }
135    ];
136}