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 pub fn into_url(self) -> DisplaySafeUrl {
33 self.0
34 }
35
36 fn check_scheme(url: &Url) -> Result<(), ServiceParseError> {
38 match url.scheme() {
39 "https" => Ok(()),
40 "http" if matches!(url.host_str(), Some("localhost" | "127.0.0.1")) => Ok(()),
41 "http" => Err(ServiceParseError::HttpsRequired),
42 value => Err(ServiceParseError::UnsupportedScheme(value.to_string())),
43 }
44 }
45}
46
47impl FromStr for Service {
48 type Err = ServiceParseError;
49
50 fn from_str(s: &str) -> Result<Self, Self::Err> {
51 let url = match DisplaySafeUrl::parse(s) {
53 Ok(url) => url,
54 Err(DisplaySafeUrlError::Url(url::ParseError::RelativeUrlWithoutBase)) => {
55 let with_https = format!("https://{s}");
57 DisplaySafeUrl::parse(&with_https)?
58 }
59 Err(err) => return Err(err.into()),
60 };
61
62 Self::check_scheme(&url)?;
63
64 Ok(Self(url))
65 }
66}
67
68impl std::fmt::Display for Service {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 self.0.fmt(f)
71 }
72}
73
74impl TryFrom<String> for Service {
75 type Error = ServiceParseError;
76
77 fn try_from(value: String) -> Result<Self, Self::Error> {
78 Self::from_str(&value)
79 }
80}
81
82impl From<Service> for String {
83 fn from(service: Service) -> Self {
84 service.to_string()
85 }
86}
87
88impl TryFrom<DisplaySafeUrl> for Service {
89 type Error = ServiceParseError;
90
91 fn try_from(value: DisplaySafeUrl) -> Result<Self, Self::Error> {
92 Self::check_scheme(&value)?;
93 Ok(Self(value))
94 }
95}