lodestone-proto 0.0.31

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

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

use lodestone_types::{
    ProfileBatchV1, ProfileFunctionV1, ProfileInstrumentationScopeV1, ProfileLabelV1, ProfileLineV1, ProfileLocationV1,
    ProfileMappingV1, ProfileNumberV1, ProfilePrivacyActionV1, ProfilePrivacyFieldV1, ProfilePrivacyPolicyV1,
    ProfilePrivacyRuleV1, ProfileSampleTypeV1, ProfileSampleV1, ProfileStackV1,
};

use crate::{
    I64, MetricNumber, ProfileBatch, ProfileLabel, ProfilePrivacyAction, ProfilePrivacyField, ProfilePrivacyPolicy,
    ProfilePrivacyRule, U64,
};

pub fn profile_batch_from_wire(value: &ProfileBatch) -> Result<ProfileBatchV1, NativeProfileWireError> {
    Ok(ProfileBatchV1 {
        format_version: u16::try_from(value.format_version).map_err(|_| NativeProfileWireError::Integer)?,
        profile_id: value.profile_id.clone(),
        batch_id: value.batch_id.clone(),
        start_unix_nanos: parse_u64(&value.start_unix_nanos)?,
        end_unix_nanos: parse_u64(&value.end_unix_nanos)?,
        duration_nanos: parse_u64(&value.duration_nanos)?,
        period_nanos: parse_u64(&value.period_nanos)?,
        sample_types: value
            .sample_types
            .iter()
            .map(|item| ProfileSampleTypeV1 {
                sample_type: item.sample_type.clone(),
                unit: item.unit.clone(),
            })
            .collect(),
        resource_attributes: labels_from_wire(&value.resource_attributes),
        instrumentation_scope: ProfileInstrumentationScopeV1 {
            name: value.instrumentation_scope.name.clone(),
            version: value.instrumentation_scope.version.clone(),
            attributes: labels_from_wire(&value.instrumentation_scope.attributes),
        },
        mappings: value
            .mappings
            .iter()
            .map(|item| ProfileMappingV1 {
                mapping_id: item.mapping_id.clone(),
                build_id: item.build_id.clone(),
                module_id: item.module_id.clone(),
                filename: item.filename.clone(),
            })
            .collect(),
        functions: value
            .functions
            .iter()
            .map(|item| {
                Ok(ProfileFunctionV1 {
                    function_id: item.function_id.clone(),
                    name: item.name.clone(),
                    system_name: item.system_name.clone(),
                    filename: item.filename.clone(),
                    start_line: item.start_line.as_ref().map(parse_u64).transpose()?,
                    symbol_observation_id: item.symbol_observation_id.clone(),
                })
            })
            .collect::<Result<Vec<_>, NativeProfileWireError>>()?,
        locations: value
            .locations
            .iter()
            .map(|item| {
                Ok(ProfileLocationV1 {
                    location_id: item.location_id.clone(),
                    mapping_id: item.mapping_id.clone(),
                    address: parse_u64(&item.address)?,
                    lines: item
                        .lines
                        .iter()
                        .map(|line| {
                            Ok(ProfileLineV1 {
                                function_id: line.function_id.clone(),
                                line: parse_u64(&line.line)?,
                            })
                        })
                        .collect::<Result<Vec<_>, NativeProfileWireError>>()?,
                })
            })
            .collect::<Result<Vec<_>, NativeProfileWireError>>()?,
        stacks: value
            .stacks
            .iter()
            .map(|item| ProfileStackV1 {
                stack_id: item.stack_id.clone(),
                parent_stack_id: item.parent_stack_id.clone(),
                location_id: item.location_id.clone(),
            })
            .collect(),
        samples: value
            .samples
            .iter()
            .map(|item| {
                Ok(ProfileSampleV1 {
                    sample_id: item.sample_id.clone(),
                    stack_id: item.stack_id.clone(),
                    values: item
                        .values
                        .iter()
                        .map(profile_number_from_wire)
                        .collect::<Result<Vec<_>, _>>()?,
                    labels: labels_from_wire(&item.labels),
                    trace_id: item.trace_id.clone(),
                    span_id: item.span_id.clone(),
                })
            })
            .collect::<Result<Vec<_>, NativeProfileWireError>>()?,
    })
}

pub fn profile_number_from_wire(value: &MetricNumber) -> Result<ProfileNumberV1, NativeProfileWireError> {
    match value {
        MetricNumber::I64 { value } => Ok(ProfileNumberV1::I64 {
            value: parse_i64(value)?,
        }),
        MetricNumber::F64Bits { value } => Ok(ProfileNumberV1::F64Bits {
            value: parse_u64(value)?,
        }),
    }
}

