iota_grpc_client/api/
metadata.rs

1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! MetadataEnvelope wrapper that preserves gRPC metadata headers alongside the
5//! body.
6
7use crate::ResponseExt;
8
9/// A response from the gRPC API that carries both the response body and
10/// metadata headers.
11///
12/// The metadata contains IOTA-specific headers such as chain ID, epoch,
13/// checkpoint height, and timestamps. Access them via the [`ResponseExt`]
14/// trait methods.
15///
16/// Use [`body()`](Self::body) / [`body_mut()`](Self::body_mut) to access the
17/// response body, or [`into_inner()`](Self::into_inner) to consume the
18/// envelope and extract the body.
19///
20/// # Example
21///
22/// ```ignore
23/// let response = client.get_health(None).await?;
24///
25/// // Access body fields
26/// println!("{:?}", response.body().executed_checkpoint_height);
27///
28/// // Access metadata headers via ResponseExt
29/// println!("epoch: {:?}", response.epoch());
30/// println!("chain: {:?}", response.chain());
31///
32/// // Extract just the body if you don't need metadata
33/// let body = response.into_inner();
34/// ```
35#[derive(Debug, Clone)]
36pub struct MetadataEnvelope<T> {
37    inner: T,
38    metadata: tonic::metadata::MetadataMap,
39}
40
41impl<T> MetadataEnvelope<T> {
42    /// Create a new response from a body and metadata.
43    pub fn new(inner: T, metadata: tonic::metadata::MetadataMap) -> Self {
44        Self { inner, metadata }
45    }
46
47    /// Consume the response and return the body, discarding metadata.
48    pub fn into_inner(self) -> T {
49        self.inner
50    }
51
52    /// Get a reference to the response body.
53    pub fn body(&self) -> &T {
54        &self.inner
55    }
56
57    /// Get a mutable reference to the response body.
58    pub fn body_mut(&mut self) -> &mut T {
59        &mut self.inner
60    }
61
62    /// Consume the response and return both the body and metadata.
63    pub fn into_parts(self) -> (T, tonic::metadata::MetadataMap) {
64        (self.inner, self.metadata)
65    }
66
67    /// Get a reference to the metadata.
68    pub fn metadata(&self) -> &tonic::metadata::MetadataMap {
69        &self.metadata
70    }
71
72    /// Transform the body, preserving metadata.
73    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> MetadataEnvelope<U> {
74        MetadataEnvelope {
75            inner: f(self.inner),
76            metadata: self.metadata,
77        }
78    }
79
80    /// Transform the body with a fallible function, preserving metadata on
81    /// success.
82    pub fn try_map<U, E>(
83        self,
84        f: impl FnOnce(T) -> Result<U, E>,
85    ) -> Result<MetadataEnvelope<U>, E> {
86        Ok(MetadataEnvelope {
87            inner: f(self.inner)?,
88            metadata: self.metadata,
89        })
90    }
91}
92
93impl<T> From<tonic::Response<T>> for MetadataEnvelope<T> {
94    fn from(response: tonic::Response<T>) -> Self {
95        let metadata = response.metadata().clone();
96        Self {
97            inner: response.into_inner(),
98            metadata,
99        }
100    }
101}
102
103impl<T> ResponseExt for MetadataEnvelope<T> {
104    fn chain_id(&self) -> Option<iota_sdk_types::Digest> {
105        self.metadata.chain_id()
106    }
107
108    fn chain(&self) -> Option<&str> {
109        self.metadata.chain()
110    }
111
112    fn epoch(&self) -> Option<u64> {
113        self.metadata.epoch()
114    }
115
116    fn checkpoint_height(&self) -> Option<u64> {
117        self.metadata.checkpoint_height()
118    }
119
120    fn timestamp_ms(&self) -> Option<u64> {
121        self.metadata.timestamp_ms()
122    }
123
124    fn timestamp(&self) -> Option<&str> {
125        self.metadata.timestamp()
126    }
127
128    fn lowest_available_checkpoint(&self) -> Option<u64> {
129        self.metadata.lowest_available_checkpoint()
130    }
131
132    fn lowest_available_checkpoint_objects(&self) -> Option<u64> {
133        self.metadata.lowest_available_checkpoint_objects()
134    }
135
136    fn server_version(&self) -> Option<&str> {
137        self.metadata.server_version()
138    }
139}