1use crate::types::{Column, ColumnList, Doc, DocList, GetTableResponse, ListTablesResponse, NextPageToken, Row, RowList, TableList, TableReference};
2use crate::{Error, RawClient, types};
3use progenitor_client::{ClientHooks, ClientInfo, OperationInfo, ResponseValue, encode_path};
4use serde::de::DeserializeOwned;
5use thiserror::Error;
6
7#[cfg(feature = "time")]
8mod duration_value_parser;
9mod impl_from_for_value;
10mod items_list;
11mod parse_cell_value;
12mod parse_rich_value;
13mod rich_rows;
14mod row;
15mod string_or_f64;
16mod value_format_provider;
17
18#[cfg(feature = "time")]
19pub use duration_value_parser::*;
20pub use items_list::*;
21pub use parse_cell_value::*;
22pub use parse_rich_value::*;
23pub use rich_rows::*;
24pub(crate) use string_or_f64::*;
25pub use value_format_provider::*;
26
27pub type DocId = String;
28
29pub type TableId = String;
30
31pub type RowId = String;
32
33pub trait PaginatedResponse<T> {
35 fn items(&self) -> &Vec<T>;
36 fn next_page_token(&self) -> Option<&NextPageToken>;
37 fn into_items(self) -> Vec<T>;
38}
39
40pub struct PaginationState {
42 pub next_page_token: Option<String>,
43}
44
45impl PaginationState {
46 pub fn new() -> Self {
47 Self {
48 next_page_token: None,
49 }
50 }
51
52 pub fn update_from_response<T, R: PaginatedResponse<T>>(&mut self, response: &R) {
53 self.next_page_token = response.next_page_token().map(|token| token.clone().into());
54 }
55
56 pub fn has_more_pages(&self) -> bool {
57 self.next_page_token.is_some()
58 }
59
60 pub fn page_token(&self) -> Option<&str> {
61 self.next_page_token.as_deref()
62 }
63}
64
65impl Default for PaginationState {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl PaginatedResponse<TableReference> for TableList {
73 fn items(&self) -> &Vec<TableReference> {
74 &self.items
75 }
76
77 fn next_page_token(&self) -> Option<&NextPageToken> {
78 self.next_page_token.as_ref()
79 }
80
81 fn into_items(self) -> Vec<TableReference> {
82 self.items
83 }
84}
85
86impl PaginatedResponse<Column> for ColumnList {
87 fn items(&self) -> &Vec<Column> {
88 &self.items
89 }
90
91 fn next_page_token(&self) -> Option<&NextPageToken> {
92 self.next_page_token.as_ref()
93 }
94
95 fn into_items(self) -> Vec<Column> {
96 self.items
97 }
98}
99
100impl PaginatedResponse<Doc> for DocList {
101 fn items(&self) -> &Vec<Doc> {
102 &self.items
103 }
104
105 fn next_page_token(&self) -> Option<&NextPageToken> {
106 self.next_page_token.as_ref()
107 }
108
109 fn into_items(self) -> Vec<Doc> {
110 self.items
111 }
112}
113
114impl PaginatedResponse<Row> for RowList {
115 fn items(&self) -> &Vec<Row> {
116 &self.items
117 }
118
119 fn next_page_token(&self) -> Option<&NextPageToken> {
120 self.next_page_token.as_ref()
121 }
122
123 fn into_items(self) -> Vec<Row> {
124 self.items
125 }
126}
127
128impl<T> PaginatedResponse<T> for ItemsList<T> {
129 fn items(&self) -> &Vec<T> {
130 &self.items
131 }
132
133 fn next_page_token(&self) -> Option<&NextPageToken> {
134 self.next_page_token.as_ref()
135 }
136
137 fn into_items(self) -> Vec<T> {
138 self.items
139 }
140}
141
142impl RawClient {
143 pub const BASE_URL: &'static str = "https://coda.io/apis/v1";
144
145 pub fn new_with_key(api_key: &str) -> reqwest::Result<Self> {
146 let authorization_header = format!("Bearer {api_key}")
147 .parse()
148 .expect("API key should be valid");
149
150 let mut headers = reqwest::header::HeaderMap::with_capacity(1);
151 headers.insert(reqwest::header::AUTHORIZATION, authorization_header);
152
153 let client_with_custom_defaults = reqwest::ClientBuilder::new()
154 .default_headers(headers)
155 .build()?;
156
157 let client = Self::new_with_client(Self::BASE_URL, client_with_custom_defaults);
158
159 Ok(client)
160 }
161
162 #[allow(clippy::too_many_arguments)]
163 pub async fn list_rows_correct<'a, T: DeserializeOwned + ValueFormatProvider>(&'a self, doc_id: &'a str, table_id_or_name: &'a str, limit: Option<::std::num::NonZeroU64>, page_token: Option<&'a str>, query: Option<&'a str>, sort_by: Option<types::RowsSortBy>, sync_token: Option<&'a str>, use_column_names: Option<bool>, visible_only: Option<bool>) -> Result<ResponseValue<ItemsList<T>>, Error<types::ListRowsResponse>> {
164 let url = format!("{}/docs/{}/tables/{}/rows", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name),);
165 let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
166 header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
167 let value_format = Some(T::value_format());
168 #[allow(unused_mut)]
169 let mut request = self
170 .client
171 .get(url)
172 .header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
173 .query(&progenitor_client::QueryParam::new("limit", &limit))
174 .query(&progenitor_client::QueryParam::new("pageToken", &page_token))
175 .query(&progenitor_client::QueryParam::new("query", &query))
176 .query(&progenitor_client::QueryParam::new("sortBy", &sort_by))
177 .query(&progenitor_client::QueryParam::new("syncToken", &sync_token))
178 .query(&progenitor_client::QueryParam::new("useColumnNames", &use_column_names))
179 .query(&progenitor_client::QueryParam::new("valueFormat", &value_format))
180 .query(&progenitor_client::QueryParam::new("visibleOnly", &visible_only))
181 .headers(header_map)
182 .build()?;
183 let info = OperationInfo {
184 operation_id: "list_rows_rich",
185 };
186 self.pre(&mut request, &info).await?;
187 let result = self.exec(request, &info).await;
188 self.post(&result, &info).await?;
189 let response = result?;
190 match response.status().as_u16() {
191 200u16 => ResponseValue::from_response(response).await,
192 400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
193 401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
194 403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
195 404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
196 429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
197 _ => Err(Error::UnexpectedResponse(response)),
198 }
199 }
200
201 pub async fn get_row_correct<'a, T: DeserializeOwned + ValueFormatProvider>(&'a self, doc_id: &'a str, table_id_or_name: &'a str, row_id_or_name: &'a str, use_column_names: Option<bool>) -> Result<ResponseValue<T>, Error<types::GetRowResponse>> {
225 let url = format!("{}/docs/{}/tables/{}/rows/{}", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name), encode_path(row_id_or_name),);
226 let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
227 header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
228 let value_format = Some(T::value_format());
229 #[allow(unused_mut)]
230 let mut request = self
231 .client
232 .get(url)
233 .header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
234 .query(&progenitor_client::QueryParam::new("useColumnNames", &use_column_names))
235 .query(&progenitor_client::QueryParam::new("valueFormat", &value_format))
236 .headers(header_map)
237 .build()?;
238 let info = OperationInfo {
239 operation_id: "get_row",
240 };
241 self.pre(&mut request, &info).await?;
242 let result = self.exec(request, &info).await;
243 self.post(&result, &info).await?;
244 let response = result?;
245 match response.status().as_u16() {
246 200u16 => ResponseValue::from_response(response).await,
247 401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
248 403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
249 404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
250 429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
251 _ => Err(Error::UnexpectedResponse(response)),
252 }
253 }
254
255 pub async fn upsert_rows_correct<'a>(&'a self, doc_id: &'a str, table_id_or_name: &'a str, disable_parsing: Option<bool>, body: &'a types::RowsUpsert) -> Result<ResponseValue<RowsUpsertResultCorrect>, Error<types::UpsertRowsResponse>> {
277 let url = format!("{}/docs/{}/tables/{}/rows", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name),);
278 let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
279 header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
280 #[allow(unused_mut)]
281 let mut request = self
282 .client
283 .post(url)
284 .header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
285 .json(&body)
286 .query(&progenitor_client::QueryParam::new("disableParsing", &disable_parsing))
287 .headers(header_map)
288 .build()?;
289 let info = OperationInfo {
290 operation_id: "upsert_rows",
291 };
292 self.pre(&mut request, &info).await?;
293 let result = self.exec(request, &info).await;
294 self.post(&result, &info).await?;
295 let response = result?;
296 match response.status().as_u16() {
297 202u16 => ResponseValue::from_response(response).await,
298 400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
299 401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
300 403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
301 404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
302 429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
303 _ => Err(Error::UnexpectedResponse(response)),
304 }
305 }
306
307 pub async fn update_row_correct<'a>(&'a self, doc_id: &'a str, table_id_or_name: &'a str, row_id_or_name: &'a str, disable_parsing: Option<bool>, body: &'a types::RowUpdate) -> Result<ResponseValue<RowUpdateResultCorrect>, Error<types::UpdateRowResponse>> {
334 let url = format!("{}/docs/{}/tables/{}/rows/{}", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name), encode_path(row_id_or_name),);
335 let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
336 header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
337 #[allow(unused_mut)]
338 let mut request = self
339 .client
340 .put(url)
341 .header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
342 .json(&body)
343 .query(&progenitor_client::QueryParam::new("disableParsing", &disable_parsing))
344 .headers(header_map)
345 .build()?;
346 let result = self.client.execute(request).await;
347 let response = result?;
348 match response.status().as_u16() {
349 202u16 => ResponseValue::from_response(response).await,
350 400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
351 401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
352 403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
353 404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
354 429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
355 _ => Err(Error::UnexpectedResponse(response)),
356 }
357 }
358}
359
360#[derive(Debug, Error)]
361pub enum ClientTablesError {
362 #[error("list tables request failed: {source}")]
363 ListTablesFailed {
364 #[source]
365 source: Error<ListTablesResponse>,
366 },
367 #[error("get table request failed: {source}")]
368 GetTableFailed {
369 #[source]
370 source: Error<GetTableResponse>,
371 },
372}
373
374#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
407#[serde(deny_unknown_fields)]
408pub struct RowUpdateResultCorrect {
409 #[serde(rename = "id")]
410 pub id: RowId,
411 #[serde(rename = "requestId")]
412 pub request_id: String,
413}
414
415#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
452#[serde(deny_unknown_fields)]
453pub struct RowsUpsertResultCorrect {
454 #[serde(rename = "addedRowIds", default)]
455 pub added_row_ids: Vec<String>,
456 #[serde(rename = "requestId")]
457 pub request_id: String,
458}
459
460pub fn format_row_url(doc_id: &str, table_id: &str, row_id: &str) -> String {
461 format!("https://coda.io/d/_d{doc_id}#_tu{table_id}/_ru{row_id}")
462}
463
464pub async fn paginate_all<T, R, F, Fut, E>(mut request_fn: F) -> Result<Vec<T>, E>
465where
466 T: Clone,
468 R: PaginatedResponse<T>,
469 F: FnMut(Option<String>) -> Fut,
470 Fut: Future<Output = Result<R, E>>,
471{
472 let mut all_items = Vec::new();
473 let mut pagination_state = PaginationState::new();
474
475 loop {
476 match request_fn(pagination_state.next_page_token.clone()).await {
477 Ok(response) => {
478 all_items.extend(response.items().iter().cloned());
479
480 if let Some(next_token) = response.next_page_token() {
481 pagination_state.next_page_token = Some(next_token.clone().into());
482 } else {
483 break;
484 }
485 }
486 Err(err) => return Err(err),
487 }
488 }
489
490 Ok(all_items)
491}