use crate::monitor::btf_render::{RenderedMember, RenderedValue};
use super::{SnapshotError, SnapshotResult};
#[derive(Debug)]
#[must_use = "SnapshotField is a borrowed view; call as_u64 / as_i64 / etc. to extract"]
#[non_exhaustive]
pub enum SnapshotField<'a> {
Value(&'a RenderedValue),
PercpuKey { key: u32 },
Missing(SnapshotError),
}
impl<'a> SnapshotField<'a> {
pub fn get(&self, path: &str) -> SnapshotField<'a> {
match self {
SnapshotField::Value(v) => walk_dotted_path(v, path),
SnapshotField::PercpuKey { .. } => {
SnapshotField::Missing(SnapshotError::TypeMismatch {
expected: "Struct".to_string(),
actual:
"Uint(percpu key) — call as_u64/as_i64/as_f64/as_bool for the key value"
.to_string(),
requested: path.to_string(),
})
}
SnapshotField::Missing(err) => SnapshotField::Missing(err.clone()),
}
}
pub fn is_present(&self) -> bool {
!matches!(self, SnapshotField::Missing(_))
}
pub fn as_u64(&self) -> SnapshotResult<u64> {
match self {
SnapshotField::Value(v) => render_to_u64(v),
SnapshotField::PercpuKey { key } => Ok(u64::from(*key)),
SnapshotField::Missing(err) => Err(err.clone()),
}
}
pub fn as_i64(&self) -> SnapshotResult<i64> {
match self {
SnapshotField::Value(v) => render_to_i64(v),
SnapshotField::PercpuKey { key } => Ok(i64::from(*key)),
SnapshotField::Missing(err) => Err(err.clone()),
}
}
pub fn as_bool(&self) -> SnapshotResult<bool> {
match self {
SnapshotField::Value(v) => match v {
RenderedValue::Bool { value } => Ok(*value),
RenderedValue::Int { value, .. } => Ok(*value != 0),
RenderedValue::Uint { value, .. } => Ok(*value != 0),
RenderedValue::Char { value } => Ok(*value != 0),
RenderedValue::Enum { value, .. } => Ok(*value != 0),
RenderedValue::Ptr { value, .. } => Ok(*value != 0),
other => Err(SnapshotError::TypeMismatch {
expected: "bool".to_string(),
actual: describe_kind(other),
requested: String::new(),
}),
},
SnapshotField::PercpuKey { key } => Ok(*key != 0),
SnapshotField::Missing(err) => Err(err.clone()),
}
}
pub fn as_f64(&self) -> SnapshotResult<f64> {
match self {
SnapshotField::Value(v) => match v {
RenderedValue::Float { value, .. } => Ok(*value),
RenderedValue::Int { value, .. } => Ok(*value as f64),
RenderedValue::Uint { value, .. } => Ok(*value as f64),
RenderedValue::Enum { value, .. } => Ok(*value as f64),
other => Err(SnapshotError::TypeMismatch {
expected: "f64".to_string(),
actual: describe_kind(other),
requested: String::new(),
}),
},
SnapshotField::PercpuKey { key } => Ok(f64::from(*key)),
SnapshotField::Missing(err) => Err(err.clone()),
}
}
pub fn as_str(&self) -> SnapshotResult<&'a str> {
match self {
SnapshotField::Value(v) => match v {
RenderedValue::Enum {
variant: Some(name),
..
} => Ok(name.as_str()),
other => Err(SnapshotError::TypeMismatch {
expected: "str (enum variant name)".to_string(),
actual: describe_kind(other),
requested: String::new(),
}),
},
SnapshotField::PercpuKey { .. } => Err(SnapshotError::TypeMismatch {
expected: "str".to_string(),
actual: "Uint(percpu key) — call as_u64/as_i64/as_f64/as_bool for the key value"
.to_string(),
requested: String::new(),
}),
SnapshotField::Missing(err) => Err(err.clone()),
}
}
pub fn as_u64_array(&self) -> SnapshotResult<Vec<u64>> {
render_to_typed_array(self, RenderedValue::as_u64, "u64")
}
pub fn as_u32_array(&self) -> SnapshotResult<Vec<u32>> {
render_to_typed_array(
self,
|v| v.as_u64().and_then(|x| u32::try_from(x).ok()),
"u32",
)
}
pub fn as_i64_array(&self) -> SnapshotResult<Vec<i64>> {
render_to_typed_array(self, RenderedValue::as_i64, "i64")
}
pub fn as_f64_array(&self) -> SnapshotResult<Vec<f64>> {
render_to_typed_array(self, RenderedValue::as_f64, "f64")
}
pub fn as_bool_array(&self) -> SnapshotResult<Vec<bool>> {
render_to_typed_array(self, RenderedValue::as_bool, "bool")
}
pub fn raw(&self) -> Option<&'a RenderedValue> {
match self {
SnapshotField::Value(v) => Some(v),
_ => None,
}
}
pub fn iter_members(&self) -> impl Iterator<Item = SnapshotField<'a>> + '_ {
let elements = match self {
SnapshotField::Value(v) => array_elements_of(v),
_ => &[],
};
elements.iter().map(SnapshotField::Value)
}
pub fn error(&self) -> Option<&SnapshotError> {
match self {
SnapshotField::Missing(err) => Some(err),
_ => None,
}
}
}
pub(crate) fn walk_dotted_path<'a>(root: &'a RenderedValue, path: &str) -> SnapshotField<'a> {
if path.is_empty() {
return SnapshotField::Value(root);
}
let mut cursor: &RenderedValue = root;
let mut walked = String::new();
for component in path.split('.') {
if component.is_empty() {
return SnapshotField::Missing(SnapshotError::EmptyPathComponent {
requested: path.to_string(),
});
}
cursor = peel_pointer(cursor);
let RenderedValue::Struct { members, .. } = cursor else {
return SnapshotField::Missing(SnapshotError::NotAStruct {
requested: path.to_string(),
walked: walked.clone(),
component: component.to_string(),
kind: describe_kind(cursor),
});
};
let next = members.iter().find(|m| m.name == component);
let Some(member) = next else {
let names: Vec<String> = members.iter().map(|m| m.name.clone()).collect();
return SnapshotField::Missing(SnapshotError::FieldNotFound {
requested: path.to_string(),
walked: walked.clone(),
component: component.to_string(),
available: names,
});
};
cursor = &member.value;
if !walked.is_empty() {
walked.push('.');
}
walked.push_str(component);
}
SnapshotField::Value(cursor)
}
pub(super) fn lookup_member<'a>(value: &'a RenderedValue, name: &str) -> Option<&'a RenderedValue> {
let v = peel_pointer(value);
let RenderedValue::Struct { members, .. } = v else {
return None;
};
members
.iter()
.find(|m: &&RenderedMember| m.name == name)
.map(|m| &m.value)
}
fn peel_pointer(mut v: &RenderedValue) -> &RenderedValue {
let mut steps = 0;
while let RenderedValue::Ptr {
deref: Some(inner), ..
} = v
{
v = inner.as_ref();
steps += 1;
if steps > 16 {
break;
}
}
v
}
fn describe_kind(v: &RenderedValue) -> String {
match v {
RenderedValue::Int { .. } => "Int",
RenderedValue::Uint { .. } => "Uint",
RenderedValue::Bool { .. } => "Bool",
RenderedValue::Char { .. } => "Char",
RenderedValue::Float { .. } => "Float",
RenderedValue::Enum { .. } => "Enum",
RenderedValue::Struct { .. } => "Struct",
RenderedValue::Array { .. } => "Array",
RenderedValue::CpuList { .. } => "CpuList",
RenderedValue::Ptr { .. } => "Ptr",
RenderedValue::Bytes { .. } => "Bytes",
RenderedValue::Truncated { .. } => "Truncated",
RenderedValue::Unsupported { .. } => "Unsupported",
}
.to_string()
}
fn array_elements_or_mismatch(v: &RenderedValue) -> Result<&[RenderedValue], &RenderedValue> {
match v {
RenderedValue::Array { elements, .. } => Ok(elements.as_slice()),
RenderedValue::Ptr {
deref: Some(inner), ..
} => array_elements_or_mismatch(inner.as_ref()),
RenderedValue::Truncated { partial, .. } => array_elements_or_mismatch(partial.as_ref()),
other => Err(other),
}
}
fn array_elements_of(v: &RenderedValue) -> &[RenderedValue] {
array_elements_or_mismatch(v).unwrap_or(&[])
}
fn render_to_typed_array<T, F>(
field: &SnapshotField<'_>,
coerce: F,
type_name: &'static str,
) -> SnapshotResult<Vec<T>>
where
F: Fn(&RenderedValue) -> Option<T>,
{
let value = match field {
SnapshotField::Value(v) => *v,
SnapshotField::PercpuKey { .. } => {
return Err(SnapshotError::TypeMismatch {
expected: format!("[{type_name}]"),
actual: "Uint(percpu key) — call as_u64/as_i64/as_f64/as_bool for the key value"
.to_string(),
requested: String::new(),
});
}
SnapshotField::Missing(err) => return Err(err.clone()),
};
let elements = array_elements_or_mismatch(value).map_err(|other| {
let actual = match value {
RenderedValue::Truncated { .. } => {
format!("Truncated(partial={})", describe_kind(other))
}
_ => describe_kind(other),
};
SnapshotError::TypeMismatch {
expected: format!("[{type_name}]"),
actual,
requested: String::new(),
}
})?;
let mut out = Vec::with_capacity(elements.len());
for (i, element) in elements.iter().enumerate() {
let v = coerce(element).ok_or_else(|| SnapshotError::TypeMismatch {
expected: format!("[{type_name}]"),
actual: format!("{}[{i}]={}", "Array", describe_kind(element)),
requested: String::new(),
})?;
out.push(v);
}
Ok(out)
}
fn render_to_u64(v: &RenderedValue) -> SnapshotResult<u64> {
match v {
RenderedValue::Uint { value, .. } => Ok(*value),
RenderedValue::Int { value, .. } => {
if *value < 0 {
Err(SnapshotError::TypeMismatch {
expected: "u64".to_string(),
actual: "Int(negative)".to_string(),
requested: String::new(),
})
} else {
Ok(*value as u64)
}
}
RenderedValue::Bool { value } => Ok(u64::from(*value)),
RenderedValue::Char { value } => Ok(u64::from(*value)),
RenderedValue::Enum {
value, is_signed, ..
} => {
if *is_signed && *value < 0 {
Err(SnapshotError::TypeMismatch {
expected: "u64".to_string(),
actual: "Enum(signed-negative)".to_string(),
requested: String::new(),
})
} else {
Ok(*value as u64)
}
}
RenderedValue::Ptr { value, .. } => Ok(*value),
other => Err(SnapshotError::TypeMismatch {
expected: "u64".to_string(),
actual: describe_kind(other),
requested: String::new(),
}),
}
}
fn render_to_i64(v: &RenderedValue) -> SnapshotResult<i64> {
match v {
RenderedValue::Int { value, .. } => Ok(*value),
RenderedValue::Uint { value, .. } => {
if *value > i64::MAX as u64 {
Err(SnapshotError::TypeMismatch {
expected: "i64".to_string(),
actual: "Uint(>i64::MAX)".to_string(),
requested: String::new(),
})
} else {
Ok(*value as i64)
}
}
RenderedValue::Bool { value } => Ok(i64::from(*value)),
RenderedValue::Char { value } => Ok(i64::from(*value)),
RenderedValue::Enum { value, .. } => Ok(*value),
other => Err(SnapshotError::TypeMismatch {
expected: "i64".to_string(),
actual: describe_kind(other),
requested: String::new(),
}),
}
}