thegraph_headers/
graph_attestation.rs

1//! An HTTP _typed header_ for the `graph-attestation` header.
2//!
3//! The `graph-attestation` header can contain a JSON-encoded [`Attestation`] struct, or an empty
4//! string if no attestation is provided.
5//!
6//! # Using the `headers::HeaderMapExt` extension trait
7//!
8//! ```rust
9//! # use fake::{Fake, Faker};
10//! use headers::HeaderMapExt as _;
11//! use thegraph_headers::graph_attestation::{GraphAttestation, HEADER_NAME};
12//!
13//! let mut header_map = http::HeaderMap::new();
14//! # let value = Faker.fake::<thegraph_headers::graph_attestation::Attestation>();
15//!
16//! // Insert a `graph-attestation` HTTP header
17//! header_map.typed_insert(GraphAttestation(value));
18//!
19//! // Get the `graph-attestation` HTTP header by name
20//! let header_by_name = header_map.get(HEADER_NAME);
21//! assert!(header_by_name.is_some());
22//!
23//! // Get the `graph-attestation` HTTP header by type
24//! let header_typed = header_map.typed_get::<GraphAttestation>();
25//! assert!(matches!(header_typed, Some(GraphAttestation(..))));
26//! ```
27
28use headers::{Error as HeaderError, HeaderName, HeaderValue};
29use thegraph_core::alloy::primitives::B256;
30pub use thegraph_core::attestation::Attestation;
31
32/// The HTTP header name for the `graph-attestation` header.
33pub const HEADER_NAME: &str = "graph-attestation";
34
35/// An HTTP _typed header_ for the `graph-attestation` header.
36///
37/// The `graph-attestation` header can contain a JSON-encoded [`Attestation`] struct, or an empty
38/// string if no attestation is provided.
39#[derive(Debug, Clone)]
40pub struct GraphAttestation(pub Attestation);
41
42impl headers::Header for GraphAttestation {
43    fn name() -> &'static HeaderName {
44        static HTTP_HEADER_NAME: HeaderName = HeaderName::from_static(HEADER_NAME);
45        &HTTP_HEADER_NAME
46    }
47
48    fn decode<'i, I>(values: &mut I) -> Result<Self, HeaderError>
49    where
50        Self: Sized,
51        I: Iterator<Item = &'i HeaderValue>,
52    {
53        // Get the first header value, and try to deserialize it into an `Attestation`.
54        let value = values.next().ok_or_else(HeaderError::invalid)?;
55        let attestation = serde_json::from_slice::<'_, AttestationSerde>(value.as_bytes())
56            .map_err(|_| HeaderError::invalid())?;
57        Ok(Self(attestation.into()))
58    }
59
60    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
61        // Serialize the attestation as a JSON string, and convert it to a `HeaderValue`.
62        let bytes =
63            serde_json::to_vec(&AttestationSerde::from(&self.0)).expect("header to be valid json");
64        let value = HeaderValue::from_bytes(&bytes).expect("header to be valid utf-8");
65        values.extend(std::iter::once(value));
66    }
67}
68
69#[derive(serde::Serialize, serde::Deserialize)]
70struct AttestationSerde {
71    #[serde(rename = "requestCID")]
72    request_cid: B256,
73    #[serde(rename = "responseCID")]
74    response_cid: B256,
75    #[serde(rename = "subgraphDeploymentID")]
76    deployment: B256,
77    r: B256,
78    s: B256,
79    v: u8,
80}
81
82impl From<AttestationSerde> for Attestation {
83    fn from(value: AttestationSerde) -> Self {
84        Self {
85            request_cid: value.request_cid,
86            response_cid: value.response_cid,
87            deployment: value.deployment,
88            r: value.r,
89            s: value.s,
90            v: value.v,
91        }
92    }
93}
94
95impl From<&Attestation> for AttestationSerde {
96    fn from(value: &Attestation) -> Self {
97        Self {
98            request_cid: value.request_cid,
99            response_cid: value.response_cid,
100            deployment: value.deployment,
101            r: value.r,
102            s: value.s,
103            v: value.v,
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use fake::{Fake, Faker};
111    use headers::{Header, HeaderValue};
112    use thegraph_core::attestation::Attestation;
113
114    use super::{AttestationSerde, GraphAttestation};
115
116    #[test]
117    fn encode_attestation_into_header() {
118        //* Given
119        let attestation = Faker.fake::<Attestation>();
120
121        let mut headers = vec![];
122
123        //* When
124        let header = GraphAttestation(attestation.clone());
125
126        header.encode(&mut headers);
127
128        //* Then
129        let value = headers.first().expect("header to have been encoded");
130
131        let att: AttestationSerde =
132            serde_json::from_slice(value.as_bytes()).expect("header to be valid json");
133        assert_eq!(attestation.request_cid, att.request_cid);
134        assert_eq!(attestation.response_cid, att.response_cid);
135        assert_eq!(attestation.deployment, att.deployment);
136        assert_eq!(attestation.r, att.r);
137        assert_eq!(attestation.s, att.s);
138        assert_eq!(attestation.v, att.v);
139    }
140
141    #[test]
142    fn decode_attestation_from_valid_header() {
143        //* Given
144        let attestation = Faker.fake::<Attestation>();
145
146        let header = {
147            let value = serde_json::to_string(&AttestationSerde::from(&attestation)).unwrap();
148            HeaderValue::from_str(value.as_str()).unwrap()
149        };
150        let headers = [header];
151
152        //* When
153        let header = GraphAttestation::decode(&mut headers.iter());
154
155        //* Then
156        let GraphAttestation(att) = header.expect("header to be valid");
157
158        assert_eq!(attestation.request_cid, att.request_cid);
159        assert_eq!(attestation.response_cid, att.response_cid);
160        assert_eq!(attestation.deployment, att.deployment);
161        assert_eq!(attestation.r, att.r);
162        assert_eq!(attestation.s, att.s);
163        assert_eq!(attestation.v, att.v);
164    }
165
166    #[test]
167    fn decode_attestation_from_first_header() {
168        //* Given
169        let attestation = Faker.fake::<Attestation>();
170
171        let header = {
172            let value = serde_json::to_string(&AttestationSerde::from(&attestation)).unwrap();
173            HeaderValue::from_str(&value).unwrap()
174        };
175        let headers = [
176            header,
177            HeaderValue::from_static("invalid"),
178            HeaderValue::from_static(""),
179        ];
180
181        //* When
182        let result = GraphAttestation::decode(&mut headers.iter());
183
184        //* Then
185        assert!(result.is_ok());
186    }
187
188    #[test]
189    fn fail_decode_attestation_from_empty_string_header() {
190        //* Given
191        let header = HeaderValue::from_static("");
192        let headers = [header];
193
194        //* When
195        let result = GraphAttestation::decode(&mut headers.iter());
196
197        //* Then
198        assert!(result.is_err());
199    }
200
201    #[test]
202    fn fail_decode_attestation_from_invalid_header() {
203        //* Given
204        let header = HeaderValue::from_static("invalid");
205        let headers = [header];
206
207        //* When
208        let header = GraphAttestation::decode(&mut headers.iter());
209
210        //* Then
211        assert!(header.is_err());
212    }
213
214    #[test]
215    fn fail_decode_attestation_if_no_headers() {
216        //* Given
217        let headers = [];
218
219        //* When
220        let header = GraphAttestation::decode(&mut headers.iter());
221
222        //* Then
223        assert!(header.is_err());
224    }
225}