use crate::compiler::prelude::*;
use std::net::IpAddr;
fn ipv6_to_ipv4(value: &Value) -> Resolved {
let ip = value
.try_bytes_utf8_lossy()?
.parse()
.map_err(|err| format!("unable to parse IP address: {err}"))?;
match ip {
IpAddr::V4(addr) => Ok(addr.to_string().into()),
IpAddr::V6(addr) => match addr.to_ipv4() {
Some(addr) => Ok(addr.to_string().into()),
None => Err(format!("IPV6 address {addr} is not compatible with IPV4").into()),
},
}
}
#[derive(Clone, Copy, Debug)]
pub struct Ipv6ToIpV4;
impl Function for Ipv6ToIpV4 {
fn identifier(&self) -> &'static str {
"ipv6_to_ipv4"
}
fn usage(&self) -> &'static str {
indoc! {"
Converts the `ip` to an IPv4 address. `ip` is returned unchanged if it's already an IPv4 address. If `ip` is
currently an IPv6 address then it needs to be IPv4 compatible, otherwise an error is thrown.
"}
}
fn category(&self) -> &'static str {
Category::Ip.as_ref()
}
fn internal_failure_reasons(&self) -> &'static [&'static str] {
&[
"`ip` is not a valid IP address.",
"`ip` is an IPv6 address that is not compatible with IPv4.",
]
}
fn return_kind(&self) -> u16 {
kind::BYTES
}
fn return_rules(&self) -> &'static [&'static str] {
&[
"The `ip` is returned unchanged if it's already an IPv4 address. If it's an IPv6 address it must be IPv4
compatible, otherwise an error is thrown.",
]
}
fn parameters(&self) -> &'static [Parameter] {
const PARAMETERS: &[Parameter] = &[Parameter::required(
"value",
kind::BYTES,
"The IPv4-mapped IPv6 address to convert.",
)];
PARAMETERS
}
fn examples(&self) -> &'static [Example] {
&[example! {
title: "IPv6 to IPv4",
source: r#"ipv6_to_ipv4!("::ffff:192.168.0.1")"#,
result: Ok("192.168.0.1"),
}]
}
fn compile(
&self,
_state: &state::TypeState,
_ctx: &mut FunctionCompileContext,
arguments: ArgumentList,
) -> Compiled {
let value = arguments.required("value");
Ok(Ipv6ToIpV4Fn { value }.as_expr())
}
}
#[derive(Debug, Clone)]
struct Ipv6ToIpV4Fn {
value: Box<dyn Expression>,
}
impl FunctionExpression for Ipv6ToIpV4Fn {
fn resolve(&self, ctx: &mut Context) -> Resolved {
let value = self.value.resolve(ctx)?;
ipv6_to_ipv4(&value)
}
fn type_def(&self, _: &state::TypeState) -> TypeDef {
TypeDef::bytes().fallible()
}
}
#[cfg(test)]
mod tests {
use super::*;
test_function![
ipv6_to_ipv4 => Ipv6ToIpV4;
error {
args: func_args![value: "i am not an ipaddress"],
want: Err("unable to parse IP address: invalid IP address syntax".to_string()),
tdef: TypeDef::bytes().fallible(),
}
incompatible {
args: func_args![value: "2001:0db8:85a3::8a2e:0370:7334"],
want: Err("IPV6 address 2001:db8:85a3::8a2e:370:7334 is not compatible with IPV4".to_string()),
tdef: TypeDef::bytes().fallible(),
}
ipv4_compatible {
args: func_args![value: "::ffff:192.168.0.1"],
want: Ok(Value::from("192.168.0.1")),
tdef: TypeDef::bytes().fallible(),
}
ipv6 {
args: func_args![value: "0:0:0:0:0:ffff:c633:6410"],
want: Ok(Value::from("198.51.100.16")),
tdef: TypeDef::bytes().fallible(),
}
ipv4 {
args: func_args![value: "198.51.100.16"],
want: Ok(Value::from("198.51.100.16")),
tdef: TypeDef::bytes().fallible(),
}
];
}