use crate::types::{Column, ColumnList, Doc, DocList, GetTableResponse, ListTablesResponse, NextPageToken, Row, RowList, TableList, TableReference};
use crate::{Error, RawClient, types};
use progenitor_client::{ClientHooks, ClientInfo, OperationInfo, ResponseValue, encode_path};
use serde::de::DeserializeOwned;
use thiserror::Error;
#[cfg(feature = "time")]
mod duration_value_parser;
mod impl_from_for_value;
mod items_list;
mod parse_cell_value;
mod parse_rich_value;
mod rich_rows;
mod row;
mod string_or_f64;
mod value_format_provider;
#[cfg(feature = "time")]
pub use duration_value_parser::*;
pub use items_list::*;
pub use parse_cell_value::*;
pub use parse_rich_value::*;
pub use rich_rows::*;
pub(crate) use string_or_f64::*;
pub use value_format_provider::*;
pub type DocId = String;
pub type TableId = String;
pub type RowId = String;
pub trait PaginatedResponse<T> {
fn items(&self) -> &Vec<T>;
fn next_page_token(&self) -> Option<&NextPageToken>;
fn into_items(self) -> Vec<T>;
}
pub struct PaginationState {
pub next_page_token: Option<String>,
}
impl PaginationState {
pub fn new() -> Self {
Self {
next_page_token: None,
}
}
pub fn update_from_response<T, R: PaginatedResponse<T>>(&mut self, response: &R) {
self.next_page_token = response.next_page_token().map(|token| token.clone().into());
}
pub fn has_more_pages(&self) -> bool {
self.next_page_token.is_some()
}
pub fn page_token(&self) -> Option<&str> {
self.next_page_token.as_deref()
}
}
impl Default for PaginationState {
fn default() -> Self {
Self::new()
}
}
impl PaginatedResponse<TableReference> for TableList {
fn items(&self) -> &Vec<TableReference> {
&self.items
}
fn next_page_token(&self) -> Option<&NextPageToken> {
self.next_page_token.as_ref()
}
fn into_items(self) -> Vec<TableReference> {
self.items
}
}
impl PaginatedResponse<Column> for ColumnList {
fn items(&self) -> &Vec<Column> {
&self.items
}
fn next_page_token(&self) -> Option<&NextPageToken> {
self.next_page_token.as_ref()
}
fn into_items(self) -> Vec<Column> {
self.items
}
}
impl PaginatedResponse<Doc> for DocList {
fn items(&self) -> &Vec<Doc> {
&self.items
}
fn next_page_token(&self) -> Option<&NextPageToken> {
self.next_page_token.as_ref()
}
fn into_items(self) -> Vec<Doc> {
self.items
}
}
impl PaginatedResponse<Row> for RowList {
fn items(&self) -> &Vec<Row> {
&self.items
}
fn next_page_token(&self) -> Option<&NextPageToken> {
self.next_page_token.as_ref()
}
fn into_items(self) -> Vec<Row> {
self.items
}
}
impl<T> PaginatedResponse<T> for ItemsList<T> {
fn items(&self) -> &Vec<T> {
&self.items
}
fn next_page_token(&self) -> Option<&NextPageToken> {
self.next_page_token.as_ref()
}
fn into_items(self) -> Vec<T> {
self.items
}
}
impl RawClient {
pub const BASE_URL: &'static str = "https://coda.io/apis/v1";
pub fn new_with_key(api_key: &str) -> reqwest::Result<Self> {
let authorization_header = format!("Bearer {api_key}")
.parse()
.expect("API key should be valid");
let mut headers = reqwest::header::HeaderMap::with_capacity(1);
headers.insert(reqwest::header::AUTHORIZATION, authorization_header);
let client_with_custom_defaults = reqwest::ClientBuilder::new()
.default_headers(headers)
.build()?;
let client = Self::new_with_client(Self::BASE_URL, client_with_custom_defaults);
Ok(client)
}
#[allow(clippy::too_many_arguments)]
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>> {
let url = format!("{}/docs/{}/tables/{}/rows", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name),);
let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
let value_format = Some(T::value_format());
#[allow(unused_mut)]
let mut request = self
.client
.get(url)
.header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
.query(&progenitor_client::QueryParam::new("limit", &limit))
.query(&progenitor_client::QueryParam::new("pageToken", &page_token))
.query(&progenitor_client::QueryParam::new("query", &query))
.query(&progenitor_client::QueryParam::new("sortBy", &sort_by))
.query(&progenitor_client::QueryParam::new("syncToken", &sync_token))
.query(&progenitor_client::QueryParam::new("useColumnNames", &use_column_names))
.query(&progenitor_client::QueryParam::new("valueFormat", &value_format))
.query(&progenitor_client::QueryParam::new("visibleOnly", &visible_only))
.headers(header_map)
.build()?;
let info = OperationInfo {
operation_id: "list_rows_rich",
};
self.pre(&mut request, &info).await?;
let result = self.exec(request, &info).await;
self.post(&result, &info).await?;
let response = result?;
match response.status().as_u16() {
200u16 => ResponseValue::from_response(response).await,
400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
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>> {
let url = format!("{}/docs/{}/tables/{}/rows/{}", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name), encode_path(row_id_or_name),);
let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
let value_format = Some(T::value_format());
#[allow(unused_mut)]
let mut request = self
.client
.get(url)
.header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
.query(&progenitor_client::QueryParam::new("useColumnNames", &use_column_names))
.query(&progenitor_client::QueryParam::new("valueFormat", &value_format))
.headers(header_map)
.build()?;
let info = OperationInfo {
operation_id: "get_row",
};
self.pre(&mut request, &info).await?;
let result = self.exec(request, &info).await;
self.post(&result, &info).await?;
let response = result?;
match response.status().as_u16() {
200u16 => ResponseValue::from_response(response).await,
401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
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>> {
let url = format!("{}/docs/{}/tables/{}/rows", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name),);
let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
#[allow(unused_mut)]
let mut request = self
.client
.post(url)
.header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
.json(&body)
.query(&progenitor_client::QueryParam::new("disableParsing", &disable_parsing))
.headers(header_map)
.build()?;
let info = OperationInfo {
operation_id: "upsert_rows",
};
self.pre(&mut request, &info).await?;
let result = self.exec(request, &info).await;
self.post(&result, &info).await?;
let response = result?;
match response.status().as_u16() {
202u16 => ResponseValue::from_response(response).await,
400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
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>> {
let url = format!("{}/docs/{}/tables/{}/rows/{}", self.baseurl, encode_path(doc_id), encode_path(table_id_or_name), encode_path(row_id_or_name),);
let mut header_map = ::reqwest::header::HeaderMap::with_capacity(1usize);
header_map.append(::reqwest::header::HeaderName::from_static("api-version"), ::reqwest::header::HeaderValue::from_static(Self::api_version()));
#[allow(unused_mut)]
let mut request = self
.client
.put(url)
.header(::reqwest::header::ACCEPT, ::reqwest::header::HeaderValue::from_static("application/json"))
.json(&body)
.query(&progenitor_client::QueryParam::new("disableParsing", &disable_parsing))
.headers(header_map)
.build()?;
let result = self.client.execute(request).await;
let response = result?;
match response.status().as_u16() {
202u16 => ResponseValue::from_response(response).await,
400u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
401u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
403u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
404u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
429u16 => Err(Error::ErrorResponse(ResponseValue::from_response(response).await?)),
_ => Err(Error::UnexpectedResponse(response)),
}
}
}
#[derive(Debug, Error)]
pub enum ClientTablesError {
#[error("list tables request failed: {source}")]
ListTablesFailed {
#[source]
source: Error<ListTablesResponse>,
},
#[error("get table request failed: {source}")]
GetTableFailed {
#[source]
source: Error<GetTableResponse>,
},
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(deny_unknown_fields)]
pub struct RowUpdateResultCorrect {
#[serde(rename = "id")]
pub id: RowId,
#[serde(rename = "requestId")]
pub request_id: String,
}
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(deny_unknown_fields)]
pub struct RowsUpsertResultCorrect {
#[serde(rename = "addedRowIds", default)]
pub added_row_ids: Vec<String>,
#[serde(rename = "requestId")]
pub request_id: String,
}
pub fn format_row_url(doc_id: &str, table_id: &str, row_id: &str) -> String {
format!("https://coda.io/d/_d{doc_id}#_tu{table_id}/_ru{row_id}")
}
pub async fn paginate_all<T, R, F, Fut, E>(mut request_fn: F) -> Result<Vec<T>, E>
where
T: Clone,
R: PaginatedResponse<T>,
F: FnMut(Option<String>) -> Fut,
Fut: Future<Output = Result<R, E>>,
{
let mut all_items = Vec::new();
let mut pagination_state = PaginationState::new();
loop {
match request_fn(pagination_state.next_page_token.clone()).await {
Ok(response) => {
all_items.extend(response.items().iter().cloned());
if let Some(next_token) = response.next_page_token() {
pagination_state.next_page_token = Some(next_token.clone().into());
} else {
break;
}
}
Err(err) => return Err(err),
}
}
Ok(all_items)
}