This repository has no description
1use serde::Deserialize;
2use serde::de::{self, Deserializer};
3
4use knot_types::{AtUri, RefName};
5use url::Url;
6
7pub(crate) struct RepoAtUri(AtUri<String>);
8
9impl RepoAtUri {
10 pub(crate) fn at_uri(&self) -> &AtUri<String> {
11 &self.0
12 }
13}
14
15impl<'de> Deserialize<'de> for RepoAtUri {
16 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
17 let raw = String::deserialize(deserializer)?;
18 AtUri::new_owned(raw)
19 .map(RepoAtUri)
20 .map_err(|_| de::Error::custom("repo must be an at-uri"))
21 }
22}
23
24#[derive(Clone)]
25pub(crate) struct SourceUrl(Url);
26
27impl SourceUrl {
28 pub(crate) fn from_url(url: Url) -> Result<Self, &'static str> {
29 match matches!(url.scheme(), "http" | "https") && url.has_host() {
30 true => Ok(Self(url)),
31 false => Err("source must be an http or https url"),
32 }
33 }
34
35 pub(crate) fn as_str(&self) -> &str {
36 self.0.as_str()
37 }
38
39 pub(crate) fn as_url(&self) -> &Url {
40 &self.0
41 }
42}
43
44impl<'de> Deserialize<'de> for SourceUrl {
45 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
46 let raw = String::deserialize(deserializer)?;
47 parse_source_url(&raw).map_err(de::Error::custom)
48 }
49}
50
51fn parse_source_url(raw: &str) -> Result<SourceUrl, &'static str> {
52 let url = Url::parse(raw).map_err(|_| "source must be a valid url")?;
53 SourceUrl::from_url(url)
54}
55
56// A sourceless repo is a plain repo not a bad request necessarily,
57// so empty string / missing field both have to make `None`.
58pub(crate) fn optional_source_url<'de, D: Deserializer<'de>>(
59 deserializer: D,
60) -> Result<Option<SourceUrl>, D::Error> {
61 match Option::<String>::deserialize(deserializer)?.as_deref() {
62 None | Some("") => Ok(None),
63 Some(raw) => parse_source_url(raw).map(Some).map_err(de::Error::custom),
64 }
65}
66
67knot_types::text_newtype! {
68 pub(crate) struct Patch(String) => verbatim;
69 pub(crate) struct CommitMessage(String) => verbatim;
70 pub(crate) struct CommitBody(String) => verbatim;
71}
72
73pub(crate) use crate::query::Revspec;
74
75pub(crate) struct ForkRef(RefName);
76
77impl ForkRef {
78 pub(crate) fn hidden_ref(&self, remote: &RemoteRef) -> Option<RefName> {
79 RefName::new(format!("{}/{}", self.0.as_str(), remote.as_str())).ok()
80 }
81}
82
83// Comes as a bare name,
84// and `refs/hidden/` is the only place a fork staging ref is allowed to exist,
85// so `Deserialize` prepends the prefix & `RefName::new` validates the whole string,
86// therefore there's nothing to append after check.
87impl<'de> Deserialize<'de> for ForkRef {
88 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
89 let raw = String::deserialize(deserializer)?;
90 RefName::new(format!("refs/hidden/{raw}"))
91 .map(ForkRef)
92 .map_err(|_| de::Error::custom("invalid fork ref"))
93 }
94}
95
96pub(crate) struct RemoteRef(knot_types::BranchName);
97
98impl RemoteRef {
99 pub(crate) fn as_str(&self) -> &str {
100 self.0.as_str()
101 }
102
103 pub(crate) fn head_ref(&self) -> RefName {
104 self.0.head_ref()
105 }
106}
107
108impl<'de> Deserialize<'de> for RemoteRef {
109 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
110 let raw = String::deserialize(deserializer)?;
111 knot_types::BranchName::new(raw)
112 .map(RemoteRef)
113 .map_err(|_| de::Error::custom("invalid remote ref"))
114 }
115}