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