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#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
22#[serde(transparent)]
23pub struct Service(DisplaySafeUrl);
24
25impl Service {
26 pub fn url(&self) -> &DisplaySafeUrl {
28 &self.0
29 }
30
31 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 let url = match DisplaySafeUrl::parse(s) {
48 Ok(url) => url,
49 Err(DisplaySafeUrlError::Url(url::ParseError::RelativeUrlWithoutBase)) => {
50 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}