lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

metrics.rs (44835B)


      1 //! Bounded, deterministic Prometheus-compatible metrics snapshots.
      2 
      3 use core::fmt;
      4 use std::collections::BTreeMap;
      5 use std::error::Error;
      6 
      7 use crate::{BuildInfo, ServicePhase, TaskClassification, TaskName};
      8 
      9 pub const METRICS_CONTENT_TYPE: &str = "text/plain; version=0.0.4; charset=utf-8";
     10 pub const METRICS_MAX_DESCRIPTORS: usize = 64;
     11 pub const METRICS_MAX_SAMPLES: usize = 512;
     12 pub const METRICS_MAX_LABELS_PER_SAMPLE: usize = 8;
     13 pub const METRICS_MAX_RENDER_UTF8_BYTES: usize = 1_048_576;
     14 
     15 const METRIC_NAME_MAX_BYTES: usize = 128;
     16 const METRIC_HELP_MAX_BYTES: usize = 512;
     17 const METRIC_LABEL_VALUE_MAX_BYTES: usize = 128;
     18 const STABLE_RELAY_ID_MAX_BYTES: usize = 64;
     19 
     20 /// One of the five service-neutral metric interfaces owned by the host.
     21 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
     22 pub enum CommonMetricGroup {
     23     Build,
     24     Phase,
     25     Task,
     26     Storage,
     27     Transport,
     28 }
     29 
     30 /// Prometheus metric type supported by the bounded snapshot.
     31 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     32 pub enum MetricKind {
     33     Counter,
     34     Gauge,
     35 }
     36 
     37 impl MetricKind {
     38     const fn as_str(self) -> &'static str {
     39         match self {
     40             Self::Counter => "counter",
     41             Self::Gauge => "gauge",
     42         }
     43     }
     44 }
     45 
     46 /// Closed label-key inventory. Services cannot add arbitrary label dimensions.
     47 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
     48 pub enum MetricLabelKey {
     49     Version,
     50     Revision,
     51     Phase,
     52     Task,
     53     Classification,
     54     Storage,
     55     Transport,
     56     RelayId,
     57     State,
     58     Outcome,
     59 }
     60 
     61 /// A bounded canonical name for a storage or transport component.
     62 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
     63 pub struct MetricComponentId(String);
     64 
     65 impl MetricComponentId {
     66     pub fn new(value: impl AsRef<str>) -> Result<Self, MetricsContractError> {
     67         let value = value.as_ref();
     68         if value.is_empty()
     69             || value.len() > METRIC_LABEL_VALUE_MAX_BYTES
     70             || !value.bytes().enumerate().all(|(index, byte)| {
     71                 if index == 0 {
     72                     byte.is_ascii_lowercase()
     73                 } else {
     74                     byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'_'
     75                 }
     76             })
     77         {
     78             return Err(MetricsContractError::InvalidComponentId);
     79         }
     80         Ok(Self(value.to_owned()))
     81     }
     82 
     83     #[must_use]
     84     pub fn as_str(&self) -> &str {
     85         &self.0
     86     }
     87 }
     88 
     89 /// Closed storage and transport health vocabulary.
     90 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
     91 pub enum MetricHealthState {
     92     Ready,
     93     Degraded,
     94     Unready,
     95     ReadOnly,
     96     RepairRequired,
     97     Unavailable,
     98 }
     99 
    100 impl MetricHealthState {
    101     const fn as_str(self) -> &'static str {
    102         match self {
    103             Self::Ready => "ready",
    104             Self::Degraded => "degraded",
    105             Self::Unready => "unready",
    106             Self::ReadOnly => "read_only",
    107             Self::RepairRequired => "repair_required",
    108             Self::Unavailable => "unavailable",
    109         }
    110     }
    111 }
    112 
    113 /// Closed supervisor outcome vocabulary for task metrics.
    114 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    115 pub enum MetricTaskOutcome {
    116     ExpectedCompletion,
    117     OptionalFailure,
    118     TaskReturnedError,
    119     TaskPanicked,
    120     UnexpectedCompletion,
    121     UnexpectedCancellation,
    122     JoinFailed,
    123 }
    124 
    125 impl MetricTaskOutcome {
    126     const fn as_str(self) -> &'static str {
    127         match self {
    128             Self::ExpectedCompletion => "expected_completion",
    129             Self::OptionalFailure => "optional_failure",
    130             Self::TaskReturnedError => "task_returned_error",
    131             Self::TaskPanicked => "task_panicked",
    132             Self::UnexpectedCompletion => "unexpected_completion",
    133             Self::UnexpectedCancellation => "unexpected_cancellation",
    134             Self::JoinFailed => "join_failed",
    135         }
    136     }
    137 }
    138 
    139 impl MetricLabelKey {
    140     #[must_use]
    141     pub const fn as_str(self) -> &'static str {
    142         match self {
    143             Self::Version => "version",
    144             Self::Revision => "revision",
    145             Self::Phase => "phase",
    146             Self::Task => "task",
    147             Self::Classification => "classification",
    148             Self::Storage => "storage",
    149             Self::Transport => "transport",
    150             Self::RelayId => "relay_id",
    151             Self::State => "state",
    152             Self::Outcome => "outcome",
    153         }
    154     }
    155 
    156     const fn allowed_for(self, group: CommonMetricGroup) -> bool {
    157         match group {
    158             CommonMetricGroup::Build => matches!(self, Self::Version | Self::Revision),
    159             CommonMetricGroup::Phase => matches!(self, Self::Phase),
    160             CommonMetricGroup::Task => {
    161                 matches!(self, Self::Task | Self::Classification | Self::Outcome)
    162             }
    163             CommonMetricGroup::Storage => matches!(self, Self::Storage | Self::State),
    164             CommonMetricGroup::Transport => {
    165                 matches!(self, Self::Transport | Self::RelayId | Self::State)
    166             }
    167         }
    168     }
    169 }
    170 
    171 /// A validated Prometheus metric name.
    172 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
    173 pub struct MetricName(String);
    174 
    175 impl MetricName {
    176     pub fn new(value: impl AsRef<str>) -> Result<Self, MetricsContractError> {
    177         let value = value.as_ref();
    178         if value.is_empty() || value.len() > METRIC_NAME_MAX_BYTES || !valid_metric_name(value) {
    179             return Err(MetricsContractError::InvalidMetricName);
    180         }
    181         Ok(Self(value.to_owned()))
    182     }
    183 
    184     #[must_use]
    185     pub fn as_str(&self) -> &str {
    186         &self.0
    187     }
    188 }
    189 
    190 /// A canonical bounded relay identity suitable for a label value.
    191 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
    192 pub struct StableRelayId(String);
    193 
    194 impl StableRelayId {
    195     pub fn new(value: impl AsRef<str>) -> Result<Self, MetricsContractError> {
    196         let value = value.as_ref();
    197         if value.is_empty()
    198             || value.len() > STABLE_RELAY_ID_MAX_BYTES
    199             || !value.bytes().enumerate().all(|(index, byte)| {
    200                 if index == 0 {
    201                     byte.is_ascii_lowercase() || byte.is_ascii_digit()
    202                 } else {
    203                     byte.is_ascii_lowercase()
    204                         || byte.is_ascii_digit()
    205                         || matches!(byte, b'.' | b'_' | b'-')
    206                 }
    207             })
    208         {
    209             return Err(MetricsContractError::InvalidStableRelayId);
    210         }
    211         Ok(Self(value.to_owned()))
    212     }
    213 
    214     #[must_use]
    215     pub fn as_str(&self) -> &str {
    216         &self.0
    217     }
    218 }
    219 
    220 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
    221 struct MetricLabelValue(String);
    222 
    223 impl MetricLabelValue {
    224     fn new(value: impl AsRef<str>) -> Result<Self, MetricsContractError> {
    225         let value = value.as_ref();
    226         if value.is_empty()
    227             || value.len() > METRIC_LABEL_VALUE_MAX_BYTES
    228             || !value.bytes().enumerate().all(|(index, byte)| {
    229                 if index == 0 {
    230                     byte.is_ascii_alphanumeric()
    231                 } else {
    232                     byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b':' | b'-')
    233                 }
    234             })
    235         {
    236             return Err(MetricsContractError::InvalidLabelValue);
    237         }
    238         Ok(Self(value.to_owned()))
    239     }
    240 
    241     fn as_str(&self) -> &str {
    242         &self.0
    243     }
    244 }
    245 
    246 /// One validated label with a closed key and bounded value.
    247 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
    248 pub struct MetricLabel {
    249     key: MetricLabelKey,
    250     value: MetricLabelValue,
    251 }
    252 
    253 impl MetricLabel {
    254     pub fn build_version(build: &BuildInfo) -> Result<Self, MetricsContractError> {
    255         Self::validated(MetricLabelKey::Version, build.service_version())
    256     }
    257 
    258     pub fn build_revision(build: &BuildInfo) -> Result<Self, MetricsContractError> {
    259         Self::validated(MetricLabelKey::Revision, build.service_commit())
    260     }
    261 
    262     #[must_use]
    263     pub fn phase(phase: ServicePhase) -> Self {
    264         Self::fixed(
    265             MetricLabelKey::Phase,
    266             match phase {
    267                 ServicePhase::Starting => "starting",
    268                 ServicePhase::Ready => "ready",
    269                 ServicePhase::Degraded => "degraded",
    270                 ServicePhase::Unready => "unready",
    271                 ServicePhase::Stopping => "stopping",
    272                 ServicePhase::Failed => "failed",
    273             },
    274         )
    275     }
    276 
    277     pub fn task(task: &TaskName) -> Result<Self, MetricsContractError> {
    278         Self::validated(MetricLabelKey::Task, task.as_str())
    279     }
    280 
    281     #[must_use]
    282     pub fn classification(classification: TaskClassification) -> Self {
    283         Self::fixed(
    284             MetricLabelKey::Classification,
    285             match classification {
    286                 TaskClassification::Critical => "critical",
    287                 TaskClassification::Optional => "optional",
    288                 TaskClassification::OneShot => "one_shot",
    289             },
    290         )
    291     }
    292 
    293     #[must_use]
    294     pub fn storage(storage: MetricComponentId) -> Self {
    295         Self::fixed_owned(MetricLabelKey::Storage, storage.0)
    296     }
    297 
    298     #[must_use]
    299     pub fn transport(transport: MetricComponentId) -> Self {
    300         Self::fixed_owned(MetricLabelKey::Transport, transport.0)
    301     }
    302 
    303     #[must_use]
    304     pub fn relay_id(value: StableRelayId) -> Self {
    305         Self::fixed_owned(MetricLabelKey::RelayId, value.0)
    306     }
    307 
    308     #[must_use]
    309     pub fn health_state(state: MetricHealthState) -> Self {
    310         Self::fixed(MetricLabelKey::State, state.as_str())
    311     }
    312 
    313     #[must_use]
    314     pub fn task_outcome(outcome: MetricTaskOutcome) -> Self {
    315         Self::fixed(MetricLabelKey::Outcome, outcome.as_str())
    316     }
    317 
    318     #[must_use]
    319     pub const fn key(&self) -> MetricLabelKey {
    320         self.key
    321     }
    322 
    323     #[must_use]
    324     pub fn value(&self) -> &str {
    325         self.value.as_str()
    326     }
    327 
    328     fn validated(key: MetricLabelKey, value: &str) -> Result<Self, MetricsContractError> {
    329         Ok(Self {
    330             key,
    331             value: MetricLabelValue::new(value)?,
    332         })
    333     }
    334 
    335     fn fixed(key: MetricLabelKey, value: &'static str) -> Self {
    336         Self {
    337             key,
    338             value: MetricLabelValue(value.to_owned()),
    339         }
    340     }
    341 
    342     fn fixed_owned(key: MetricLabelKey, value: String) -> Self {
    343         Self {
    344             key,
    345             value: MetricLabelValue(value),
    346         }
    347     }
    348 }
    349 
    350 impl fmt::Debug for MetricLabel {
    351     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    352         formatter
    353             .debug_struct("MetricLabel")
    354             .field("key", &self.key)
    355             .field("value", &"[redacted]")
    356             .finish()
    357     }
    358 }
    359 
    360 /// One validated metric family descriptor.
    361 #[derive(Clone, PartialEq, Eq)]
    362 pub struct MetricDescriptor {
    363     group: CommonMetricGroup,
    364     name: MetricName,
    365     help: String,
    366     kind: MetricKind,
    367     label_keys: Vec<MetricLabelKey>,
    368 }
    369 
    370 impl MetricDescriptor {
    371     pub fn new(
    372         group: CommonMetricGroup,
    373         name: MetricName,
    374         help: impl AsRef<str>,
    375         kind: MetricKind,
    376         label_keys: impl IntoIterator<Item = MetricLabelKey>,
    377     ) -> Result<Self, MetricsContractError> {
    378         let help = help.as_ref();
    379         if help.is_empty()
    380             || help.len() > METRIC_HELP_MAX_BYTES
    381             || help
    382                 .chars()
    383                 .any(|character| character.is_control() && character != '\n')
    384         {
    385             return Err(MetricsContractError::InvalidHelp);
    386         }
    387         let mut label_keys: Vec<_> = label_keys
    388             .into_iter()
    389             .take(METRICS_MAX_LABELS_PER_SAMPLE + 1)
    390             .collect();
    391         if label_keys.len() > METRICS_MAX_LABELS_PER_SAMPLE {
    392             return Err(MetricsContractError::TooManyLabels);
    393         }
    394         label_keys.sort_unstable();
    395         if label_keys.windows(2).any(|pair| pair[0] == pair[1]) {
    396             return Err(MetricsContractError::DuplicateLabelKey);
    397         }
    398         if label_keys.iter().any(|key| !key.allowed_for(group)) {
    399             return Err(MetricsContractError::ForbiddenLabelKey);
    400         }
    401         Ok(Self {
    402             group,
    403             name,
    404             help: help.to_owned(),
    405             kind,
    406             label_keys,
    407         })
    408     }
    409 
    410     #[must_use]
    411     pub const fn group(&self) -> CommonMetricGroup {
    412         self.group
    413     }
    414 
    415     #[must_use]
    416     pub fn name(&self) -> &MetricName {
    417         &self.name
    418     }
    419 
    420     #[must_use]
    421     pub const fn kind(&self) -> MetricKind {
    422         self.kind
    423     }
    424 
    425     #[must_use]
    426     pub fn label_keys(&self) -> &[MetricLabelKey] {
    427         &self.label_keys
    428     }
    429 }
    430 
    431 impl fmt::Debug for MetricDescriptor {
    432     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    433         formatter
    434             .debug_struct("MetricDescriptor")
    435             .field("group", &self.group)
    436             .field("name", &self.name)
    437             .field("help", &"[redacted]")
    438             .field("kind", &self.kind)
    439             .field("label_keys", &self.label_keys)
    440             .finish()
    441     }
    442 }
    443 
    444 /// Exact integer sample value; floating-point ambiguity is intentionally absent.
    445 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    446 pub enum MetricValue {
    447     Counter(u64),
    448     Gauge(i64),
    449 }
    450 
    451 impl MetricValue {
    452     const fn kind(self) -> MetricKind {
    453         match self {
    454             Self::Counter(_) => MetricKind::Counter,
    455             Self::Gauge(_) => MetricKind::Gauge,
    456         }
    457     }
    458 }
    459 
    460 /// One bounded sample associated with a descriptor by exact name.
    461 #[derive(Clone, PartialEq, Eq)]
    462 pub struct MetricSample {
    463     name: MetricName,
    464     value: MetricValue,
    465     labels: Vec<MetricLabel>,
    466 }
    467 
    468 impl MetricSample {
    469     pub fn new(
    470         name: MetricName,
    471         value: MetricValue,
    472         labels: impl IntoIterator<Item = MetricLabel>,
    473     ) -> Result<Self, MetricsContractError> {
    474         let mut labels: Vec<_> = labels
    475             .into_iter()
    476             .take(METRICS_MAX_LABELS_PER_SAMPLE + 1)
    477             .collect();
    478         if labels.len() > METRICS_MAX_LABELS_PER_SAMPLE {
    479             return Err(MetricsContractError::TooManyLabels);
    480         }
    481         labels.sort_unstable();
    482         if labels.windows(2).any(|pair| pair[0].key == pair[1].key) {
    483             return Err(MetricsContractError::DuplicateLabelKey);
    484         }
    485         Ok(Self {
    486             name,
    487             value,
    488             labels,
    489         })
    490     }
    491 
    492     #[must_use]
    493     pub fn name(&self) -> &MetricName {
    494         &self.name
    495     }
    496 
    497     #[must_use]
    498     pub const fn value(&self) -> MetricValue {
    499         self.value
    500     }
    501 
    502     #[must_use]
    503     pub fn labels(&self) -> &[MetricLabel] {
    504         &self.labels
    505     }
    506 }
    507 
    508 impl fmt::Debug for MetricSample {
    509     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    510         formatter
    511             .debug_struct("MetricSample")
    512             .field("name", &self.name)
    513             .field("value", &self.value)
    514             .field("label_count", &self.labels.len())
    515             .finish()
    516     }
    517 }
    518 
    519 /// An immutable validated metrics snapshot with deterministic ordering.
    520 #[derive(Clone, PartialEq, Eq)]
    521 pub struct BoundedMetricsSnapshot {
    522     descriptors: Vec<MetricDescriptor>,
    523     samples: Vec<MetricSample>,
    524 }
    525 
    526 impl BoundedMetricsSnapshot {
    527     pub fn new(
    528         descriptors: impl IntoIterator<Item = MetricDescriptor>,
    529         samples: impl IntoIterator<Item = MetricSample>,
    530     ) -> Result<Self, MetricsContractError> {
    531         let mut descriptors: Vec<_> = descriptors
    532             .into_iter()
    533             .take(METRICS_MAX_DESCRIPTORS + 1)
    534             .collect();
    535         let mut samples: Vec<_> = samples.into_iter().take(METRICS_MAX_SAMPLES + 1).collect();
    536         if descriptors.len() > METRICS_MAX_DESCRIPTORS {
    537             return Err(MetricsContractError::TooManyDescriptors);
    538         }
    539         if samples.len() > METRICS_MAX_SAMPLES {
    540             return Err(MetricsContractError::TooManySamples);
    541         }
    542         descriptors.sort_by(|left, right| left.name.cmp(&right.name));
    543         if descriptors
    544             .windows(2)
    545             .any(|pair| pair[0].name == pair[1].name)
    546         {
    547             return Err(MetricsContractError::DuplicateDescriptor);
    548         }
    549         let descriptor_by_name: BTreeMap<_, _> = descriptors
    550             .iter()
    551             .map(|descriptor| (&descriptor.name, descriptor))
    552             .collect();
    553         for sample in &samples {
    554             let descriptor = descriptor_by_name
    555                 .get(&sample.name)
    556                 .ok_or(MetricsContractError::UnknownDescriptor)?;
    557             if sample.value.kind() != descriptor.kind {
    558                 return Err(MetricsContractError::ValueKindMismatch);
    559             }
    560             let actual_keys: Vec<_> = sample.labels.iter().map(|label| label.key).collect();
    561             if actual_keys != descriptor.label_keys {
    562                 return Err(MetricsContractError::LabelSetMismatch);
    563             }
    564         }
    565         samples.sort_by(|left, right| {
    566             left.name
    567                 .cmp(&right.name)
    568                 .then_with(|| left.labels.cmp(&right.labels))
    569         });
    570         if samples
    571             .windows(2)
    572             .any(|pair| pair[0].name == pair[1].name && pair[0].labels == pair[1].labels)
    573         {
    574             return Err(MetricsContractError::DuplicateSample);
    575         }
    576         Ok(Self {
    577             descriptors,
    578             samples,
    579         })
    580     }
    581 
    582     #[must_use]
    583     pub fn descriptors(&self) -> &[MetricDescriptor] {
    584         &self.descriptors
    585     }
    586 
    587     #[must_use]
    588     pub fn samples(&self) -> &[MetricSample] {
    589         &self.samples
    590     }
    591 
    592     /// Renders deterministic Prometheus text without exceeding `maximum_bytes`.
    593     pub fn render(&self, maximum_bytes: usize) -> Result<Vec<u8>, MetricsRenderError> {
    594         if maximum_bytes == 0 || maximum_bytes > METRICS_MAX_RENDER_UTF8_BYTES {
    595             return Err(MetricsRenderError::InvalidMaximum);
    596         }
    597         let mut output = BoundedOutput::new(maximum_bytes);
    598         for descriptor in &self.descriptors {
    599             output.push_str("# HELP ")?;
    600             output.push_str(descriptor.name.as_str())?;
    601             output.push_byte(b' ')?;
    602             output.push_escaped_help(&descriptor.help)?;
    603             output.push_byte(b'\n')?;
    604             output.push_str("# TYPE ")?;
    605             output.push_str(descriptor.name.as_str())?;
    606             output.push_byte(b' ')?;
    607             output.push_str(descriptor.kind.as_str())?;
    608             output.push_byte(b'\n')?;
    609 
    610             for sample in self
    611                 .samples
    612                 .iter()
    613                 .filter(|sample| sample.name == descriptor.name)
    614             {
    615                 output.push_str(sample.name.as_str())?;
    616                 if !sample.labels.is_empty() {
    617                     output.push_byte(b'{')?;
    618                     for (index, label) in sample.labels.iter().enumerate() {
    619                         if index != 0 {
    620                             output.push_byte(b',')?;
    621                         }
    622                         output.push_str(label.key.as_str())?;
    623                         output.push_str("=\"")?;
    624                         output.push_escaped_label_value(label.value.as_str())?;
    625                         output.push_byte(b'\"')?;
    626                     }
    627                     output.push_byte(b'}')?;
    628                 }
    629                 output.push_byte(b' ')?;
    630                 match sample.value {
    631                     MetricValue::Counter(value) => output.push_str(&value.to_string())?,
    632                     MetricValue::Gauge(value) => output.push_str(&value.to_string())?,
    633                 }
    634                 output.push_byte(b'\n')?;
    635             }
    636         }
    637         Ok(output.finish())
    638     }
    639 }
    640 
    641 impl fmt::Debug for BoundedMetricsSnapshot {
    642     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    643         formatter
    644             .debug_struct("BoundedMetricsSnapshot")
    645             .field("descriptor_count", &self.descriptors.len())
    646             .field("sample_count", &self.samples.len())
    647             .finish()
    648     }
    649 }
    650 
    651 /// Safe construction failure for the metrics contract.
    652 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    653 pub enum MetricsContractError {
    654     InvalidMetricName,
    655     InvalidHelp,
    656     InvalidLabelValue,
    657     InvalidStableRelayId,
    658     InvalidComponentId,
    659     ForbiddenLabelKey,
    660     DuplicateLabelKey,
    661     TooManyLabels,
    662     TooManyDescriptors,
    663     TooManySamples,
    664     DuplicateDescriptor,
    665     UnknownDescriptor,
    666     ValueKindMismatch,
    667     LabelSetMismatch,
    668     DuplicateSample,
    669 }
    670 
    671 impl fmt::Display for MetricsContractError {
    672     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    673         formatter.write_str("metrics snapshot violates the bounded host contract")
    674     }
    675 }
    676 
    677 impl Error for MetricsContractError {}
    678 
    679 /// Safe deterministic-rendering failure.
    680 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    681 pub enum MetricsRenderError {
    682     InvalidMaximum,
    683     ResponseTooLarge,
    684 }
    685 
    686 impl fmt::Display for MetricsRenderError {
    687     fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
    688         formatter.write_str("metrics snapshot cannot be rendered within the response limit")
    689     }
    690 }
    691 
    692 impl Error for MetricsRenderError {}
    693 
    694 struct BoundedOutput {
    695     bytes: Vec<u8>,
    696     maximum: usize,
    697 }
    698 
    699 impl BoundedOutput {
    700     fn new(maximum: usize) -> Self {
    701         Self {
    702             bytes: Vec::with_capacity(maximum.min(4096)),
    703             maximum,
    704         }
    705     }
    706 
    707     fn push_byte(&mut self, byte: u8) -> Result<(), MetricsRenderError> {
    708         if self.bytes.len() == self.maximum {
    709             return Err(MetricsRenderError::ResponseTooLarge);
    710         }
    711         self.bytes.push(byte);
    712         Ok(())
    713     }
    714 
    715     fn push_str(&mut self, value: &str) -> Result<(), MetricsRenderError> {
    716         let remaining = self.maximum.saturating_sub(self.bytes.len());
    717         if value.len() > remaining {
    718             return Err(MetricsRenderError::ResponseTooLarge);
    719         }
    720         self.bytes.extend_from_slice(value.as_bytes());
    721         Ok(())
    722     }
    723 
    724     fn push_escaped_help(&mut self, value: &str) -> Result<(), MetricsRenderError> {
    725         for character in value.chars() {
    726             match character {
    727                 '\\' => self.push_str("\\\\")?,
    728                 '\n' => self.push_str("\\n")?,
    729                 _ => {
    730                     let mut bytes = [0; 4];
    731                     self.push_str(character.encode_utf8(&mut bytes))?;
    732                 }
    733             }
    734         }
    735         Ok(())
    736     }
    737 
    738     fn push_escaped_label_value(&mut self, value: &str) -> Result<(), MetricsRenderError> {
    739         for character in value.chars() {
    740             match character {
    741                 '\\' => self.push_str("\\\\")?,
    742                 '\"' => self.push_str("\\\"")?,
    743                 '\n' => self.push_str("\\n")?,
    744                 _ => {
    745                     let mut bytes = [0; 4];
    746                     self.push_str(character.encode_utf8(&mut bytes))?;
    747                 }
    748             }
    749         }
    750         Ok(())
    751     }
    752 
    753     fn finish(self) -> Vec<u8> {
    754         self.bytes
    755     }
    756 }
    757 
    758 fn valid_metric_name(value: &str) -> bool {
    759     value.bytes().enumerate().all(|(index, byte)| {
    760         if index == 0 {
    761             byte.is_ascii_alphabetic() || matches!(byte, b'_' | b':')
    762         } else {
    763             byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b':')
    764         }
    765     })
    766 }
    767 
    768 #[cfg(test)]
    769 mod tests {
    770     use std::collections::BTreeSet;
    771     use std::error::Error;
    772 
    773     use super::*;
    774     use crate::{BuildInfoEnvironment, BuildMode, ContractVersions};
    775 
    776     fn name(value: &str) -> MetricName {
    777         MetricName::new(value).unwrap()
    778     }
    779 
    780     fn descriptor(
    781         group: CommonMetricGroup,
    782         metric_name: &str,
    783         kind: MetricKind,
    784         labels: &[MetricLabelKey],
    785     ) -> MetricDescriptor {
    786         MetricDescriptor::new(
    787             group,
    788             name(metric_name),
    789             "safe help",
    790             kind,
    791             labels.iter().copied(),
    792         )
    793         .unwrap()
    794     }
    795 
    796     fn build_info() -> BuildInfo {
    797         BuildInfo::from_compile_time(
    798             BuildMode::Release,
    799             BuildInfoEnvironment {
    800                 service_version: Some("1.2.3"),
    801                 service_commit: Some("1111111111111111111111111111111111111111"),
    802                 lib_revision: Some("2222222222222222222222222222222222222222"),
    803                 rust_version: Some("1.97.0"),
    804                 target: Some("x86_64-unknown-linux-gnu"),
    805                 feature_profile: Some("release"),
    806                 contract_versions: ContractVersions::new(1, 1, 1, 1, 1).unwrap(),
    807             },
    808         )
    809         .unwrap()
    810     }
    811 
    812     #[test]
    813     fn arbitrary_and_cross_group_labels_are_rejected() {
    814         assert_eq!(
    815             MetricDescriptor::new(
    816                 CommonMetricGroup::Build,
    817                 name("radroots_build_info"),
    818                 "build information",
    819                 MetricKind::Gauge,
    820                 [MetricLabelKey::RelayId],
    821             ),
    822             Err(MetricsContractError::ForbiddenLabelKey)
    823         );
    824         assert_eq!(
    825             MetricDescriptor::new(
    826                 CommonMetricGroup::Transport,
    827                 name("radroots_transport_state"),
    828                 "transport state",
    829                 MetricKind::Gauge,
    830                 [MetricLabelKey::RelayId, MetricLabelKey::RelayId],
    831             ),
    832             Err(MetricsContractError::DuplicateLabelKey)
    833         );
    834         assert_eq!(
    835             MetricName::new("bad metric"),
    836             Err(MetricsContractError::InvalidMetricName)
    837         );
    838     }
    839 
    840     #[test]
    841     fn relay_labels_require_stable_ids_and_reject_raw_urls() {
    842         let stable = StableRelayId::new("relay-west-01").unwrap();
    843         let label = MetricLabel::relay_id(stable.clone());
    844         assert_eq!(label.value(), stable.as_str());
    845         for forbidden in [
    846             "wss://relay.example.com",
    847             "Relay-West",
    848             "relay west",
    849             "-relay",
    850             "",
    851         ] {
    852             assert_eq!(
    853                 StableRelayId::new(forbidden),
    854                 Err(MetricsContractError::InvalidStableRelayId)
    855             );
    856         }
    857     }
    858 
    859     #[test]
    860     fn rendering_is_sorted_and_escapes_help() {
    861         let descriptor = MetricDescriptor::new(
    862             CommonMetricGroup::Task,
    863             name("radroots_task_outcomes_total"),
    864             "task\\outcomes\nby state",
    865             MetricKind::Counter,
    866             [MetricLabelKey::Task, MetricLabelKey::Outcome],
    867         )
    868         .unwrap();
    869         let sample = MetricSample::new(
    870             name("radroots_task_outcomes_total"),
    871             MetricValue::Counter(7),
    872             [
    873                 MetricLabel::task_outcome(MetricTaskOutcome::OptionalFailure),
    874                 MetricLabel::task(&TaskName::new("worker_one").unwrap()).unwrap(),
    875             ],
    876         )
    877         .unwrap();
    878         let snapshot = BoundedMetricsSnapshot::new([descriptor], [sample]).unwrap();
    879         assert_eq!(
    880             String::from_utf8(snapshot.render(1024).unwrap()).unwrap(),
    881             concat!(
    882                 "# HELP radroots_task_outcomes_total task\\\\outcomes\\nby state\n",
    883                 "# TYPE radroots_task_outcomes_total counter\n",
    884                 "radroots_task_outcomes_total{task=\"worker_one\",outcome=\"optional_failure\"} 7\n",
    885             )
    886         );
    887 
    888         let mut defensive = BoundedOutput::new(64);
    889         defensive
    890             .push_escaped_label_value("worker\\\"one\n")
    891             .unwrap();
    892         assert_eq!(defensive.finish(), b"worker\\\\\\\"one\\n");
    893     }
    894 
    895     #[test]
    896     fn component_ids_reject_free_text_urls_paths_and_control_characters() {
    897         for forbidden in [
    898             "raw error text",
    899             "wss://relay.example.com",
    900             "/private/path",
    901             "line\nfeed",
    902             "quote\"value",
    903             "back\\slash",
    904             "",
    905         ] {
    906             assert_eq!(
    907                 MetricComponentId::new(forbidden),
    908                 Err(MetricsContractError::InvalidComponentId)
    909             );
    910         }
    911         assert!(MetricComponentId::new("sqlite_writer").is_ok());
    912     }
    913 
    914     #[test]
    915     fn duplicates_unknowns_kind_and_label_mismatches_fail_closed() {
    916         let phase = descriptor(
    917             CommonMetricGroup::Phase,
    918             "radroots_phase",
    919             MetricKind::Gauge,
    920             &[MetricLabelKey::Phase],
    921         );
    922         assert_eq!(
    923             BoundedMetricsSnapshot::new([phase.clone(), phase.clone()], []),
    924             Err(MetricsContractError::DuplicateDescriptor)
    925         );
    926         let unknown =
    927             MetricSample::new(name("radroots_unknown"), MetricValue::Gauge(1), []).unwrap();
    928         assert_eq!(
    929             BoundedMetricsSnapshot::new([phase.clone()], [unknown]),
    930             Err(MetricsContractError::UnknownDescriptor)
    931         );
    932         let wrong_kind = MetricSample::new(
    933             name("radroots_phase"),
    934             MetricValue::Counter(1),
    935             [MetricLabel::phase(ServicePhase::Ready)],
    936         )
    937         .unwrap();
    938         assert_eq!(
    939             BoundedMetricsSnapshot::new([phase.clone()], [wrong_kind]),
    940             Err(MetricsContractError::ValueKindMismatch)
    941         );
    942         let wrong_labels =
    943             MetricSample::new(name("radroots_phase"), MetricValue::Gauge(1), []).unwrap();
    944         assert_eq!(
    945             BoundedMetricsSnapshot::new([phase], [wrong_labels]),
    946             Err(MetricsContractError::LabelSetMismatch)
    947         );
    948     }
    949 
    950     #[test]
    951     fn render_and_collection_bounds_fail_before_overgrowth() {
    952         let descriptor = descriptor(
    953             CommonMetricGroup::Phase,
    954             "radroots_phase",
    955             MetricKind::Gauge,
    956             &[MetricLabelKey::Phase],
    957         );
    958         let sample = MetricSample::new(
    959             name("radroots_phase"),
    960             MetricValue::Gauge(1),
    961             [MetricLabel::phase(ServicePhase::Ready)],
    962         )
    963         .unwrap();
    964         let snapshot = BoundedMetricsSnapshot::new([descriptor.clone()], [sample]).unwrap();
    965         let exact = snapshot.render(METRICS_MAX_RENDER_UTF8_BYTES).unwrap();
    966         assert_eq!(snapshot.render(exact.len()).unwrap(), exact);
    967         assert_eq!(
    968             snapshot.render(exact.len() - 1),
    969             Err(MetricsRenderError::ResponseTooLarge)
    970         );
    971         assert_eq!(snapshot.render(0), Err(MetricsRenderError::InvalidMaximum));
    972         assert_eq!(
    973             snapshot.render(METRICS_MAX_RENDER_UTF8_BYTES + 1),
    974             Err(MetricsRenderError::InvalidMaximum)
    975         );
    976 
    977         assert_eq!(
    978             BoundedMetricsSnapshot::new(
    979                 std::iter::repeat_n(descriptor, METRICS_MAX_DESCRIPTORS + 1),
    980                 [],
    981             ),
    982             Err(MetricsContractError::TooManyDescriptors)
    983         );
    984     }
    985 
    986     #[test]
    987     fn every_common_group_and_label_key_has_a_stable_inventory() {
    988         assert_eq!(METRICS_MAX_DESCRIPTORS, 64);
    989         assert_eq!(METRICS_MAX_SAMPLES, 512);
    990         assert_eq!(METRICS_MAX_LABELS_PER_SAMPLE, 8);
    991         assert_eq!(METRICS_MAX_RENDER_UTF8_BYTES, 1_048_576);
    992         assert_eq!(METRIC_NAME_MAX_BYTES, 128);
    993         assert_eq!(METRIC_HELP_MAX_BYTES, 512);
    994         assert_eq!(METRIC_LABEL_VALUE_MAX_BYTES, 128);
    995         assert_eq!(STABLE_RELAY_ID_MAX_BYTES, 64);
    996 
    997         let inventory = [
    998             (
    999                 CommonMetricGroup::Build,
   1000                 &[MetricLabelKey::Version, MetricLabelKey::Revision][..],
   1001             ),
   1002             (CommonMetricGroup::Phase, &[MetricLabelKey::Phase][..]),
   1003             (
   1004                 CommonMetricGroup::Task,
   1005                 &[
   1006                     MetricLabelKey::Task,
   1007                     MetricLabelKey::Classification,
   1008                     MetricLabelKey::Outcome,
   1009                 ][..],
   1010             ),
   1011             (
   1012                 CommonMetricGroup::Storage,
   1013                 &[MetricLabelKey::Storage, MetricLabelKey::State][..],
   1014             ),
   1015             (
   1016                 CommonMetricGroup::Transport,
   1017                 &[
   1018                     MetricLabelKey::Transport,
   1019                     MetricLabelKey::RelayId,
   1020                     MetricLabelKey::State,
   1021                 ][..],
   1022             ),
   1023         ];
   1024         let all_keys: BTreeSet<_> = [
   1025             MetricLabelKey::Version,
   1026             MetricLabelKey::Revision,
   1027             MetricLabelKey::Phase,
   1028             MetricLabelKey::Task,
   1029             MetricLabelKey::Classification,
   1030             MetricLabelKey::Storage,
   1031             MetricLabelKey::Transport,
   1032             MetricLabelKey::RelayId,
   1033             MetricLabelKey::State,
   1034             MetricLabelKey::Outcome,
   1035         ]
   1036         .into_iter()
   1037         .collect();
   1038         assert_eq!(
   1039             inventory
   1040                 .iter()
   1041                 .flat_map(|(_, keys)| keys.iter().copied())
   1042                 .collect::<BTreeSet<_>>(),
   1043             all_keys
   1044         );
   1045         for (group, allowed) in inventory {
   1046             for key in all_keys.iter().copied() {
   1047                 assert_eq!(key.allowed_for(group), allowed.contains(&key));
   1048             }
   1049         }
   1050     }
   1051 
   1052     #[test]
   1053     fn typed_label_vocabularies_are_exact_and_non_bypassable() {
   1054         let phases = [
   1055             ServicePhase::Starting,
   1056             ServicePhase::Ready,
   1057             ServicePhase::Degraded,
   1058             ServicePhase::Unready,
   1059             ServicePhase::Stopping,
   1060             ServicePhase::Failed,
   1061         ]
   1062         .map(MetricLabel::phase);
   1063         assert_eq!(
   1064             phases.map(|label| label.value().to_owned()),
   1065             [
   1066                 "starting", "ready", "degraded", "unready", "stopping", "failed"
   1067             ]
   1068             .map(str::to_owned)
   1069         );
   1070         assert_eq!(
   1071             [
   1072                 TaskClassification::Critical,
   1073                 TaskClassification::Optional,
   1074                 TaskClassification::OneShot,
   1075             ]
   1076             .map(MetricLabel::classification)
   1077             .map(|label| label.value().to_owned()),
   1078             ["critical", "optional", "one_shot"].map(str::to_owned)
   1079         );
   1080         assert_eq!(
   1081             [
   1082                 MetricHealthState::Ready,
   1083                 MetricHealthState::Degraded,
   1084                 MetricHealthState::Unready,
   1085                 MetricHealthState::ReadOnly,
   1086                 MetricHealthState::RepairRequired,
   1087                 MetricHealthState::Unavailable,
   1088             ]
   1089             .map(MetricLabel::health_state)
   1090             .map(|label| label.value().to_owned()),
   1091             [
   1092                 "ready",
   1093                 "degraded",
   1094                 "unready",
   1095                 "read_only",
   1096                 "repair_required",
   1097                 "unavailable",
   1098             ]
   1099             .map(str::to_owned)
   1100         );
   1101         assert_eq!(
   1102             [
   1103                 MetricTaskOutcome::ExpectedCompletion,
   1104                 MetricTaskOutcome::OptionalFailure,
   1105                 MetricTaskOutcome::TaskReturnedError,
   1106                 MetricTaskOutcome::TaskPanicked,
   1107                 MetricTaskOutcome::UnexpectedCompletion,
   1108                 MetricTaskOutcome::UnexpectedCancellation,
   1109                 MetricTaskOutcome::JoinFailed,
   1110             ]
   1111             .map(MetricLabel::task_outcome)
   1112             .map(|label| label.value().to_owned()),
   1113             [
   1114                 "expected_completion",
   1115                 "optional_failure",
   1116                 "task_returned_error",
   1117                 "task_panicked",
   1118                 "unexpected_completion",
   1119                 "unexpected_cancellation",
   1120                 "join_failed",
   1121             ]
   1122             .map(str::to_owned)
   1123         );
   1124     }
   1125 
   1126     #[test]
   1127     fn infinite_iterators_are_bounded_during_ingestion() {
   1128         assert_eq!(
   1129             MetricDescriptor::new(
   1130                 CommonMetricGroup::Task,
   1131                 name("radroots_task"),
   1132                 "task",
   1133                 MetricKind::Gauge,
   1134                 std::iter::repeat(MetricLabelKey::Task),
   1135             ),
   1136             Err(MetricsContractError::TooManyLabels)
   1137         );
   1138         assert_eq!(
   1139             MetricSample::new(
   1140                 name("radroots_phase"),
   1141                 MetricValue::Gauge(1),
   1142                 std::iter::repeat(MetricLabel::phase(ServicePhase::Ready)),
   1143             ),
   1144             Err(MetricsContractError::TooManyLabels)
   1145         );
   1146 
   1147         let descriptor = descriptor(
   1148             CommonMetricGroup::Phase,
   1149             "radroots_phase",
   1150             MetricKind::Gauge,
   1151             &[MetricLabelKey::Phase],
   1152         );
   1153         let sample = MetricSample::new(
   1154             name("radroots_phase"),
   1155             MetricValue::Gauge(1),
   1156             [MetricLabel::phase(ServicePhase::Ready)],
   1157         )
   1158         .unwrap();
   1159         assert_eq!(
   1160             BoundedMetricsSnapshot::new(std::iter::repeat(descriptor.clone()), []),
   1161             Err(MetricsContractError::TooManyDescriptors)
   1162         );
   1163         assert_eq!(
   1164             BoundedMetricsSnapshot::new([descriptor], std::iter::repeat(sample)),
   1165             Err(MetricsContractError::TooManySamples)
   1166         );
   1167     }
   1168 
   1169     #[test]
   1170     fn accessors_debug_errors_and_remaining_bounds_are_exact() {
   1171         let build = build_info();
   1172         let version = MetricLabel::build_version(&build).unwrap();
   1173         let revision = MetricLabel::build_revision(&build).unwrap();
   1174         assert_eq!(version.key(), MetricLabelKey::Version);
   1175         assert_eq!(version.value(), "1.2.3");
   1176         assert_eq!(revision.key(), MetricLabelKey::Revision);
   1177         assert_eq!(revision.value(), "1111111111111111111111111111111111111111");
   1178         assert_eq!(
   1179             format!("{version:?}"),
   1180             "MetricLabel { key: Version, value: \"[redacted]\" }"
   1181         );
   1182 
   1183         let storage_id = MetricComponentId::new("sqlite_writer").unwrap();
   1184         assert_eq!(storage_id.as_str(), "sqlite_writer");
   1185         let transport_id = MetricComponentId::new("nostr_relay").unwrap();
   1186         assert_eq!(transport_id.as_str(), "nostr_relay");
   1187         assert_eq!(MetricLabel::storage(storage_id).value(), "sqlite_writer");
   1188         assert_eq!(MetricLabel::transport(transport_id).value(), "nostr_relay");
   1189         assert!(MetricComponentId::new("a0").is_ok());
   1190         assert!(MetricComponentId::new("a_").is_ok());
   1191         assert_eq!(
   1192             MetricComponentId::new("a-").unwrap_err(),
   1193             MetricsContractError::InvalidComponentId
   1194         );
   1195 
   1196         let metric_name = name("radroots_build_info");
   1197         assert_eq!(metric_name.as_str(), "radroots_build_info");
   1198         let build_descriptor = MetricDescriptor::new(
   1199             CommonMetricGroup::Build,
   1200             metric_name.clone(),
   1201             "build identity",
   1202             MetricKind::Gauge,
   1203             [MetricLabelKey::Revision, MetricLabelKey::Version],
   1204         )
   1205         .unwrap();
   1206         assert_eq!(build_descriptor.group(), CommonMetricGroup::Build);
   1207         assert_eq!(build_descriptor.name(), &metric_name);
   1208         assert_eq!(build_descriptor.kind(), MetricKind::Gauge);
   1209         assert_eq!(
   1210             build_descriptor.label_keys(),
   1211             &[MetricLabelKey::Version, MetricLabelKey::Revision]
   1212         );
   1213         assert!(format!("{build_descriptor:?}").contains("help: \"[redacted]\""));
   1214 
   1215         let sample =
   1216             MetricSample::new(metric_name, MetricValue::Gauge(1), [revision, version]).unwrap();
   1217         assert_eq!(sample.name().as_str(), "radroots_build_info");
   1218         assert_eq!(sample.value(), MetricValue::Gauge(1));
   1219         assert_eq!(sample.labels().len(), 2);
   1220         assert!(format!("{sample:?}").contains("label_count: 2"));
   1221         assert_eq!(
   1222             MetricSample::new(
   1223                 name("radroots_duplicate_labels"),
   1224                 MetricValue::Gauge(1),
   1225                 [
   1226                     MetricLabel::phase(ServicePhase::Ready),
   1227                     MetricLabel::phase(ServicePhase::Degraded),
   1228                 ],
   1229             ),
   1230             Err(MetricsContractError::DuplicateLabelKey)
   1231         );
   1232 
   1233         let snapshot = BoundedMetricsSnapshot::new([build_descriptor], [sample.clone()]).unwrap();
   1234         assert_eq!(snapshot.descriptors().len(), 1);
   1235         assert_eq!(snapshot.samples(), std::slice::from_ref(&sample));
   1236         assert_eq!(
   1237             format!("{snapshot:?}"),
   1238             "BoundedMetricsSnapshot { descriptor_count: 1, sample_count: 1 }"
   1239         );
   1240         let rendered = String::from_utf8(snapshot.render(4096).unwrap()).unwrap();
   1241         assert!(rendered.contains("version=\"1.2.3\",revision="));
   1242         assert_eq!(
   1243             BoundedMetricsSnapshot::new(snapshot.descriptors().to_vec(), [sample.clone(), sample]),
   1244             Err(MetricsContractError::DuplicateSample)
   1245         );
   1246 
   1247         let phase_descriptor = descriptor(
   1248             CommonMetricGroup::Phase,
   1249             "radroots_phase_limit",
   1250             MetricKind::Gauge,
   1251             &[MetricLabelKey::Phase],
   1252         );
   1253         let phase_sample = MetricSample::new(
   1254             name("radroots_phase_limit"),
   1255             MetricValue::Gauge(1),
   1256             [MetricLabel::phase(ServicePhase::Ready)],
   1257         )
   1258         .unwrap();
   1259         assert_eq!(
   1260             BoundedMetricsSnapshot::new(
   1261                 [phase_descriptor],
   1262                 std::iter::repeat_n(phase_sample, METRICS_MAX_SAMPLES + 1),
   1263             ),
   1264             Err(MetricsContractError::TooManySamples)
   1265         );
   1266 
   1267         for invalid_help in [
   1268             String::new(),
   1269             "x".repeat(METRIC_HELP_MAX_BYTES + 1),
   1270             "unsafe\u{0000}help".to_owned(),
   1271         ] {
   1272             assert_eq!(
   1273                 MetricDescriptor::new(
   1274                     CommonMetricGroup::Phase,
   1275                     name("radroots_invalid_help"),
   1276                     invalid_help,
   1277                     MetricKind::Gauge,
   1278                     [MetricLabelKey::Phase],
   1279                 ),
   1280                 Err(MetricsContractError::InvalidHelp)
   1281             );
   1282         }
   1283         for invalid_value in ["", "-leading", "contains space"] {
   1284             assert!(matches!(
   1285                 MetricLabelValue::new(invalid_value),
   1286                 Err(MetricsContractError::InvalidLabelValue)
   1287             ));
   1288         }
   1289         assert!(MetricLabelValue::new("A0._:-").is_ok());
   1290         assert!(matches!(
   1291             MetricLabelValue::new("A/"),
   1292             Err(MetricsContractError::InvalidLabelValue)
   1293         ));
   1294         assert!(MetricName::new("_metric").is_ok());
   1295         assert!(MetricName::new(":metric9").is_ok());
   1296         assert!(MetricName::new("a".repeat(METRIC_NAME_MAX_BYTES)).is_ok());
   1297         assert_eq!(
   1298             MetricName::new("").unwrap_err(),
   1299             MetricsContractError::InvalidMetricName
   1300         );
   1301         assert_eq!(
   1302             MetricName::new("9metric").unwrap_err(),
   1303             MetricsContractError::InvalidMetricName
   1304         );
   1305         assert_eq!(
   1306             MetricName::new("x".repeat(METRIC_NAME_MAX_BYTES + 1)),
   1307             Err(MetricsContractError::InvalidMetricName)
   1308         );
   1309         assert_eq!(
   1310             StableRelayId::new("x".repeat(STABLE_RELAY_ID_MAX_BYTES + 1)),
   1311             Err(MetricsContractError::InvalidStableRelayId)
   1312         );
   1313         assert!(StableRelayId::new("x".repeat(STABLE_RELAY_ID_MAX_BYTES)).is_ok());
   1314         assert_eq!(
   1315             MetricComponentId::new("x".repeat(METRIC_LABEL_VALUE_MAX_BYTES + 1)),
   1316             Err(MetricsContractError::InvalidComponentId)
   1317         );
   1318         assert!(MetricComponentId::new("x".repeat(METRIC_LABEL_VALUE_MAX_BYTES)).is_ok());
   1319         assert!(MetricLabelValue::new("x".repeat(METRIC_LABEL_VALUE_MAX_BYTES)).is_ok());
   1320         assert!(matches!(
   1321             MetricLabelValue::new("x".repeat(METRIC_LABEL_VALUE_MAX_BYTES + 1)),
   1322             Err(MetricsContractError::InvalidLabelValue)
   1323         ));
   1324 
   1325         let exact_help = "x".repeat(METRIC_HELP_MAX_BYTES);
   1326         assert!(
   1327             MetricDescriptor::new(
   1328                 CommonMetricGroup::Phase,
   1329                 name("radroots_exact_help"),
   1330                 &exact_help,
   1331                 MetricKind::Gauge,
   1332                 [MetricLabelKey::Phase],
   1333             )
   1334             .is_ok()
   1335         );
   1336 
   1337         let very_large = "x".repeat(4 * 1024 * 1024);
   1338         assert_eq!(
   1339             MetricName::new(&very_large),
   1340             Err(MetricsContractError::InvalidMetricName)
   1341         );
   1342         assert_eq!(
   1343             StableRelayId::new(&very_large),
   1344             Err(MetricsContractError::InvalidStableRelayId)
   1345         );
   1346         assert_eq!(
   1347             MetricComponentId::new(&very_large),
   1348             Err(MetricsContractError::InvalidComponentId)
   1349         );
   1350         assert!(matches!(
   1351             MetricLabelValue::new(&very_large),
   1352             Err(MetricsContractError::InvalidLabelValue)
   1353         ));
   1354         assert_eq!(
   1355             MetricDescriptor::new(
   1356                 CommonMetricGroup::Phase,
   1357                 name("radroots_very_large_help"),
   1358                 &very_large,
   1359                 MetricKind::Gauge,
   1360                 [MetricLabelKey::Phase],
   1361             ),
   1362             Err(MetricsContractError::InvalidHelp)
   1363         );
   1364 
   1365         for error in [
   1366             MetricsContractError::InvalidMetricName.to_string(),
   1367             MetricsRenderError::InvalidMaximum.to_string(),
   1368         ] {
   1369             assert!(!error.is_empty());
   1370         }
   1371         assert!(MetricsContractError::InvalidMetricName.source().is_none());
   1372         assert!(MetricsRenderError::ResponseTooLarge.source().is_none());
   1373     }
   1374 }