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}