polyte_core/
request.rs

1use std::marker::PhantomData;
2
3use reqwest::{Client, Response};
4use serde::de::DeserializeOwned;
5use url::Url;
6
7use crate::ApiError;
8
9/// Query parameter builder
10pub trait QueryBuilder: Sized {
11    /// Add a query parameter
12    fn add_query(&mut self, key: String, value: String);
13
14    /// Add a query parameter
15    fn query(mut self, key: impl Into<String>, value: impl ToString) -> Self {
16        self.add_query(key.into(), value.to_string());
17        self
18    }
19
20    /// Add optional query parameter (only if Some)
21    fn query_opt(mut self, key: impl Into<String>, value: Option<impl ToString>) -> Self {
22        if let Some(v) = value {
23            self.add_query(key.into(), v.to_string());
24        }
25        self
26    }
27
28    /// Add multiple query parameters with the same key
29    fn query_many<I, V>(self, key: impl Into<String>, values: I) -> Self
30    where
31        I: IntoIterator<Item = V>,
32        V: ToString,
33    {
34        let key = key.into();
35        let mut result = self;
36        for value in values {
37            result.add_query(key.clone(), value.to_string());
38        }
39        result
40    }
41
42    /// Add multiple optional query parameters with the same key
43    fn query_many_opt<I, V>(self, key: impl Into<String>, values: Option<I>) -> Self
44    where
45        I: IntoIterator<Item = V>,
46        V: ToString,
47    {
48        if let Some(values) = values {
49            self.query_many(key, values)
50        } else {
51            self
52        }
53    }
54}
55
56/// Trait for error types that can be created from API responses
57pub trait RequestError: From<ApiError> + std::fmt::Debug {
58    /// Create error from HTTP response
59    fn from_response(response: Response) -> impl std::future::Future<Output = Self> + Send;
60}
61
62/// Generic request builder for simple GET-only APIs (Gamma, Data)
63pub struct Request<T, E> {
64    pub(crate) client: Client,
65    pub(crate) base_url: Url,
66    pub(crate) path: String,
67    pub(crate) query: Vec<(String, String)>,
68    pub(crate) _marker: PhantomData<(T, E)>,
69}
70
71impl<T, E> Request<T, E> {
72    /// Create a new request
73    pub fn new(client: Client, base_url: Url, path: impl Into<String>) -> Self {
74        Self {
75            client,
76            base_url,
77            path: path.into(),
78            query: Vec::new(),
79            _marker: PhantomData,
80        }
81    }
82}
83
84impl<T, E> QueryBuilder for Request<T, E> {
85    fn add_query(&mut self, key: String, value: String) {
86        self.query.push((key, value));
87    }
88}
89
90impl<T: DeserializeOwned, E: RequestError> Request<T, E> {
91    /// Execute the request and deserialize response
92    pub async fn send(self) -> Result<T, E> {
93        let response = self.send_raw().await?;
94
95        // Get text for debugging
96        let text = response
97            .text()
98            .await
99            .map_err(|e| E::from(ApiError::from(e)))?;
100
101        tracing::debug!("Response body: {}", text);
102
103        // Deserialize and provide better error context
104        serde_json::from_str(&text).map_err(|e| {
105            tracing::error!("Deserialization failed: {}", e);
106            tracing::error!("Failed to deserialize: {}", text);
107            E::from(ApiError::from(e))
108        })
109    }
110
111    /// Execute the request and return raw response
112    pub async fn send_raw(self) -> Result<Response, E> {
113        let url = self
114            .base_url
115            .join(&self.path)
116            .map_err(|e| E::from(ApiError::from(e)))?;
117
118        let mut request = self.client.get(url);
119
120        if !self.query.is_empty() {
121            request = request.query(&self.query);
122        }
123
124        tracing::debug!("Sending request to: {:?}", request);
125
126        let response = request
127            .send()
128            .await
129            .map_err(|e| E::from(ApiError::from(e)))?;
130        let status = response.status();
131
132        tracing::debug!("Response status: {}", status);
133
134        if !status.is_success() {
135            let error = E::from_response(response).await;
136            tracing::error!("Request failed: {:?}", error);
137            return Err(error);
138        }
139
140        Ok(response)
141    }
142}
143
144/// Type marker for deserializable responses
145pub struct TypedRequest<T> {
146    pub(crate) _marker: PhantomData<T>,
147}
148
149impl<T> TypedRequest<T> {
150    pub fn new() -> Self {
151        Self {
152            _marker: PhantomData,
153        }
154    }
155}
156
157impl<T> Default for TypedRequest<T> {
158    fn default() -> Self {
159        Self::new()
160    }
161}