Skip to main content

uv_auth/
service.rs

1use serde::{Deserialize, Serialize};
2use std::str::FromStr;
3use thiserror::Error;
4use url::Url;
5use uv_redacted::{DisplaySafeUrl, DisplaySafeUrlError};
6
7#[derive(Error, Debug)]
8pub enum ServiceParseError {
9    #[error(transparent)]
10    InvalidUrl(#[from] DisplaySafeUrlError),
11    #[error("Unsupported scheme: {0}")]
12    UnsupportedScheme(String),
13    #[error("HTTPS is required for non-local hosts")]
14    HttpsRequired,
15}
16
17/// A service URL that wraps [`DisplaySafeUrl`] for CLI usage.
18///
19/// This type provides automatic URL parsing and validation when used as a CLI argument,
20/// eliminating the need for manual parsing in command functions.
21#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
22#[serde(transparent)]
23pub struct Service(DisplaySafeUrl);
24
25impl Service {
26    /// Get the underlying [`DisplaySafeUrl`].
27    pub fn url(&self) -> &DisplaySafeUrl {
28        &self.0
29    }
30
31    /// Validate that the URL scheme is supported.
32    fn check_scheme(url: &Url) -> Result<(), ServiceParseError> {
33        match url.scheme() {
34            "https" => Ok(()),
35            "http" if matches!(url.host_str(), Some("localhost" | "127.0.0.1")) => Ok(()),
36            "http" => Err(ServiceParseError::HttpsRequired),
37            value => Err(ServiceParseError::UnsupportedScheme(value.to_string())),
38        }
39    }
40}
41
42impl FromStr for Service {
43    type Err = ServiceParseError;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        // First try parsing as-is
47        let url = match DisplaySafeUrl::parse(s) {
48            Ok(url) => url,
49            Err(DisplaySafeUrlError::Url(url::ParseError::RelativeUrlWithoutBase)) => {
50                // If it's a relative URL, try prepending https://
51                let with_https = format!("https://{s}");
52                DisplaySafeUrl::parse(&with_https)?
53            }
54            Err(err) => return Err(err.into()),
55        };
56
57        Self::check_scheme(&url)?;
58
59        Ok(Self(url))
60    }
61}
62
63impl std::fmt::Display for Service {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        self.0.fmt(f)
66    }
67}
68
69impl TryFrom<String> for Service {
70    type Error = ServiceParseError;
71
72    fn try_from(value: String) -> Result<Self, Self::Error> {
73        Self::from_str(&value)
74    }
75}
76
77impl From<Service> for String {
78    fn from(service: Service) -> Self {
79        service.to_string()
80    }
81}
82
83impl TryFrom<DisplaySafeUrl> for Service {
84    type Error = ServiceParseError;
85
86    fn try_from(value: DisplaySafeUrl) -> Result<Self, Self::Error> {
87        Self::check_scheme(&value)?;
88        Ok(Self(value))
89    }
90}