#[must_use]
pub fn profile_number_to_wire(value: ProfileNumberV1) -> MetricNumber {
    match value {
        ProfileNumberV1::I64 { value } => MetricNumber::I64 {
            value: I64 {
                value: value.to_string(),
            },
        },
        ProfileNumberV1::F64Bits { value } => MetricNumber::F64Bits {
            value: U64 {
                value: value.to_string(),
            },
        },
    }
}

pub fn profile_privacy_policy_from_wire(
    value: &ProfilePrivacyPolicy,
) -> Result<ProfilePrivacyPolicyV1, NativeProfileWireError> {
    Ok(ProfilePrivacyPolicyV1 {
        format_version: u16::try_from(value.format_version).map_err(|_| NativeProfileWireError::Integer)?,
        rules: value
            .rules
            .iter()
            .map(|rule| ProfilePrivacyRuleV1 {
                field: match &rule.field {
                    ProfilePrivacyField::ResourceAttribute { key } => {
                        ProfilePrivacyFieldV1::ResourceAttribute { key: key.clone() }
                    }
                    ProfilePrivacyField::ScopeAttribute { key } => {
                        ProfilePrivacyFieldV1::ScopeAttribute { key: key.clone() }
                    }
                    ProfilePrivacyField::SampleLabel { key } => ProfilePrivacyFieldV1::SampleLabel { key: key.clone() },
                    ProfilePrivacyField::FunctionName => ProfilePrivacyFieldV1::FunctionName,
                    ProfilePrivacyField::FunctionSystemName => ProfilePrivacyFieldV1::FunctionSystemName,
                    ProfilePrivacyField::FunctionFilename => ProfilePrivacyFieldV1::FunctionFilename,
                    ProfilePrivacyField::MappingFilename => ProfilePrivacyFieldV1::MappingFilename,
                },
                action: match rule.action {
                    ProfilePrivacyAction::Allow => ProfilePrivacyActionV1::Allow,
                    ProfilePrivacyAction::Drop => ProfilePrivacyActionV1::Drop,
                    ProfilePrivacyAction::Redact => ProfilePrivacyActionV1::Redact,
                    ProfilePrivacyAction::Reject => ProfilePrivacyActionV1::Reject,
                },
            })
            .collect(),
    })
}

#[must_use]
pub fn profile_privacy_policy_to_wire(value: &ProfilePrivacyPolicyV1) -> ProfilePrivacyPolicy {
    ProfilePrivacyPolicy {
        format_version: u32::from(value.format_version),
        rules: value
            .rules
            .iter()
            .map(|rule| ProfilePrivacyRule {
                field: match &rule.field {
                    ProfilePrivacyFieldV1::ResourceAttribute { key } => {
                        ProfilePrivacyField::ResourceAttribute { key: key.clone() }
                    }
                    ProfilePrivacyFieldV1::ScopeAttribute { key } => {
                        ProfilePrivacyField::ScopeAttribute { key: key.clone() }
                    }
                    ProfilePrivacyFieldV1::SampleLabel { key } => ProfilePrivacyField::SampleLabel { key: key.clone() },
                    ProfilePrivacyFieldV1::FunctionName => ProfilePrivacyField::FunctionName,
                    ProfilePrivacyFieldV1::FunctionSystemName => ProfilePrivacyField::FunctionSystemName,
                    ProfilePrivacyFieldV1::FunctionFilename => ProfilePrivacyField::FunctionFilename,
                    ProfilePrivacyFieldV1::MappingFilename => ProfilePrivacyField::MappingFilename,
                },
                action: match rule.action {
                    ProfilePrivacyActionV1::Allow => ProfilePrivacyAction::Allow,
                    ProfilePrivacyActionV1::Drop => ProfilePrivacyAction::Drop,
                    ProfilePrivacyActionV1::Redact => ProfilePrivacyAction::Redact,
                    ProfilePrivacyActionV1::Reject => ProfilePrivacyAction::Reject,
                },
            })
            .collect(),
    }
}

fn labels_from_wire(values: &[ProfileLabel]) -> Vec<ProfileLabelV1> {
    values
        .iter()
        .map(|item| ProfileLabelV1 {
            key: item.key.clone(),
            value: item.value.clone(),
        })
        .collect()
}

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

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

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

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

impl Error for NativeProfileWireError {}