use crate::functions::metadata::{ArgumentMetadata, FunctionMetadata, SyntaxVariants};
use crate::is_functions::IsFunction;
use minijinja::value::Kwargs;
use minijinja::{Environment, Error, ErrorKind, Value};
use std::net::TcpListener;
pub struct PortAvailable;
impl PortAvailable {
pub fn is_available(port: u16) -> bool {
TcpListener::bind(("0.0.0.0", port)).is_ok()
}
fn validate_port(port: i64) -> Result<u16, Error> {
if (1..=65535).contains(&port) {
Ok(port as u16)
} else {
Err(Error::new(
ErrorKind::InvalidOperation,
format!("Port must be between 1 and 65535, got {}", port),
))
}
}
}
impl IsFunction for PortAvailable {
const FUNCTION_NAME: &'static str = "is_port_available";
const IS_NAME: &'static str = "port_available";
const METADATA: FunctionMetadata = FunctionMetadata {
name: "is_port_available",
category: "network",
description: "Check if a TCP port is available for binding",
arguments: &[ArgumentMetadata {
name: "port",
arg_type: "integer",
required: true,
default: None,
description: "Port number (1-65535)",
}],
return_type: "boolean",
examples: &[
"{{ is_port_available(port=8080) }}",
"{% if 8080 is port_available %}port is free{% endif %}",
],
syntax: SyntaxVariants::FUNCTION_AND_TEST,
};
fn call_as_function(kwargs: Kwargs) -> Result<Value, Error> {
let port_i64: i64 = kwargs.get("port")?;
let port = Self::validate_port(port_i64)?;
Ok(Value::from(Self::is_available(port)))
}
fn call_as_is(value: &Value) -> bool {
let port = if let Some(n) = value.as_i64() {
n
} else if let Some(s) = value.as_str() {
s.parse::<i64>().unwrap_or(-1)
} else {
return false;
};
if (1..=65535).contains(&port) {
Self::is_available(port as u16)
} else {
false
}
}
}
pub fn register_all(env: &mut Environment) {
PortAvailable::register(env);
}