seda_sdk_rs/
proxy_http_fetch.rs

1//! Proxy HTTP fetch action and associated types for the `seda_runtime_sdk`.
2//! These are used to make HTTP requests through the proxy nodes.
3//!
4//! Defines JSON-serializable request and the struct [`ProxyHttpFetchAction`]
5//! and provides, [`proxy_http_fetch`], [`generate_proxy_http_signing_message`]
6//! for executing proxy HTTP requests, and generating signing messages via VM FFI calls.
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    bytes::{Bytes, ToBytes},
12    http::{HttpFetchOptions, HttpFetchResponse},
13    keccak256::keccak256,
14    promise::PromiseStatus,
15    HttpFetchMethod,
16};
17
18/// An Proxy HTTP fetch action containing the target URL, fetch options, and public key of the proxy.
19/// This action is serialized and sent to the VM for execution.
20#[derive(Serialize, Deserialize, Clone, Debug)]
21pub struct ProxyHttpFetchAction {
22    /// The URL to fetch.
23    pub url: String,
24    /// The public key of the proxy node that will handle the request.
25    pub public_key: Option<String>,
26    /// The options for the HTTP fetch request.
27    pub options: HttpFetchOptions,
28}
29
30/// Makes an HTTP request through the proxy service
31///
32/// # Panics
33///
34/// Panics if the serialization of the [`ProxyHttpFetchAction`] fails or if the deserialization of the response fails.
35/// We expect these to never happen in practice, as the SDK is designed to ensure valid inputs.
36///
37/// # Examples
38///
39/// ```no_run
40/// use seda_sdk_rs::bytes::ToBytes;
41/// use seda_sdk_rs::proxy_http_fetch::proxy_http_fetch;
42/// use seda_sdk_rs::http::{HttpFetchOptions, HttpFetchMethod};
43/// use std::collections::BTreeMap;
44///
45/// // Basic GET request
46/// let response = proxy_http_fetch("https://api.example.com/data", None, None);
47/// if response.is_ok() {
48///     println!("Status: {}", response.status);
49///     println!("Body length: {}", response.content_length);
50/// }
51///
52/// // POST request with custom options
53/// let mut headers = BTreeMap::new();
54/// headers.insert("Content-Type".to_string(), "application/json".to_string());
55///
56/// let options = HttpFetchOptions {
57///     method: HttpFetchMethod::Post,
58///     headers,
59///     body: Some(serde_json::to_vec(&serde_json::json!({"key": "value"})).unwrap().to_bytes()),
60///     timeout_ms: None,
61/// };
62///
63/// let response = proxy_http_fetch(
64///     "https://api.example.com/data",
65///     Some("02452f1c0a2753c6d2e55da4abeb1b6115c595eec9b0e237b8aaa74913ad3f1dc7".to_string()),
66///     Some(options)
67/// );
68///
69/// // Check response
70/// if response.is_ok() {
71///     // Access response data
72///     println!("Status: {}", response.status);
73///     println!("Final URL: {}", response.url);
74///     println!("Response size: {}", response.content_length);
75///
76///     // Access response headers
77///     if let Some(content_type) = response.headers.get("content-type") {
78///         println!("Content-Type: {}", content_type);
79///     }
80/// }
81/// ```
82pub fn proxy_http_fetch<URL: ToString>(
83    url: URL,
84    public_key: Option<String>,
85    options: Option<HttpFetchOptions>,
86) -> HttpFetchResponse {
87    let http_action = ProxyHttpFetchAction {
88        url: url.to_string(),
89        public_key,
90        options: options.unwrap_or_default(),
91    };
92
93    let action = serde_json::to_string(&http_action).unwrap();
94    let result_length = unsafe { super::raw::proxy_http_fetch(action.as_ptr(), action.len() as u32) };
95    let mut result_data_ptr = vec![0; result_length as usize];
96
97    unsafe {
98        super::raw::call_result_write(result_data_ptr.as_mut_ptr(), result_length);
99    }
100
101    let promise_status: PromiseStatus =
102        serde_json::from_slice(&result_data_ptr).expect("Could not deserialize proxy_http_fetch");
103
104    HttpFetchResponse::from_promise(promise_status)
105}
106
107/// Generates the message which the data proxy hashed and signed. This can be useful when you need to verify
108/// the data proxy signature in the tally phase. With this message there is no need to include the entire request
109/// and response data in the execution result.
110///
111///
112/// # Examples
113///
114/// ```no_run
115/// use seda_sdk_rs::{
116///     bytes::{Bytes, ToBytes},
117///     http::HttpFetchMethod,
118///     proxy_http_fetch::{generate_proxy_http_signing_message, proxy_http_fetch},
119/// };
120///
121/// let url = "https://api.example.com/data";
122/// let response = proxy_http_fetch(url, None, None);
123///
124/// if response.is_ok() {
125///     let proxy_message = generate_proxy_http_signing_message(
126///         url.to_string(),
127///         HttpFetchMethod::Get,
128///         Bytes::default(),
129///         response.bytes.to_bytes()
130///     );
131///     // Use proxy_message for signature verification
132/// }
133/// ```
134pub fn generate_proxy_http_signing_message(
135    url: String,
136    method: HttpFetchMethod,
137    request_body: Bytes,
138    response_body: Bytes,
139) -> Bytes {
140    let url_hash = keccak256(url.as_bytes().to_vec());
141    let method_hash = keccak256(method.as_str().as_bytes().to_vec());
142    let request_body_hash = keccak256(request_body.to_vec());
143    let response_body_hash = keccak256(response_body.to_vec());
144
145    [url_hash, method_hash, request_body_hash, response_body_hash]
146        .concat()
147        .to_bytes()
148}