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}