This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-xrpc / src / body.rs
3.5 kB 115 lines
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}