boreholeio 0.1.0

A library for interacting with borehole.io, a subsurface data management, delivery and visualisation platform
Documentation
use super::Scheme;
use iri_string::{
    format::ToDedicatedString,
    types::{IriAbsoluteStr, UriAbsoluteStr, UriAbsoluteString},
};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AbsoluteUri(pub(super) UriAbsoluteString);

impl AbsoluteUri {
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }

    pub fn scheme(&self) -> Scheme {
        Scheme(self.0.scheme_str())
    }
}

impl From<&UriAbsoluteStr> for AbsoluteUri {
    fn from(value: &UriAbsoluteStr) -> Self {
        Self(value.to_owned())
    }
}

impl TryFrom<&str> for AbsoluteUri {
    type Error = String;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let absolute_iri = IriAbsoluteStr::new(value)
            .or(Err(format!("String '{value}' is not a valid Absolute IRI.")).to_owned())?;
        Ok(Self(absolute_iri.encode_to_uri().to_dedicated_string()))
    }
}

impl TryFrom<&Path> for AbsoluteUri {
    type Error = String;

    fn try_from(value: &Path) -> Result<Self, Self::Error> {
        // On Windows, absolute file path can start with a "\\?\" prefix which
        // indicates that the path length can be more than `MAX_PATH` (which
        // is 260) characters. The `lstrip_value` argument specifies how many
        // characters to strip from the left of the `value`.
        //
        // For more details please refer to:
        // https://learn.microsoft.com/en-gb/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#win32-file-namespaces
        fn path_to_absolute(
            prefix: &str,
            value: &Path,
            lstrip_value: usize,
        ) -> Result<AbsoluteUri, String> {
            if let Some(path) = value.to_str() {
                let path = path.replace(std::path::MAIN_SEPARATOR, "/");
                format!("{}{}", prefix, &path[lstrip_value..])
                    .as_str()
                    .try_into()
            } else {
                Err(format!(
                    "Path '{value:?}' contains invalid characters that can't be represented by UTF-8."
                ))
            }
        }

        if value.is_relative() {
            return Err(format!(
                "Cannot create Absolute URI from relative path '{value:?}'."
            ));
        }

        let mut components = value.components();
        match components.next() {
            Some(std::path::Component::Prefix(component)) => {
                if let std::path::Prefix::Disk(_) = component.kind() {
                    path_to_absolute("file:///", value, 0)
                } else if let std::path::Prefix::VerbatimDisk(_) = component.kind() {
                    path_to_absolute("file:///", value, 4)
                } else {
                    Err(format!("Only absolute paths beginning with a drive letter can be converted to Absolute URIs but not {value:?}"))
                }
            }
            Some(std::path::Component::RootDir) => path_to_absolute("file://", value, 0),
            _ => Err(format!(
                "Provided path ({value:?}) doesn't start with a prefix or a root directory."
            )),
        }
    }
}

impl TryFrom<&AbsoluteUri> for PathBuf {
    type Error = String;

    fn try_from(value: &AbsoluteUri) -> Result<Self, Self::Error> {
        if value.scheme() != Scheme::FILE {
            return Err(format!(
                "Absolute URI {value:?} does not have a file scheme"
            ));
        }

        #[cfg(unix)]
        let path = Path::new(value.0.path_str());

        // On Windows, the path component of the URI starts with a leading
        // slash, which needs to be removed manually when the path is
        // absolute. For example, "/C:/path/to/my/file.txt" is not a valid
        // file path on Windows.
        #[cfg(windows)]
        let path = Path::new(&value.0.path_str()[1..]);

        if path.is_relative() {
            Err(format!("Absolute URI {value:?} contains relative path"))
        } else {
            Ok(path.to_owned())
        }
    }
}