trackme_backends/
lib.rs

1// Copyright (C) Robin Krahl <robin.krahl@ireas.org>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#![warn(
5    missing_copy_implementations,
6    missing_debug_implementations,
7    missing_docs,
8    non_ascii_idents,
9    trivial_casts,
10    unused,
11    unused_qualifications,
12    clippy::expect_used,
13    clippy::unwrap_used,
14    clippy::panic
15)]
16#![deny(unsafe_code)]
17
18//! Upload activities to analysis platforms.
19
20use std::{
21    error,
22    fmt::{self, Display, Formatter},
23    path::PathBuf,
24    result,
25};
26
27#[cfg(feature = "intervals_icu")]
28pub mod intervals_icu;
29#[cfg(feature = "strava")]
30pub mod strava;
31
32/// Result type for `trackme-backends`.
33pub type Result<T, E = Error> = result::Result<T, E>;
34
35/// Error type for `trackme-backends`.
36///
37/// An error is a combination of an [`ErrorKind`][] describing the type of the error and an
38/// optional [`std::error::Error`][] implementation that caused the error.
39#[derive(Debug)]
40pub struct Error {
41    kind: ErrorKind,
42    error: Option<Box<dyn error::Error + Send + Sync>>,
43}
44
45impl Error {
46    /// Creates a new error with the given kind and inner error.
47    pub fn new<E: Into<Box<dyn error::Error + Send + Sync>>>(kind: ErrorKind, error: E) -> Self {
48        Self {
49            kind,
50            error: Some(error.into()),
51        }
52    }
53
54    /// Returns the error kind.
55    pub fn kind(&self) -> &ErrorKind {
56        &self.kind
57    }
58
59    /// Provides access to the inner error (if set).
60    pub fn into_inner(self) -> Option<Box<dyn error::Error + Send + Sync>> {
61        self.error
62    }
63}
64
65impl Display for Error {
66    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
67        self.kind.fmt(f)
68    }
69}
70
71impl From<ErrorKind> for Error {
72    fn from(kind: ErrorKind) -> Self {
73        Self { kind, error: None }
74    }
75}
76
77impl error::Error for Error {
78    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
79        if let Some(error) = &self.error {
80            Some(error.as_ref())
81        } else {
82            None
83        }
84    }
85}
86
87#[cfg(feature = "multipart")]
88impl From<multipart::client::lazy::LazyIoError<'_>> for Error {
89    fn from(error: multipart::client::lazy::LazyIoError<'_>) -> Self {
90        Self::new(
91            ErrorKind::MultipartEncodingFailed {
92                field: error.field_name.map(|field| field.into_owned()),
93            },
94            error.error,
95        )
96    }
97}
98
99#[cfg(feature = "ureq")]
100impl From<ureq::Error> for Error {
101    fn from(error: ureq::Error) -> Self {
102        let mut message = error.to_string();
103        if let ureq::Error::Status(_, response) = error {
104            if let Ok(response) = response.into_string() {
105                use std::fmt::Write as _;
106                write!(&mut message, ", message: {}", response).ok();
107            }
108        }
109        Error::new(ErrorKind::RequestFailed, message)
110    }
111}
112
113/// Error kinds for `trackme-backends`.
114#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
115pub enum ErrorKind {
116    /// Failed to parse the data type from a filename.
117    DataTypeParsingFailed {
118        /// The filename that could not be parsed.
119        filename: PathBuf,
120    },
121    /// Failed to read a file.
122    FileReadingFailed {
123        /// The path of the file that was read.
124        path: PathBuf,
125    },
126    /// Failed to encode a multipart message.
127    MultipartEncodingFailed {
128        /// The field of the multipart message that caused the error.
129        field: Option<String>,
130    },
131    /// Failed to perform an API request.
132    RequestFailed,
133    /// Failed to read an API response.
134    ResponseFailed,
135    /// Failed to parse a URL.
136    UrlParsingFailed {
137        /// The URL that could not be parsed.
138        url: String,
139    },
140}
141
142impl Display for ErrorKind {
143    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
144        match self {
145            Self::DataTypeParsingFailed { filename } => {
146                write!(
147                    f,
148                    "failed to parse data type from filename {}",
149                    filename.display()
150                )
151            }
152            Self::FileReadingFailed { path } => {
153                write!(f, "failed to read file {}", path.display())
154            }
155            Self::MultipartEncodingFailed { field } => {
156                if let Some(field) = field {
157                    write!(f, "failed to encode multipart field {field}")
158                } else {
159                    write!(f, "failed to encode multipart data")
160                }
161            }
162            Self::RequestFailed => write!(f, "failed to send API request"),
163            Self::ResponseFailed => write!(f, "failed to read API response"),
164            Self::UrlParsingFailed { url } => write!(f, "failed to parse the URL {}", url),
165        }
166    }
167}