Skip to main content

strava_wrapper/filters/
uploads.rs

1use crate::models::Upload;
2use crate::query::{
3    get_with_query_and_path, post_multipart, Endpoint, ErrorWrapper, PathQuery, Query, Sendable, ID,
4};
5use async_trait::async_trait;
6use reqwest::multipart::{Form, Part};
7use std::collections::HashMap;
8use strava_wrapper_macros::{Endpoint, PathQuery, Query, ID};
9
10/// Strava-accepted upload file formats.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12#[non_exhaustive]
13pub enum UploadDataType {
14    Fit,
15    FitGz,
16    Tcx,
17    TcxGz,
18    Gpx,
19    GpxGz,
20}
21
22impl UploadDataType {
23    pub fn as_str(self) -> &'static str {
24        match self {
25            Self::Fit => "fit",
26            Self::FitGz => "fit.gz",
27            Self::Tcx => "tcx",
28            Self::TcxGz => "tcx.gz",
29            Self::Gpx => "gpx",
30            Self::GpxGz => "gpx.gz",
31        }
32    }
33}
34
35/// Builder for `POST /uploads`. Submits an activity file and returns an
36/// [`Upload`] record. The `id` on the response can then be polled via
37/// [`GetUpload`] to check processing progress.
38#[must_use = "this request is not executed until you call .send().await"]
39pub struct UploadActivity {
40    url: String,
41    token: String,
42    file: Vec<u8>,
43    data_type: UploadDataType,
44    name: Option<String>,
45    description: Option<String>,
46    trainer: Option<bool>,
47    commute: Option<bool>,
48    external_id: Option<String>,
49}
50
51impl UploadActivity {
52    pub fn new(
53        url: impl Into<String>,
54        token: impl Into<String>,
55        file: Vec<u8>,
56        data_type: UploadDataType,
57    ) -> Self {
58        Self {
59            url: url.into(),
60            token: token.into(),
61            file,
62            data_type,
63            name: None,
64            description: None,
65            trainer: None,
66            commute: None,
67            external_id: None,
68        }
69    }
70
71    pub fn name(mut self, name: impl Into<String>) -> Self {
72        self.name = Some(name.into());
73        self
74    }
75
76    pub fn description(mut self, description: impl Into<String>) -> Self {
77        self.description = Some(description.into());
78        self
79    }
80
81    pub fn trainer(mut self, trainer: bool) -> Self {
82        self.trainer = Some(trainer);
83        self
84    }
85
86    pub fn commute(mut self, commute: bool) -> Self {
87        self.commute = Some(commute);
88        self
89    }
90
91    pub fn external_id(mut self, id: impl Into<String>) -> Self {
92        self.external_id = Some(id.into());
93        self
94    }
95}
96
97#[async_trait]
98impl Sendable<Upload> for UploadActivity {
99    async fn send(self) -> Result<Upload, ErrorWrapper> {
100        let endpoint = format!("{}/v3/uploads", self.url);
101
102        let mut form = Form::new()
103            .part(
104                "file",
105                Part::bytes(self.file).file_name(format!("upload.{}", self.data_type.as_str())),
106            )
107            .text("data_type", self.data_type.as_str().to_string());
108
109        if let Some(name) = self.name {
110            form = form.text("name", name);
111        }
112        if let Some(description) = self.description {
113            form = form.text("description", description);
114        }
115        if let Some(trainer) = self.trainer {
116            form = form.text("trainer", (trainer as u8).to_string());
117        }
118        if let Some(commute) = self.commute {
119            form = form.text("commute", (commute as u8).to_string());
120        }
121        if let Some(external_id) = self.external_id {
122            form = form.text("external_id", external_id);
123        }
124
125        post_multipart(&endpoint, &self.token, form).await
126    }
127}
128
129#[derive(Debug, Clone, Endpoint, Query, PathQuery, ID)]
130#[must_use = "this request is not executed until you call .send().await"]
131pub struct GetUpload {
132    url: String,
133    token: String,
134    path: String,
135    query: Vec<(String, String)>,
136    path_params: Vec<(String, String)>,
137}
138
139#[async_trait]
140impl Sendable<Upload> for GetUpload {
141    async fn send(self) -> Result<Upload, ErrorWrapper> {
142        let token = self.token.clone();
143        get_with_query_and_path(self, &token).await
144    }
145}