aboutsummaryrefslogtreecommitdiffstats
path: root/document_tree/src/url.rs
diff options
context:
space:
mode:
Diffstat (limited to 'document_tree/src/url.rs')
-rw-r--r--document_tree/src/url.rs78
1 files changed, 78 insertions, 0 deletions
diff --git a/document_tree/src/url.rs b/document_tree/src/url.rs
new file mode 100644
index 0000000..31a0536
--- /dev/null
+++ b/document_tree/src/url.rs
@@ -0,0 +1,78 @@
+use std::fmt;
+use std::str::FromStr;
+
+use url::{self,ParseError};
+use serde_derive::Serialize;
+
+
+fn starts_with_scheme(input: &str) -> bool {
+ let scheme = input.split(':').next().unwrap();
+ if scheme == input || scheme.is_empty() {
+ return false;
+ }
+ let mut chars = input.chars();
+ // First character.
+ if !chars.next().unwrap().is_ascii_alphabetic() {
+ return false;
+ }
+ for ch in chars {
+ if !ch.is_ascii_alphanumeric() && ch != '+' && ch != '-' && ch != '.' {
+ return false;
+ }
+ }
+ true
+}
+
+/// The string representation of a URL, either absolute or relative, that has
+/// been verified as a valid URL on construction.
+#[derive(Debug,PartialEq,Serialize,Clone)]
+#[serde(transparent)]
+pub struct Url(String);
+
+impl Url {
+ pub fn parse_absolute(input: &str) -> Result<Self, ParseError> {
+ Ok(url::Url::parse(input)?.into())
+ }
+ pub fn parse_relative(input: &str) -> Result<Self, ParseError> {
+ // We're assuming that any scheme through which RsT documents are being
+ // accessed is a hierarchical scheme, and so we can parse relative to a
+ // random hierarchical URL.
+ if input.starts_with('/') || !starts_with_scheme(input) {
+ // Continue only if the parse succeeded, disregarding its result.
+ let random_base_url = url::Url::parse("https://a/b").unwrap();
+ url::Url::options()
+ .base_url(Some(&random_base_url))
+ .parse(input)?;
+ Ok(Url(input.into()))
+ } else {
+ // If this is a URL at all, it's an absolute one.
+ // There's no appropriate variant of url::ParseError really.
+ Err(ParseError::SetHostOnCannotBeABaseUrl)
+ }
+ }
+ pub fn as_str(&self) -> &str {
+ self.0.as_str()
+ }
+}
+
+impl From<url::Url> for Url {
+ fn from(url: url::Url) -> Self {
+ Url(url.into_string())
+ }
+}
+
+
+impl fmt::Display for Url {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.as_str())
+ }
+}
+
+
+impl FromStr for Url {
+ type Err = ParseError;
+ fn from_str(input: &str) -> Result<Self, Self::Err> {
+ Url::parse_absolute(input)
+ .or_else(|_| Url::parse_relative(input))
+ }
+}