lodestone-proto 0.0.31

Versioned protocol envelopes and transport-neutral requests for Lodestone
Documentation
//! Canonical projection from the generated native metric wire into the
//! transport-neutral metric model.

use std::error::Error;
use std::fmt::{self, Display, Formatter};

use lodestone_types::{
    MetricAttributeV1, MetricAttributeValueV1, MetricDataV1, MetricExemplarV1, MetricExponentialBucketsV1,
    MetricInstrumentationScopeV1, MetricNumberV1, MetricPointV1, MetricTemporalityV1,
};

use crate::{
    I64, MetricAttribute, MetricAttributeValue, MetricData, MetricExemplar, MetricExponentialBuckets, MetricNumber,
    MetricPoint, MetricTemporality, U64,
};

pub fn metric_point_from_wire(value: &MetricPoint) -> Result<MetricPointV1, NativeMetricWireError> {
    Ok(MetricPointV1 {
        format_version: u16::try_from(value.format_version).map_err(|_| NativeMetricWireError::Integer)?,
        resource_attributes: metric_attributes_from_wire(&value.resource_attributes)?,
        instrumentation_scope: MetricInstrumentationScopeV1 {
            name: value.instrumentation_scope.name.clone(),
            version: value.instrumentation_scope.version.clone(),
            schema_url: value.instrumentation_scope.schema_url.clone(),
            attributes: metric_attributes_from_wire(&value.instrumentation_scope.attributes)?,
        },
        metric_name: value.metric_name.clone(),
        description: value.description.clone(),
        unit: value.unit.clone(),
        series_attributes: metric_attributes_from_wire(&value.series_attributes)?,
        timestamp_unix_nanos: parse_u64(&value.timestamp_unix_nanos)?,
        start_timestamp_unix_nanos: value.start_timestamp_unix_nanos.as_ref().map(parse_u64).transpose()?,
        data: metric_data_from_wire(&value.data)?,
        exemplars: value
            .exemplars
            .iter()
            .map(metric_exemplar_from_wire)
            .collect::<Result<Vec<_>, _>>()?,
    })
}

fn metric_attributes_from_wire(values: &[MetricAttribute]) -> Result<Vec<MetricAttributeV1>, NativeMetricWireError> {
    values
        .iter()
        .map(|attribute| {
            Ok(MetricAttributeV1 {
                key: attribute.key.clone(),
                value: match &attribute.value {
                    MetricAttributeValue::String { value } => MetricAttributeValueV1::String { value: value.clone() },
                    MetricAttributeValue::Boolean { value } => MetricAttributeValueV1::Boolean { value: *value },
                    MetricAttributeValue::I64 { value } => MetricAttributeValueV1::I64 {
                        value: parse_i64(value)?,
                    },
                    MetricAttributeValue::U64 { value } => MetricAttributeValueV1::U64 {
                        value: parse_u64(value)?,
                    },
                    MetricAttributeValue::F64Bits { value } => MetricAttributeValueV1::F64Bits {
                        value: parse_u64(value)?,
                    },
                },
            })
        })
        .collect()
}

fn metric_number_from_wire(value: &MetricNumber) -> Result<MetricNumberV1, NativeMetricWireError> {
    Ok(match value {
        MetricNumber::I64 { value } => MetricNumberV1::I64 {
            value: parse_i64(value)?,
        },
        MetricNumber::F64Bits { value } => MetricNumberV1::F64Bits {
            value: parse_u64(value)?,
        },
    })
}

const fn metric_temporality_from_wire(value: MetricTemporality) -> MetricTemporalityV1 {
    match value {
        MetricTemporality::Cumulative => MetricTemporalityV1::Cumulative,
        MetricTemporality::Delta => MetricTemporalityV1::Delta,
    }
}

fn metric_exponential_buckets_from_wire(
    value: &MetricExponentialBuckets,
) -> Result<MetricExponentialBucketsV1, NativeMetricWireError> {
    Ok(MetricExponentialBucketsV1 {
        offset: value.offset,
        counts: value.counts.iter().map(parse_u64).collect::<Result<Vec<_>, _>>()?,
    })
}

fn metric_data_from_wire(value: &MetricData) -> Result<MetricDataV1, NativeMetricWireError> {
    Ok(match value {
        MetricData::Gauge { value } => MetricDataV1::Gauge {
            value: metric_number_from_wire(value)?,
        },
        MetricData::Sum {
            value,
            temporality,
            monotonic,
        } => MetricDataV1::Sum {
            value: metric_number_from_wire(value)?,
            temporality: metric_temporality_from_wire(*temporality),
            monotonic: *monotonic,
        },
        MetricData::ExplicitHistogram {
            temporality,
            count,
            sum,
            bounds_f64_bits,
            bucket_counts,
        } => MetricDataV1::ExplicitHistogram {
            temporality: metric_temporality_from_wire(*temporality),
            count: parse_u64(count)?,
            sum: sum.as_ref().map(metric_number_from_wire).transpose()?,
            bounds_f64_bits: bounds_f64_bits.iter().map(parse_u64).collect::<Result<Vec<_>, _>>()?,
            bucket_counts: bucket_counts.iter().map(parse_u64).collect::<Result<Vec<_>, _>>()?,
        },
        MetricData::ExponentialHistogram {
            temporality,
            count,
            sum,
            scale,
            zero_count,
            positive,
            negative,
        } => MetricDataV1::ExponentialHistogram {
            temporality: metric_temporality_from_wire(*temporality),
            count: parse_u64(count)?,
            sum: sum.as_ref().map(metric_number_from_wire).transpose()?,
            scale: *scale,
            zero_count: parse_u64(zero_count)?,
            positive: metric_exponential_buckets_from_wire(positive)?,
            negative: metric_exponential_buckets_from_wire(negative)?,
        },
    })
}

fn metric_exemplar_from_wire(value: &MetricExemplar) -> Result<MetricExemplarV1, NativeMetricWireError> {
    Ok(MetricExemplarV1 {
        timestamp_unix_nanos: parse_u64(&value.timestamp_unix_nanos)?,
        value: metric_number_from_wire(&value.value)?,
        trace_id: value.trace_id.clone(),
        span_id: value.span_id.clone(),
        filtered_attributes: metric_attributes_from_wire(&value.filtered_attributes)?,
    })
}

fn parse_u64(value: &U64) -> Result<u64, NativeMetricWireError> {
    value.value.parse().map_err(|_| NativeMetricWireError::Integer)
}

fn parse_i64(value: &I64) -> Result<i64, NativeMetricWireError> {
    value.value.parse().map_err(|_| NativeMetricWireError::Integer)
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NativeMetricWireError {
    Integer,
}

impl Display for NativeMetricWireError {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
        formatter.write_str("native metric wire contains a noncanonical integer")
    }
}

impl Error for NativeMetricWireError {}