1use csv::{ReaderBuilder, StringRecord, Writer, WriterBuilder};
2use google_sheets4::{
3 api::{ClearValuesRequest, ValueRange},
4 Sheets,
5};
6use serde::de::DeserializeOwned;
7use std::path::PathBuf;
8use thiserror::Error;
9use yup_oauth2::{ServiceAccountAuthenticator, ServiceAccountKey};
10
11#[derive(Error, Debug)]
12pub enum SheetsError {
13 #[error("SERVICE_ACCOUNT_JSON not defined")]
14 EnvVarNotFound(#[from] std::env::VarError),
15
16 #[error("Invalid service account JSON")]
17 InvalidServiceAccountJSON(#[from] serde_json::Error),
18
19 #[error("Error with token cache path")]
20 TokenCachePathError(#[from] std::io::Error),
21
22 #[error(transparent)]
23 SheetsError(#[from] google_sheets4::Error),
24
25 #[error(transparent)]
26 CSVError(#[from] csv::Error),
27
28 #[error("Internal error")]
29 InternalUTFError(#[from] std::string::FromUtf8Error),
30
31 #[error("Internal error")]
32 InternalWriterError(#[from] csv::IntoInnerError<Writer<Vec<u8>>>),
33}
34
35pub fn service_account_from_env() -> Result<ServiceAccountKey, SheetsError> {
37 let env = std::env::var("SERVICE_ACCOUNT_JSON")?;
38 let key = serde_json::from_str(&env)?;
39 Ok(key)
40}
41
42pub async fn get_sheets<P: Into<PathBuf>>(
45 service_account: ServiceAccountKey,
46 token_cache_path: Option<P>,
47) -> Result<Sheets, SheetsError> {
48 let builder = ServiceAccountAuthenticator::builder(service_account);
49 let auth = if let Some(path) = token_cache_path {
50 builder.persist_tokens_to_disk(path).build().await?
51 } else {
52 builder.build().await?
53 };
54 let connector = hyper_rustls::HttpsConnectorBuilder::new()
55 .with_native_roots()
56 .https_only()
57 .enable_http1()
58 .enable_http2()
59 .build();
60 let sheets = Sheets::new(hyper::Client::builder().build(connector), auth);
61 Ok(sheets)
62}
63
64pub async fn clear_tab(
66 sheets: &mut Sheets,
67 document_id: &str,
68 tab_name: &str,
69) -> Result<(), SheetsError> {
70 sheets
71 .spreadsheets()
72 .values_clear(ClearValuesRequest::default(), document_id, tab_name)
73 .doit()
74 .await?;
75 Ok(())
76}
77
78pub async fn write_page(
81 sheets: &mut Sheets,
82 document_id: &str,
83 tab_name: &str,
84 objects: &[impl serde::Serialize],
85) -> Result<(), SheetsError> {
86 clear_tab(sheets, document_id, tab_name).await?;
87
88 let mut wtr = WriterBuilder::new().from_writer(vec![]);
89
90 for obj in objects {
91 wtr.serialize(&obj)?;
92 }
93
94 let data = String::from_utf8(wtr.into_inner()?)?;
95
96 let mut rdr = ReaderBuilder::new()
97 .has_headers(false)
98 .from_reader(data.as_bytes());
99 let records = rdr
100 .records()
101 .collect::<Result<Vec<StringRecord>, csv::Error>>()?;
102
103 let req = ValueRange {
104 major_dimension: None,
105 range: Some(tab_name.to_string()),
106 values: Some(
107 records
108 .into_iter()
109 .map(|s| s.iter().map(|s| s.to_string()).collect())
110 .collect(),
111 ),
112 };
113
114 sheets
115 .spreadsheets()
116 .values_update(req, document_id, tab_name)
117 .value_input_option("USER_ENTERED")
118 .include_values_in_response(false)
119 .doit()
120 .await?;
121
122 Ok(())
123}
124
125pub async fn append_row(
127 sheets: &mut Sheets,
128 document_id: &str,
129 tab_name: &str,
130 obj: impl serde::Serialize,
131) -> Result<(), SheetsError> {
132 let mut wtr = WriterBuilder::new().from_writer(vec![]);
133
134 wtr.serialize(&obj)?;
135
136 let data = String::from_utf8(wtr.into_inner()?)?;
137
138 let mut rdr = ReaderBuilder::new()
139 .has_headers(true)
140 .from_reader(data.as_bytes());
141
142 let records = rdr
143 .records()
144 .collect::<Result<Vec<StringRecord>, csv::Error>>()?;
145
146 let req = ValueRange {
147 major_dimension: None,
148 range: Some(tab_name.to_string()),
149 values: Some(
150 records
151 .into_iter()
152 .map(|s| s.iter().map(|s| s.to_string()).collect())
153 .collect(),
154 ),
155 };
156
157 sheets
158 .spreadsheets()
159 .values_append(req, document_id, tab_name)
160 .value_input_option("USER_ENTERED")
161 .include_values_in_response(false)
162 .doit()
163 .await?;
164
165 Ok(())
166}
167
168pub async fn read_all<T: DeserializeOwned>(
170 sheets: &mut Sheets,
171 document_id: &str,
172 tab_name: &str,
173) -> Result<Vec<T>, SheetsError> {
174 let (_body, value_range) = sheets
175 .spreadsheets()
176 .values_get(document_id, tab_name)
177 .doit()
178 .await?;
179
180 let rows = value_range.values.unwrap();
181 let mut wtr = WriterBuilder::new().from_writer(vec![]);
182
183 for row in rows {
184 if let Err(e) = wtr.write_record(&row) {
185 println!("error writing row- {:?}", e);
186 };
187 }
188
189 let data = String::from_utf8(wtr.into_inner()?)?;
190
191 let mut rdr = ReaderBuilder::new()
192 .has_headers(true)
193 .from_reader(data.as_bytes());
194
195 let mut records = vec![];
196 for result in rdr.deserialize() {
197 match result {
198 Ok(r) => records.push(r),
199 Err(e) => {
200 println!("error deserializing row: {:?}", e);
201 }
202 }
203 }
204
205 Ok(records)
206}