duners/
response.rs

1use crate::parse_utils::{datetime_from_str, optional_datetime_from_str};
2use chrono::{DateTime, Utc};
3use serde::Deserialize;
4use serde_with::DeserializeFromStr;
5use std::str::FromStr;
6
7/// Returned from successful call to `DuneClient::execute_query`
8#[derive(Deserialize, Debug)]
9pub struct ExecutionResponse {
10    pub execution_id: String,
11    pub state: ExecutionStatus,
12}
13
14/// Represents all possible states of query execution.
15/// Most states are self explanatory.
16/// Failure can occur if query takes too long (30 minutes) to execute.
17/// Pending state also comes along with a "queue position"
18#[derive(DeserializeFromStr, Debug, PartialEq)]
19pub enum ExecutionStatus {
20    Complete,
21    Executing,
22    Pending,
23    Cancelled,
24    Failed,
25}
26
27impl FromStr for ExecutionStatus {
28    type Err = String;
29
30    fn from_str(input: &str) -> Result<ExecutionStatus, Self::Err> {
31        match input {
32            "QUERY_STATE_COMPLETED" => Ok(ExecutionStatus::Complete),
33            "QUERY_STATE_EXECUTING" => Ok(ExecutionStatus::Executing),
34            "QUERY_STATE_PENDING" => Ok(ExecutionStatus::Pending),
35            "QUERY_STATE_CANCELLED" => Ok(ExecutionStatus::Cancelled),
36            "QUERY_STATE_FAILED" => Ok(ExecutionStatus::Failed),
37            other => Err(format!("Parse Error {other}")),
38        }
39    }
40}
41
42impl ExecutionStatus {
43    /// utility method for terminal query execution status.
44    /// The three terminal states are complete, cancelled and failed.
45    pub fn is_terminal(&self) -> bool {
46        match self {
47            ExecutionStatus::Complete => true,
48            ExecutionStatus::Cancelled => true,
49            ExecutionStatus::Failed => true,
50            ExecutionStatus::Executing => false,
51            ExecutionStatus::Pending => false,
52        }
53    }
54}
55
56/// Returned from call to `DuneClient::cancel_execution`
57#[derive(Deserialize, Debug)]
58pub struct CancellationResponse {
59    /// true when cancellation was successful, otherwise false.
60    pub success: bool,
61}
62
63/// Meta content returned optionally
64/// with [GetStatusResponse](GetStatusResponse)
65/// and always contained in [ExecutionResult](ExecutionResult).
66#[derive(Deserialize, Debug)]
67pub struct ResultMetaData {
68    pub column_names: Vec<String>,
69    pub result_set_bytes: u64,
70    pub total_row_count: u32,
71    pub datapoint_count: u32,
72    pub pending_time_millis: Option<u32>,
73    pub execution_time_millis: u32,
74}
75
76/// Nested inside [GetStatusResponse](GetStatusResponse)
77/// and [GetResultResponse](GetResultResponse).
78/// Contains several UTC timestamps related to the query execution.
79#[derive(Deserialize, Debug)]
80pub struct ExecutionTimes {
81    /// Time when query execution was submitted.
82    #[serde(deserialize_with = "datetime_from_str")]
83    pub submitted_at: DateTime<Utc>,
84    /// Time when execution results will no longer be stored on Dune servers.
85    /// None when query execution has not yet completed.
86    #[serde(deserialize_with = "optional_datetime_from_str", default)]
87    pub expires_at: Option<DateTime<Utc>>,
88    /// Time when query execution began.
89    /// Differs from `submitted_at` if execution was pending in the queue.
90    #[serde(deserialize_with = "optional_datetime_from_str", default)]
91    pub execution_started_at: Option<DateTime<Utc>>,
92    /// Time that query execution completed.
93    #[serde(deserialize_with = "optional_datetime_from_str", default)]
94    pub execution_ended_at: Option<DateTime<Utc>>,
95    /// Time that query execution was cancelled.
96    #[serde(deserialize_with = "optional_datetime_from_str", default)]
97    pub cancelled_at: Option<DateTime<Utc>>,
98}
99
100/// Returned by successful call to `DuneClient::get_status`.
101/// Indicates the current state of execution along with some metadata.
102#[derive(Deserialize, Debug)]
103pub struct GetStatusResponse {
104    pub execution_id: String,
105    pub query_id: u32,
106    pub state: ExecutionStatus,
107    #[serde(flatten)]
108    pub times: ExecutionTimes,
109    /// If the query state is Pending,
110    /// then there will be an associated integer indicating queue position.
111    pub queue_position: Option<u32>,
112    /// This field will be non-empty once query execution has completed.
113    pub result_metadata: Option<ResultMetaData>,
114}
115
116/// Contains the query results along with some additional metadata.
117/// This struct is nested inside [GetResultResponse](GetResultResponse)
118/// as the `result` field.
119#[derive(Deserialize, Debug)]
120pub struct ExecutionResult<T> {
121    pub rows: Vec<T>,
122    pub metadata: ResultMetaData,
123}
124
125/// Returned by a successful call to `DuneClient::get_results`.
126/// Contains similar information to [GetStatusResponse](GetStatusResponse)
127/// except that [ResultMetaData](ResultMetaData) is contained within the `result` field.
128#[derive(Deserialize, Debug)]
129pub struct GetResultResponse<T> {
130    pub execution_id: String,
131    pub query_id: u32,
132    pub state: ExecutionStatus,
133    // TODO - this `flatten` isn't what I had hoped for.
134    //  I want the `times` field to disappear
135    //  and all sub-fields to be brought up to this layer.
136    #[serde(flatten)]
137    pub times: ExecutionTimes,
138    pub result: ExecutionResult<T>,
139}
140
141impl<T> GetResultResponse<T> {
142    /// Convenience method for fetching the "deeply" nested `rows` of the result response.
143    pub fn get_rows(self) -> Vec<T> {
144        self.result.rows
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn status_from_str() {
154        assert_eq!(
155            ExecutionStatus::from_str("invalid"),
156            Err(String::from("Parse Error invalid"))
157        );
158        assert_eq!(
159            ExecutionStatus::from_str("QUERY_STATE_COMPLETED"),
160            Ok(ExecutionStatus::Complete)
161        );
162        assert_eq!(
163            ExecutionStatus::from_str("QUERY_STATE_EXECUTING"),
164            Ok(ExecutionStatus::Executing)
165        );
166        assert_eq!(
167            ExecutionStatus::from_str("QUERY_STATE_PENDING"),
168            Ok(ExecutionStatus::Pending)
169        );
170        assert_eq!(
171            ExecutionStatus::from_str("QUERY_STATE_CANCELLED"),
172            Ok(ExecutionStatus::Cancelled)
173        );
174        assert_eq!(
175            ExecutionStatus::from_str("QUERY_STATE_FAILED"),
176            Ok(ExecutionStatus::Failed)
177        );
178    }
179
180    #[test]
181    fn terminal_statuses() {
182        assert!(ExecutionStatus::Complete.is_terminal());
183        assert!(ExecutionStatus::Cancelled.is_terminal());
184        assert!(ExecutionStatus::Failed.is_terminal());
185
186        assert!(!ExecutionStatus::Pending.is_terminal());
187        assert!(!ExecutionStatus::Executing.is_terminal());
188    }
189    #[test]
190    fn derive_debug() {
191        assert_eq!(
192            format!(
193                "{:?}",
194                ExecutionResponse {
195                    execution_id: "jerb".to_string(),
196                    state: ExecutionStatus::Failed
197                }
198            ),
199            "ExecutionResponse { execution_id: \"jerb\", state: Failed }"
200        );
201        assert_eq!(
202            format!("{:?}", CancellationResponse { success: false }),
203            "CancellationResponse { success: false }"
204        );
205        let query_id = 71;
206        let execution_id = "jerb ID";
207
208        assert_eq!(
209            format!(
210                "{:?}",
211                GetStatusResponse {
212                    execution_id: execution_id.to_string(),
213                    query_id,
214                    state: ExecutionStatus::Pending,
215                    times: ExecutionTimes {
216                        submitted_at: Default::default(),
217                        expires_at: Default::default(),
218                        execution_started_at: Default::default(),
219                        execution_ended_at: Default::default(),
220                        cancelled_at: Default::default(),
221                    },
222                    queue_position: Some(10),
223                    result_metadata: Some(ResultMetaData {
224                        column_names: vec![],
225                        result_set_bytes: 0,
226                        total_row_count: 0,
227                        datapoint_count: 0,
228                        pending_time_millis: None,
229                        execution_time_millis: 0,
230                    }),
231                }
232            ),
233            "GetStatusResponse { \
234                execution_id: \"jerb ID\", \
235                query_id: 71, \
236                state: Pending, \
237                times: ExecutionTimes { \
238                    submitted_at: 1970-01-01T00:00:00Z, \
239                    expires_at: None, \
240                    execution_started_at: None, \
241                    execution_ended_at: None, \
242                    cancelled_at: None \
243                }, \
244                queue_position: Some(10), \
245                result_metadata: Some(ResultMetaData { \
246                        column_names: [], \
247                        result_set_bytes: 0, \
248                        total_row_count: 0, \
249                        datapoint_count: 0, \
250                        pending_time_millis: None, \
251                        execution_time_millis: 0 \
252                }\
253             ) }",
254        );
255        assert_eq!(
256            format!(
257                "{:?}",
258                GetResultResponse {
259                    execution_id: execution_id.to_string(),
260                    query_id,
261                    state: ExecutionStatus::Complete,
262                    times: ExecutionTimes {
263                        submitted_at: Default::default(),
264                        expires_at: Default::default(),
265                        execution_started_at: Default::default(),
266                        execution_ended_at: Default::default(),
267                        cancelled_at: Default::default(),
268                    },
269                    result: ExecutionResult::<u8> {
270                        rows: vec![],
271                        metadata: ResultMetaData {
272                            column_names: vec![],
273                            result_set_bytes: 0,
274                            total_row_count: 0,
275                            datapoint_count: 0,
276                            pending_time_millis: None,
277                            execution_time_millis: 0,
278                        }
279                    },
280                }
281            ),
282            "GetResultResponse { \
283                execution_id: \"jerb ID\", \
284                query_id: 71, \
285                state: Complete, \
286                times: ExecutionTimes { \
287                    submitted_at: 1970-01-01T00:00:00Z, \
288                    expires_at: None, \
289                    execution_started_at: None, \
290                    execution_ended_at: None, \
291                    cancelled_at: None \
292                }, \
293                result: ExecutionResult { \
294                    rows: [], \
295                    metadata: ResultMetaData { \
296                        column_names: [], \
297                        result_set_bytes: 0, \
298                        total_row_count: 0, \
299                        datapoint_count: 0, \
300                        pending_time_millis: None, \
301                        execution_time_millis: 0 \
302                    } \
303                } \
304            }",
305        );
306    }
307}