datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use crate::datasource_error::DatasourceError;
use crate::driver::Driver;
use crate::loaded_driver::LoadedDriver;
use crate::provider_catalog::ProviderCatalog;
use crate::result::Result;
use crate::sabi_driver::SabiDriverBox;
use libloading::Library;
use std::path::PathBuf;
use std::sync::Arc;

pub(crate) struct DriverLoader;

impl DriverLoader {
    pub(crate) fn load(catalog: &ProviderCatalog) -> Result<Arc<dyn Driver>> {
        let path = find(&catalog.location).ok_or_else(|| DatasourceError::not_installed(&catalog.name))?;
        let lib = unsafe { Library::new(&path) }
            .map_err(|_| DatasourceError::not_installed(&catalog.name))?;
        let symbol = format!("{}\0", catalog.module);
        let ctor = unsafe {
            lib.get::<extern "C" fn() -> SabiDriverBox>(symbol.as_bytes())
                .map_err(|_| DatasourceError::not_installed(&catalog.name))?
        };
        let sabi = ctor();
        Ok(Arc::new(LoadedDriver::new(sabi, lib)))
    }
}

fn find(location: &str) -> Option<PathBuf> {
    let file = lib_name(location);
    search_dirs().into_iter().find_map(|dir| {
        let path = dir.join(&file);
        path.is_file().then_some(path)
    })
}

fn search_dirs() -> Vec<PathBuf> {
    if let Some(dir) = std::env::var_os("DATASOURCES_PLUGIN_DIR") {
        return vec![PathBuf::from(dir)];
    }
    let mut dirs = Vec::new();
    if let Ok(exe) = std::env::current_exe() {
        if let Some(dir) = exe.parent() {
            dirs.push(dir.to_path_buf());
            if let Some(parent) = dir.parent() {
                dirs.push(parent.to_path_buf());
            }
        }
    }
    dirs.push(PathBuf::from("plugins"));
    dirs
}

fn lib_name(location: &str) -> String {
    match std::env::consts::OS {
        "windows" => format!("{location}.dll"),
        "macos" => format!("lib{location}.dylib"),
        _ => format!("lib{location}.so"),
    }
}