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