smartsheet_rs/api.rs
1//! Smartsheet API v2 implementation in Rust
2//!
3use crate::auth::auth_token;
4use crate::builders::ParamBuilder;
5use crate::constants::{API_ENDPOINT, ENV_VAR_NAME};
6use crate::https::{get_https_client, tls};
7use crate::log::{debug, warn};
8use crate::models::*;
9use crate::status::raise_for_status;
10use crate::types::Result;
11use crate::utils::*;
12
13use std::io::{Error, ErrorKind};
14use std::time::Instant;
15
16use hyper::client::HttpConnector;
17use hyper::header::AUTHORIZATION;
18use hyper::{Body, Client, Method, Request};
19
20/// Client implementation for making requests to the *Smartsheet
21/// API v2*
22///
23/// # Links
24/// - [`smartsheet-rs`](https://docs.rs/smartsheet-rs)
25/// - [Official Documentation](https://smartsheet-platform.github.io/api-docs/)
26///
27pub struct SmartsheetApi<'a> {
28 bearer_token: String,
29 client: Client<tls::HttpsConnector<HttpConnector>>,
30 endpoint: &'a str,
31}
32
33impl<'a> SmartsheetApi<'a> {
34 /// Initialize a new `SmartsheetApi` object from an API access token.
35 pub fn from_token(token: &str) -> Self {
36 Self::new(API_ENDPOINT, token)
37 }
38
39 /// Initialize a new `SmartsheetApi` object from an API access token,
40 /// assuming this is currently set in the environment.
41 pub fn from_env() -> Result<Self> {
42 let token: String = match std::env::var(ENV_VAR_NAME) {
43 Ok(val) => Ok(val),
44 Err(_) => Err(Error::new(
45 ErrorKind::NotFound,
46 format!(
47 "Environment variable `{name}` must be set.",
48 name = ENV_VAR_NAME
49 ),
50 )),
51 }?;
52
53 Ok(Self::new(API_ENDPOINT, &token))
54 }
55
56 /// Initialize a new `SmartsheetApi` object from a (custom) base API
57 /// endpoint, and an access token.
58 pub fn from_endpoint_and_token(endpoint: &'a str, token: &str) -> Self {
59 Self::new(endpoint, token)
60 }
61
62 /// Constructor function, for internal use
63 fn new(endpoint: &'a str, token: &str) -> Self {
64 let bearer_token = auth_token(token);
65 let client = get_https_client();
66
67 Self {
68 bearer_token,
69 client,
70 endpoint,
71 }
72 }
73
74 /// **List Sheets** - Gets a list of all sheets that the user has access
75 /// to in alphabetical order by name. The list contains an abbreviated
76 /// Sheet object for each sheet.
77 ///
78 /// # Docs
79 /// - https://smartsheet-platform.github.io/api-docs/#list-sheets
80 ///
81 pub async fn list_sheets(&self) -> Result<IndexResult<Sheet>> {
82 self.list_sheets_with_params(None, None, None).await
83 }
84
85 /// **List Sheets** - Gets a list of all sheets that the user has access
86 /// to in alphabetical order by name, with included _query parameters_.
87 /// The list contains an abbreviated Sheet object for each sheet.
88 ///
89 /// # Arguments
90 ///
91 /// * `include` - A comma-separated list of elements to include in the response.
92 /// * `include_all` - If true, include all results (i.e. do not paginate).
93 /// * `modified_since` - Return sheets modified since a provided datetime.
94 /// Date should be in ISO-8601 format, for example,
95 /// `2020-01-30T13:25:32-07:00`.
96 ///
97 /// # Docs
98 /// - https://smartsheet-platform.github.io/api-docs/#list-sheets
99 ///
100 pub async fn list_sheets_with_params(
101 &self,
102 include: impl Into<Option<Vec<ListSheetIncludeFlags>>>,
103 include_all: impl Into<Option<bool>>,
104 modified_since: impl Into<Option<&'a str>>, // TODO change this to a DATE type maybe
105 ) -> Result<IndexResult<Sheet>> {
106 let mut url = format!("{}/{}", self.endpoint, "sheets");
107
108 ParamBuilder::new(&mut url)
109 .with_comma_separated_values("include", include.into())
110 .with_value("includeAll", include_all.into())
111 .with_value("modifiedSince", modified_since.into())
112 .build();
113
114 debug!("URL: {}", url);
115
116 let req = Request::get(&url)
117 .header(AUTHORIZATION, &self.bearer_token)
118 .body(Body::empty())?;
119
120 let mut res = self.client.request(req).await?;
121 raise_for_status(url, &mut res).await?;
122
123 let start = Instant::now();
124
125 let sheets = into_struct_from_slice(res).await?;
126
127 debug!("Deserialize: {:?}", start.elapsed());
128
129 Ok(sheets)
130 }
131
132 /// **Get Sheet** - Retrieves the specified sheet. Returns the sheet,
133 /// including rows, and optionally populated with discussion and
134 /// attachment objects.
135 ///
136 /// # Arguments
137 ///
138 /// * `sheet_id` - The Smartsheet to retrieve the rows and data for.
139 ///
140 /// # Docs
141 /// - https://smartsheet-platform.github.io/api-docs/#get-sheet
142 /// - https://smartsheet-platform.github.io/api-docs/#row-include-flags
143 ///
144 pub async fn get_sheet(&self, sheet_id: u64) -> Result<Sheet> {
145 self.get_sheet_with_params(sheet_id, None, None, None, None, None, None, None)
146 .await
147 }
148
149 /// **Get Sheet** - Retrieves the specified sheet. Returns the sheet,
150 /// including rows, and optionally populated with discussion and
151 /// attachment objects.
152 ///
153 /// # Note
154 ///
155 /// This is a convenience method to retrieve a Sheet with the `MULTI_CONTACT`
156 /// cell data correctly populated. This is primarily important so that we can
157 /// retrieve the email addresses for such cells, for example.
158 ///
159 /// # Arguments
160 ///
161 /// * `sheet_id` - The Smartsheet to retrieve the rows and data for.
162 ///
163 /// # Docs
164 /// - https://smartsheet-platform.github.io/api-docs/#get-sheet
165 /// - https://smartsheet-platform.github.io/api-docs/#row-include-flags
166 ///
167 pub async fn get_sheet_with_multi_contact_info(&self, sheet_id: u64) -> Result<Sheet> {
168 self.get_sheet_with_params(
169 sheet_id,
170 // TODO: maybe change the underlying type to `slice` instead of `vec`?
171 Some(vec![SheetIncludeFlags::Base(RowIncludeFlags::ObjectValue)]),
172 None,
173 None,
174 None,
175 None,
176 None,
177 Some(Level::MultiContact),
178 )
179 .await
180 }
181
182 /// **Get Sheet** - Retrieves the specified sheet, with included
183 /// _query parameters_. Returns the sheet, including rows, and optionally
184 /// populated with discussion and attachment objects.
185 ///
186 /// # Arguments
187 ///
188 /// * `sheet_id` - The Smartsheet to retrieve the rows and data for.
189 /// * `include` - A comma-separated list of elements to include in the response.
190 /// * `exclude` - A comma-separated list of elements to _not_ include in the response.
191 /// * `row_ids` - A comma-separated list of Row IDs on which to filter the
192 /// rows included in the result.
193 /// * `row_numbers` - A comma-separated list of Row numbers on which to
194 /// filter the rows included in the result. Non-existent
195 /// row numbers are ignored.
196 /// * `column_ids` - A comma-separated comma-separated list of Column IDs.
197 /// The response will contain only the specified columns
198 /// in the 'columns' array, and individual rows' 'cells'
199 /// array will only contain cells in the specified columns.
200 /// * `rows_modified_since` - Return rows modified since a provided datetime.
201 /// Date should be in ISO-8601 format, for example,
202 /// `2020-01-30T13:25:32-07:00`.
203 ///
204 /// # Docs
205 /// - https://smartsheet-platform.github.io/api-docs/#get-sheet
206 /// - https://smartsheet-platform.github.io/api-docs/#row-include-flags
207 ///
208 #[allow(clippy::too_many_arguments)]
209 pub async fn get_sheet_with_params(
210 &self,
211 sheet_id: u64,
212 include: impl Into<Option<Vec<SheetIncludeFlags>>>,
213 exclude: impl Into<Option<Vec<SheetExcludeFlags>>>,
214 row_ids: impl Into<Option<Vec<u64>>>,
215 row_numbers: impl Into<Option<Vec<u64>>>,
216 column_ids: impl Into<Option<Vec<u64>>>,
217 rows_modified_since: impl Into<Option<&'a str>>, // TODO change this to a date type maybe
218 level: impl Into<Option<Level>>,
219 ) -> Result<Sheet> {
220 let mut url = format!("{}/{}/{}", self.endpoint, "sheets", sheet_id);
221
222 ParamBuilder::new(&mut url)
223 .with_comma_separated_values("include", include.into())
224 .with_comma_separated_values("exclude", exclude.into())
225 .with_comma_separated_values("rowIds", row_ids.into())
226 .with_comma_separated_values("rowNumbers", row_numbers.into())
227 .with_comma_separated_values("columnIds", column_ids.into())
228 .with_value("rowsModifiedSince", rows_modified_since.into())
229 .with_value("level", level.into())
230 .build();
231
232 debug!("URL: {}", url);
233
234 let req = Request::get(&url)
235 .header(AUTHORIZATION, &self.bearer_token)
236 .body(Body::empty())?;
237
238 let mut res = self.client.request(req).await?;
239 raise_for_status(url, &mut res).await?;
240
241 let start = Instant::now();
242
243 // Note: I've timed the different methods for converting response data
244 // to a `struct` type, and found the buffered reader approach to work
245 // slightly better on average (at least on a Mac OS)
246
247 // 1. Bytes
248 #[cfg(feature = "serde-alloc")]
249 let sheet = into_struct_from_slice(res).await?;
250
251 // 2. String
252 // let sheet = into_struct_from_str(res).await?;
253
254 // 3. (Buffered) Reader
255 #[cfg(not(feature = "serde-alloc"))]
256 let sheet = resp_into_struct(res).await?;
257
258 debug!("Deserialize: {:?}", start.elapsed());
259
260 Ok(sheet)
261 }
262
263 /// **Get Row** - Retrieves the specified row from a sheet.
264 ///
265 /// # Arguments
266 ///
267 /// * `sheet_id` - The Smartsheet to retrieve the rows from.
268 /// * `row_id` - The specified row to retrieve.
269 ///
270 /// # Docs
271 /// - https://smartsheet-platform.github.io/api-docs/#get-row
272 ///
273 pub async fn get_row(&self, sheet_id: u64, row_id: u64) -> Result<Row> {
274 self.get_row_with_params(sheet_id, row_id, None, None, None)
275 .await
276 }
277
278 /// **Get Row** - Retrieves the specified row from a sheet, with included _column data_.
279 ///
280 /// # Arguments
281 ///
282 /// * `sheet_id` - The Smartsheet to retrieve the rows from.
283 /// * `row_id` - The specified row to retrieve.
284 ///
285 /// # Docs
286 /// - https://smartsheet-platform.github.io/api-docs/#get-row
287 ///
288 pub async fn get_row_with_column_data(&self, sheet_id: u64, row_id: u64) -> Result<Row> {
289 let include_flags = Some(vec![RowIncludeFlags::Columns]);
290 self.get_row_with_params(sheet_id, row_id, include_flags, None, None)
291 .await
292 }
293
294 /// **Get Row** - Retrieves the specified row from a sheet, with included
295 /// _Multi-contact data_.
296 ///
297 /// # Note
298 ///
299 /// This is a convenience method to retrieve a Row with the `MULTI_CONTACT`
300 /// cell data correctly populated. This is primarily important so that we can
301 /// retrieve the email addresses for such cells, for example.
302 ///
303 /// # Arguments
304 ///
305 /// * `sheet_id` - The Smartsheet to retrieve the rows from.
306 /// * `row_id` - The specified row to retrieve.
307 ///
308 /// # Docs
309 /// - https://smartsheet-platform.github.io/api-docs/#get-row
310 ///
311 pub async fn get_row_with_multi_contact_info(&self, sheet_id: u64, row_id: u64) -> Result<Row> {
312 self.get_row_with_params(
313 sheet_id,
314 row_id,
315 Some(vec![RowIncludeFlags::ObjectValue]),
316 None,
317 Some(Level::MultiContact),
318 )
319 .await
320 }
321
322 /// **Get Row** - Retrieves the specified row from a sheet, with included _query parameters_.
323 ///
324 /// # Arguments
325 ///
326 /// * `sheet_id` - The Smartsheet to retrieve the rows from.
327 /// * `row_id` - The specified row to retrieve.
328 /// * `include` - A comma-separated list of elements to include in the response.
329 /// * `exclude` - A comma-separated list of elements to _not_ include in the response.
330 /// * `level` - Specifies whether multi-contact data is returned in a
331 /// backwards-compatible, text format, or as multi-contact data.
332 ///
333 /// # Docs
334 /// - https://smartsheet-platform.github.io/api-docs/#get-row
335 /// - https://smartsheet-platform.github.io/api-docs/#row-include-flags
336 ///
337 pub async fn get_row_with_params(
338 &self,
339 sheet_id: u64,
340 row_id: u64,
341 include: impl Into<Option<Vec<RowIncludeFlags>>>,
342 exclude: impl Into<Option<Vec<RowExcludeFlags>>>,
343 level: impl Into<Option<Level>>,
344 ) -> Result<Row> {
345 let mut url: String = format!(
346 "{}/{}/{}/{}/{}",
347 self.endpoint, "sheets", sheet_id, "rows", row_id
348 );
349
350 ParamBuilder::new(&mut url)
351 .with_comma_separated_values("include", include.into())
352 .with_comma_separated_values("exclude", exclude.into())
353 .with_value("level", level.into())
354 .build();
355
356 debug!("URL: {}", url);
357
358 let req = Request::get(&url)
359 .header(AUTHORIZATION, &self.bearer_token)
360 .body(Body::empty())?;
361
362 let mut res = self.client.request(req).await?;
363 raise_for_status(url, &mut res).await?;
364
365 let start = Instant::now();
366
367 // asynchronously aggregate the chunks of the body
368 let row = into_struct_from_slice(res).await?;
369
370 debug!("Deserialize: {:?}", start.elapsed());
371
372 Ok(row)
373 }
374
375 /// **Add Rows** - Inserts one or more rows into the sheet.
376 ///
377 /// If you want to insert the rows in any position but the default, use
378 /// [location-specifier attributes].
379 ///
380 /// [location-specifier attributes]: https://smartsheet.redoc.ly/tag/rowsRelated#section/Specify-Row-Location
381 ///
382 /// # Arguments
383 ///
384 /// * `sheet_id` - The Smartsheet to add the rows to.
385 /// * `rows` - An array (list) of new Rows with the cell values to add.
386 ///
387 /// # Docs
388 /// - <https://smartsheet.redoc.ly/#operation/rows-addToSheet>
389 ///
390 pub async fn add_rows(&self, sheet_id: u64, rows: impl Into<Vec<Row>>) -> Result<RowResult> {
391 self.add_rows_with_params(sheet_id, rows, None, None).await
392 }
393
394 /// **Add Rows** - Inserts one or more rows into the sheet, with included
395 /// _query parameters_.
396 ///
397 /// If you want to insert the rows in any position but the default, use
398 /// [location-specifier attributes].
399 ///
400 /// [location-specifier attributes]: https://smartsheet.redoc.ly/tag/rowsRelated#section/Specify-Row-Location
401 ///
402 /// # Arguments
403 ///
404 /// * `sheet_id` - The Smartsheet to add the rows to.
405 /// * `rows` - An array (list) of new Rows with the cell values to add.
406 /// * `allow_partial_success` - Default: `false`. When specified with a value
407 /// of `true`, enables partial success for this bulk operation.
408 /// See [Partial Success] for more information.
409 /// * `override_validation` - Default: `false`. If set to a value of `true`,
410 /// allows a cell value outside of the validation limits. You
411 /// must also specify **strict** on a per-cell level with a
412 /// value of **false** to bypass value type checking.
413 ///
414 /// [Partial Success]: https://smartsheet.redoc.ly/#section/Work-at-Scale/Bulk-Operations
415 ///
416 /// # Docs
417 /// - <https://smartsheet.redoc.ly/#operation/rows-addToSheet>
418 ///
419 pub async fn add_rows_with_params(
420 &self,
421 sheet_id: u64,
422 rows: impl Into<Vec<Row>>,
423 allow_partial_success: impl Into<Option<bool>>,
424 override_validation: impl Into<Option<bool>>,
425 ) -> Result<RowResult> {
426 self.add_or_update_rows(
427 Method::POST,
428 sheet_id,
429 rows,
430 allow_partial_success.into(),
431 override_validation.into(),
432 )
433 .await
434 }
435
436 /// **Update Rows** - Updates cell values in the specified rows,
437 /// expands/collapses the specified rows, and/or modifies the position of
438 /// specified rows (including indenting/outdenting). For detailed
439 /// information about changing row positions, see
440 /// [location-specifier attributes].
441 ///
442 /// [location-specifier attributes]: https://smartsheet.redoc.ly/tag/rowsRelated#section/Specify-Row-Location
443 ///
444 /// # Arguments
445 ///
446 /// * `sheet_id` - The Smartsheet to update the rows in.
447 /// * `rows` - An array (list) of Rows with the updated cell values.
448 ///
449 /// # Docs
450 /// - <https://smartsheet.redoc.ly/#operation/update-rows>
451 ///
452 pub async fn update_rows(&self, sheet_id: u64, rows: impl Into<Vec<Row>>) -> Result<RowResult> {
453 self.update_rows_with_params(sheet_id, rows, None, None)
454 .await
455 }
456
457 /// **Update Rows** - Updates cell values in the specified rows,
458 /// with included _query parameters_.
459 ///
460 /// Alternatively, expands/collapses the specified rows, and/or modifies
461 /// the position of specified rows (including indenting/outdenting). For
462 /// detailed information about changing row positions, see
463 /// [location-specifier attributes].
464 ///
465 /// [location-specifier attributes]: https://smartsheet.redoc.ly/tag/rowsRelated#section/Specify-Row-Location
466 ///
467 /// # Arguments
468 ///
469 /// * `sheet_id` - The Smartsheet to update the rows in.
470 /// * `rows` - An array (list) of Rows with the updated cell values.
471 /// * `allow_partial_success` - When specified with a value of `true`, enables
472 /// partial success for this bulk operation. See [Partial
473 /// Success] for more information.
474 /// * `override_validation` - If set to a value of `true`, allows a cell value
475 /// outside of the validation limits. You must also specify **strict**
476 /// on a per-cell level with a value of **false** to bypass value
477 /// type checking.
478 ///
479 /// [Partial Success]: https://smartsheet.redoc.ly/#section/Work-at-Scale/Bulk-Operations
480 ///
481 /// # Docs
482 /// - <https://smartsheet.redoc.ly/#operation/update-rows>
483 ///
484 pub async fn update_rows_with_params(
485 &self,
486 sheet_id: u64,
487 rows: impl Into<Vec<Row>>,
488 allow_partial_success: impl Into<Option<bool>>,
489 override_validation: impl Into<Option<bool>>,
490 ) -> Result<RowResult> {
491 self.add_or_update_rows(
492 Method::PUT,
493 sheet_id,
494 rows,
495 allow_partial_success.into(),
496 override_validation.into(),
497 )
498 .await
499 }
500
501 /// Internal method to *add* or *update* rows in a sheet.
502 pub(crate) async fn add_or_update_rows(
503 &self,
504 method: hyper::Method,
505 sheet_id: u64,
506 rows: impl Into<Vec<Row>>,
507 allow_partial_success: Option<bool>,
508 override_validation: Option<bool>,
509 ) -> Result<RowResult> {
510 // The endpoint to ADD or UPDATE rows is the same.
511 let mut url: String = format!("{}/{}/{}/{}", self.endpoint, "sheets", sheet_id, "rows");
512
513 ParamBuilder::new(&mut url)
514 .with_value("allowPartialSuccess", allow_partial_success)
515 .with_value("overrideValidation", override_validation)
516 .build();
517
518 debug!("URL: {}", url);
519
520 let data = serde_json::to_vec(&rows.into())?;
521
522 let req = Request::builder()
523 .method(method)
524 .uri(&url)
525 .header(AUTHORIZATION, &self.bearer_token)
526 .body(Body::from(data))?;
527
528 let mut res = self.client.request(req).await?;
529 raise_for_status(url, &mut res).await?;
530
531 let start = Instant::now();
532
533 // asynchronously aggregate the chunks of the body
534 let result = into_struct_from_slice(res).await?;
535
536 debug!("Deserialize: {:?}", start.elapsed());
537
538 Ok(result)
539 }
540
541 /// **Delete Rows** - Deletes one or more specified rows from the sheet.
542 ///
543 /// # Arguments
544 ///
545 /// * `sheet_id` - The Smartsheet to delete the rows from.
546 /// * `row_ids` - An array (list) containing the IDs of the Rows to
547 /// delete from the smartsheet.
548 ///
549 /// # Docs
550 /// - <https://smartsheet.redoc.ly/#operation/delete-rows>
551 ///
552 pub async fn delete_rows<const N: usize>(
553 &self,
554 sheet_id: u64,
555 row_ids: impl Into<[u64; N]>,
556 ) -> Result<RowResult<u64>> {
557 self.delete_rows_with_params(sheet_id, row_ids, None).await
558 }
559
560 /// **Delete Rows** - Deletes one or more specified rows from the sheet,
561 /// with included _query parameters_.
562 ///
563 /// # Arguments
564 ///
565 /// * `sheet_id` - The Smartsheet to delete the rows from.
566 /// * `row_ids` - An array (list) containing the IDs of the Rows to
567 /// delete from the smartsheet.
568 /// * `ignore_rows_not_found` - Default: `false`. If set to `false` and any of
569 /// the specified Row IDs are not found, no rows are deleted,
570 /// and the "not found" error is returned.
571 ///
572 /// # Docs
573 /// - <https://smartsheet.redoc.ly/#operation/delete-rows>
574 ///
575 pub async fn delete_rows_with_params<const N: usize>(
576 &self,
577 sheet_id: u64,
578 row_ids: impl Into<[u64; N]>,
579 ignore_rows_not_found: impl Into<Option<bool>>,
580 ) -> Result<RowResult<u64>> {
581 // The endpoint to ADD or UPDATE rows is the same.
582 let mut url: String = format!("{}/{}/{}/{}", self.endpoint, "sheets", sheet_id, "rows");
583
584 ParamBuilder::new(&mut url)
585 .with_array("ids", row_ids.into())
586 .with_value("ignoreRowsNotFound", ignore_rows_not_found.into())
587 .build();
588
589 debug!("URL: {}", url);
590
591 let req = Request::delete(&url)
592 .header(AUTHORIZATION, &self.bearer_token)
593 .body(Body::empty())?;
594
595 let mut res = self.client.request(req).await?;
596 raise_for_status(url, &mut res).await?;
597
598 let start = Instant::now();
599
600 // asynchronously aggregate the chunks of the body
601 let result = into_struct_from_slice(res).await?;
602
603 debug!("Deserialize: {:?}", start.elapsed());
604
605 Ok(result)
606 }
607
608 /// **List Columns** - Gets a list of all columns belonging to the specified sheet.
609 ///
610 /// # Docs
611 /// - https://smartsheet-platform.github.io/api-docs/#list-columns
612 ///
613 pub async fn list_columns(&self, sheet_id: u64) -> Result<IndexResult<Column>> {
614 self.list_columns_with_params(sheet_id, None, None, None)
615 .await
616 }
617
618 /// **List Columns** - Gets a list of all columns belonging to the
619 /// specified sheet, with included _query parameters_.
620 ///
621 /// # Arguments
622 ///
623 /// * `sheet_id` - The Smartsheet to retrieve the columns from.
624 /// * `level` - Specifies whether multi-contact data is returned in a
625 /// backwards-compatible, text format, or as multi-contact data.
626 /// * `include` - A comma-separated list of elements to include in the response.
627 /// * `include_all` - If true, include all results (i.e. do not paginate).
628 ///
629 /// # Docs
630 /// - https://smartsheet-platform.github.io/api-docs/#list-columns
631 ///
632 pub async fn list_columns_with_params(
633 &self,
634 sheet_id: u64,
635 level: impl Into<Option<Level>>,
636 include: impl Into<Option<Vec<ColumnIncludeFlags>>>,
637 include_all: impl Into<Option<bool>>,
638 ) -> Result<IndexResult<Column>> {
639 let mut url = format!("{}/{}/{}/{}", self.endpoint, "sheets", sheet_id, "columns");
640
641 ParamBuilder::new(&mut url)
642 .with_value("level", level.into())
643 .with_comma_separated_values("include", include.into())
644 .with_value("includeAll", include_all.into())
645 .build();
646
647 debug!("URL: {}", url);
648
649 let req = Request::get(&url)
650 .header(AUTHORIZATION, &self.bearer_token)
651 .body(Body::empty())?;
652
653 let mut res = self.client.request(req).await?;
654 raise_for_status(url, &mut res).await?;
655
656 let start = Instant::now();
657
658 let columns = into_struct_from_slice(res).await?;
659
660 debug!("Deserialize: {:?}", start.elapsed());
661
662 Ok(columns)
663 }
664
665 /// **Get Column** - Retrieves a column by *id* from the specified sheet.
666 ///
667 /// # Arguments
668 ///
669 /// * `sheet_id` - The Smartsheet to retrieve the column for.
670 /// * `column_id` - The Column Id to retrieve the data for.
671 ///
672 /// # Docs
673 /// - https://smartsheet-platform.github.io/api-docs/#get-column
674 ///
675 pub async fn get_column(&self, sheet_id: u64, column_id: u64) -> Result<Column> {
676 self.get_column_with_params(sheet_id, column_id, None, None)
677 .await
678 }
679
680 /// **Get Column** - Retrieves a column by *id* from the specified sheet,
681 /// with included _query parameters_.
682 ///
683 /// # Arguments
684 ///
685 /// * `sheet_id` - The Smartsheet to retrieve the column for.
686 /// * `column_id` - The Column Id to retrieve the data for.
687 /// * `level` - Specifies whether multi-contact data is returned in a
688 /// backwards-compatible, text format, or as multi-contact data.
689 /// * `include` - A comma-separated list of elements to include in the response.
690 ///
691 /// # Docs
692 /// - https://smartsheet-platform.github.io/api-docs/#get-column
693 ///
694 pub async fn get_column_with_params(
695 &self,
696 sheet_id: u64,
697 column_id: u64,
698 level: impl Into<Option<Level>>,
699 include: impl Into<Option<Vec<ColumnIncludeFlags>>>,
700 ) -> Result<Column> {
701 let mut url = format!(
702 "{}/{}/{}/{}/{}",
703 self.endpoint, "sheets", sheet_id, "columns", column_id
704 );
705
706 ParamBuilder::new(&mut url)
707 .with_value("level", level.into())
708 .with_comma_separated_values("include", include.into())
709 .build();
710
711 debug!("URL: {}", url);
712
713 let req = Request::get(&url)
714 .header(AUTHORIZATION, &self.bearer_token)
715 .body(Body::empty())?;
716
717 let mut res = self.client.request(req).await?;
718 raise_for_status(url, &mut res).await?;
719
720 let start = Instant::now();
721
722 let column = into_struct_from_slice(res).await?;
723
724 debug!("Deserialize: {:?}", start.elapsed());
725
726 Ok(column)
727 }
728
729 /// **List Attachments** - Gets a list of all attachments that are on the
730 /// sheet, including sheet, row, and discussion-level attachments.
731 ///
732 /// # Arguments
733 ///
734 /// * `sheet_id` - The Smartsheet to retrieve the attachments for.
735 ///
736 /// # Docs
737 /// - https://smartsheet-platform.github.io/api-docs/#list-attachments
738 ///
739 pub async fn list_attachments(&self, sheet_id: u64) -> Result<IndexResult<AttachmentMeta>> {
740 let url = format!(
741 "{}/{}/{}/{}",
742 self.endpoint, "sheets", sheet_id, "attachments"
743 );
744
745 debug!("URL: {}", url);
746
747 let req = Request::get(&url)
748 .header(AUTHORIZATION, &self.bearer_token)
749 .body(Body::empty())?;
750
751 let mut res = self.client.request(req).await?;
752 raise_for_status(url, &mut res).await?;
753
754 let start = Instant::now();
755
756 let attachments = into_struct_from_slice(res).await?;
757
758 debug!("Deserialize: {:?}", start.elapsed());
759
760 Ok(attachments)
761 }
762
763 /// **Get Attachment** - Retrieves an attachment by *id* from the
764 /// specified sheet.
765 ///
766 /// # Notes
767 ///
768 /// Fetches a temporary URL that allows you to download an attachment. The
769 /// `urlExpiresInMillis` attribute tells you how long the URL is valid.
770 ///
771 /// # Arguments
772 ///
773 /// * `sheet_id` - The Smartsheet to retrieve the attachments for.
774 /// * `attachment_id` - The Attachment Id to retrieve the data for.
775 ///
776 /// # Docs
777 /// - https://smartsheet-platform.github.io/api-docs/#get-attachment
778 ///
779 pub async fn get_attachment(&self, sheet_id: u64, attachment_id: u64) -> Result<Attachment> {
780 let url = format!(
781 "{}/{}/{}/{}/{}",
782 self.endpoint, "sheets", sheet_id, "attachments", attachment_id
783 );
784
785 debug!("URL: {}", url);
786
787 let req = Request::get(&url)
788 .header(AUTHORIZATION, &self.bearer_token)
789 .body(Body::empty())?;
790
791 let mut res = self.client.request(req).await?;
792 raise_for_status(url, &mut res).await?;
793
794 let start = Instant::now();
795
796 let attachment = into_struct_from_slice(res).await?;
797
798 debug!("Deserialize: {:?}", start.elapsed());
799
800 Ok(attachment)
801 }
802
803 /// **Get Sheet By Name** - Convenience function to retrieve a specified
804 /// sheet by name. Used for those times when you don't know the Sheet Id.
805 ///
806 /// This will internally call `list_sheets` and then filter the response
807 /// data by the sheet name. It returns the first matching name.
808 ///
809 /// Returns the sheet, including rows, and optionally populated with
810 /// discussion and attachment objects.
811 ///
812 /// # Arguments
813 ///
814 /// * `sheet_name` - The name of the Smartsheet to filter results by.
815 ///
816 #[deprecated(
817 since = "0.2.0",
818 note = "please cache the sheet id and use `get_sheet` instead"
819 )]
820 pub async fn get_sheet_by_name(&self, sheet_name: &'a str) -> Result<Sheet> {
821 // Display a warning that the usage of this method is not recommended
822 warn!(
823 "{}",
824 "Calling `get_sheet_by_name()` is not recommended; it's \
825 preferable to cache the sheet ID and call \
826 `get_sheet()` instead."
827 );
828
829 // Get a fresh list of sheets
830 let result = self.list_sheets_with_params(None, Some(true), None).await?;
831
832 // Find the sheet by the provided name
833 return match result
834 .data
835 .into_iter()
836 .find(|sheet| sheet.name == sheet_name)
837 {
838 Some(sheet) => Ok(sheet),
839 None => Err(Box::from(Error::new(
840 ErrorKind::NotFound,
841 format!("The provided sheet `{}` was not found", sheet_name),
842 ))),
843 };
844 }
845
846 /// **Get Column By Title** - Convenience function to retrieve a specified
847 /// column by title (name). Used for those times when you don't know the
848 /// Column Id.
849 ///
850 /// This will internally call `list_columns` and then filter the response
851 /// data by the column title. It returns the first matching name.
852 ///
853 /// # Arguments
854 ///
855 /// * `sheet_id` - The Smartsheet to retrieve the column from.
856 /// * `column_title` - The name of the column to filter results by.
857 ///
858 ///
859 #[deprecated(
860 since = "0.2.0",
861 note = "please cache the column id and use `get_column` instead"
862 )]
863 pub async fn get_column_by_title(
864 &self,
865 sheet_id: u64,
866 column_title: &'a str,
867 ) -> Result<Column> {
868 // Display a warning that the usage of this method is not recommended
869 warn!(
870 "{}",
871 "Calling `get_column_by_title()` is not recommended; it's \
872 preferable to cache the column ID and call \
873 `get_column()` instead."
874 );
875
876 // Get a fresh list of columns
877 let result = self
878 .list_columns_with_params(sheet_id, None, None, Some(true))
879 .await?;
880
881 // Find the column by the provided name
882 return match result
883 .data
884 .into_iter()
885 .find(|column| column.title == column_title)
886 {
887 Some(column) => Ok(column),
888 None => Err(Box::from(Error::new(
889 ErrorKind::NotFound,
890 format!("The provided column `{}` was not found", column_title),
891 ))),
892 };
893 }
894}