1use serde::{de, Deserialize, Deserializer, Serialize};
2use std::{fmt::Display, str::FromStr};
3use thiserror::Error;
4
5#[derive(Debug, PartialEq, Eq, Clone)]
11pub struct RemoteGitUrl {
12 pub(crate) host: String,
14 pub(crate) repo: String,
16 pub(crate) owner: String,
18 url_str: String,
20}
21
22#[derive(Debug, Error)]
23pub enum RemoteGitUrlParseError {
24 #[error("error parsing git URL:\n{0}")]
25 GitUrlParse(String),
26 #[error("not a remote git URL: {0}")]
27 NotARemoteGitUrl(String),
28}
29
30impl FromStr for RemoteGitUrl {
31 type Err = RemoteGitUrlParseError;
32
33 fn from_str(s: &str) -> Result<Self, Self::Err> {
34 let url = git_url_parse::GitUrl::parse(s)
35 .map_err(|err| RemoteGitUrlParseError::GitUrlParse(err.to_string()))?;
36 let host = url
37 .host()
38 .ok_or_else(|| RemoteGitUrlParseError::NotARemoteGitUrl(url.to_string()))?;
39 let provider: Result<
40 git_url_parse::types::provider::GenericProvider,
41 git_url_parse::GitUrlParseError,
42 > = url.provider_info();
43 let provider =
44 provider.map_err(|_err| RemoteGitUrlParseError::NotARemoteGitUrl(s.to_string()))?;
45 Ok(RemoteGitUrl {
46 host: host.to_string(),
47 repo: provider.repo().to_string(),
48 owner: provider.owner().to_string(),
49 url_str: url.to_string(),
50 })
51 }
52}
53
54impl Display for RemoteGitUrl {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 self.url_str.fmt(f)
57 }
58}
59
60impl<'de> Deserialize<'de> for RemoteGitUrl {
61 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
62 where
63 D: Deserializer<'de>,
64 {
65 String::deserialize(deserializer)?
66 .parse()
67 .map_err(de::Error::custom)
68 }
69}
70
71impl Serialize for RemoteGitUrl {
72 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
73 where
74 S: serde::Serializer,
75 {
76 self.to_string().serialize(serializer)
77 }
78}