Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
/// Check a host name against the RFC 1123 / RFC 952 rules and normalise it:
/// lower-case, one trailing dot (a fully qualified name) removed.
///
/// Checks run in a fixed order and the first failure is the reason: empty,
/// characters, total length, then each label in turn, then the top-level label.
pub fn validate_hostname(text: &str) -> HostnameValidation {
let name = text.strip_suffix('.').unwrap_or(text);
let labels: Vec<&str> = if name.is_empty() { Vec::new() } else { name.split('.').collect() };
let count = labels.len() as i64;
let fail = |reason: &str| HostnameValidation {
valid: false,
hostname: None,
reason: Some(reason.to_string()),
labels: count,
};
if name.is_empty() {
return fail("empty");
}
// Characters first: once the text is known to be ASCII, every language
// counts its length the same way.
if !name.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'.') {
return fail("character other than a letter, digit, hyphen or dot");
}
if name.len() > 253 {
return fail("longer than 253 characters");
}
for label in &labels {
if label.is_empty() {
return fail("empty label");
}
if label.len() > 63 {
return fail("label longer than 63 characters");
}
if label.starts_with('-') || label.ends_with('-') {
return fail("label starts or ends with a hyphen");
}
}
// RFC 3696 section 2: a top-level domain is never all-numeric, which keeps
// 1.2.3.4 an address rather than a name.
if labels[labels.len() - 1].bytes().all(|b| b.is_ascii_digit()) {
return fail("top-level label is all numeric");
}
HostnameValidation {
valid: true,
hostname: Some(name.to_ascii_lowercase()),
reason: None,
labels: count,
}
}
/// Object keys are camelCase to match the shared vectors.
pub fn hostname_validation_to_value(result: &HostnameValidation) -> Value {
let text = |field: &Option<String>| match field {
Some(s) => Value::str(s),
None => Value::Null,
};
Value::obj(vec![
("valid", Value::Bool(result.valid)),
("hostname", text(&result.hostname)),
("reason", text(&result.reason)),
("labels", Value::Int(result.labels)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
// A non-string is not a host name, as TypeScript and Python answer.
match &args[0] {
Value::Str(s) => hostname_validation_to_value(&validate_hostname(s)),
_ => hostname_validation_to_value(&HostnameValidation {
valid: false,
hostname: None,
reason: Some("not text".to_string()),
labels: 0,
}),
}
}