boreholeio 0.1.0

A library for interacting with borehole.io, a subsurface data management, delivery and visualisation platform
Documentation
use crate::{
    array_cache::{ArrayCache, UriResolvable},
    schema::{array_uri_reference::AbsoluteUri, LoggingRun},
};
use serde::{Deserialize, Deserializer};
use std::{fmt::Debug, fs::File, io::Read, path::Path};
use zip::ZipArchive;

pub type Error = String;

#[derive(Debug, Deserialize)]
pub struct BoreholeDataArchive {
    #[serde(deserialize_with = "ensure_version")]
    pub version: Version,
    pub logging_runs: Vec<LoggingRun>,
}

#[derive(Debug, Deserialize, PartialEq)]
pub struct Version {
    pub major: u64,
    pub minor: u64,
}

impl BoreholeDataArchive {
    const VERSION: Version = Version { major: 0, minor: 2 };

    /// Two-pass JSON file deserialization that resolves all relative URIs.
    pub fn from_file<P: AsRef<Path> + Debug>(path: P) -> Result<Self, Error> {
        // Open the BDA file.
        let zip_file = File::open(&path)
            .map_err(|e| format!("Failed to open BDA file {path:?} due to an error {e}"))?;
        // Interpret it as a ZIP.
        let mut archive = ZipArchive::new(zip_file)
            .map_err(|e| format!("Failed to construct ZipArchive: {e}"))?;

        // Read BDA index.
        let mut index: Self = {
            // Extract index file from ZIP into memory.
            let mut file = archive
                .by_name("index.json")
                .map_err(|e| format!("Failed to extract BDA index: {e}"))?;
            // Read its contents into `String`.
            let mut contents = String::new();
            let _bytes_read = file
                .read_to_string(&mut contents)
                .map_err(|e| format!("Failed to read BDA index: {e}"))?;
            // Interpret the string as JSON.
            serde_json::from_str(&contents)
                .map_err(|e| format!("Failed to interpret BDA index file {path:?} as JSON: {e}"))?
        };

        // Insert BDA into cache.
        let base_uri = ArrayCache::insert_archive(archive)?;
        // 2nd pass: update all the URIs.
        index.set_base_for_relative_uris(&base_uri);

        Ok(index)
    }
}

impl UriResolvable for BoreholeDataArchive {
    fn set_base_for_relative_uris(&mut self, base_uri: &AbsoluteUri) {
        for logging_run in &mut self.logging_runs {
            logging_run.set_base_for_relative_uris(base_uri);
        }
    }
}

/// Deserializes `Version` and ensures its value equals to
/// `BoreholeDataArchive::VERSION`.
fn ensure_version<'de, D>(d: D) -> Result<Version, D::Error>
where
    D: Deserializer<'de>,
{
    let version = Version::deserialize(d)?;
    if version != BoreholeDataArchive::VERSION {
        Err(serde::de::Error::custom(format!(
            "Unsupported version: {version:?}"
        )))
    } else {
        Ok(version)
    }
}

#[test]
fn caching() {
    use crate::schema::axes::{ChannelAxis, Numeric, NumericAxis};

    let bda = BoreholeDataArchive::from_file("QL40-OBI.bda").unwrap();
    for channel in &bda.logging_runs[0].channels {
        if channel.name != "Image" {
            continue;
        }
        let composite_axis = match &channel.data.axes[0] {
            ChannelAxis::Composite(axis) => axis,
            _ => panic!("CompositeAxis expected"),
        };
        for component in &composite_axis.descriptors {
            if let NumericAxis::Coordinate(axis) = component {
                assert!(axis.coordinates().unwrap().len() > 0);
                return;
            }
        }
    }
    panic!("Should've found a CoordinateAxis by now!");
}