datasources 0.1.5

Async datasource interfaces, middleware, registry, and test doubles
Documentation
use crate::datasource::{BuiltDatasource, Datasource};
use crate::datasource_error::DatasourceError;
use crate::defaults;
use crate::dot_env;
use crate::registry_state::registry;
use crate::result::Result;
use std::collections::HashMap;
use std::sync::Arc;

pub fn create(options: Option<&HashMap<String, String>>) -> Result<Arc<dyn Datasource>> {
    let listed = match options {
        Some(options) => options.clone(),
        None => {
            let listed = merge(&dot_env::load(".env"), env);
            defaults::register_listed(&listed)?;
            listed
        }
    };
    let name = get(&listed, "PROVIDER")
        .filter(|value| !value.is_empty())
        .map(str::to_string)
        .ok_or_else(|| DatasourceError::message("Datasource defaults are not specified"))?;
    let guard = registry();
    let pair = guard
        .as_ref()
        .and_then(|map| map.get(&name))
        .ok_or_else(|| DatasourceError::not_installed(&name))?;
    let driver = pair.driver.clone();
    drop(guard);
    Ok(Arc::new(BuiltDatasource::new(name, driver, Some(listed))))
}

pub fn default() -> Result<Arc<dyn Datasource>> {
    create(None)
}

fn get<'a>(options: &'a HashMap<String, String>, key: &str) -> Option<&'a str> {
    options
        .get(key)
        .map(|value| value.trim())
        .filter(|value| !value.is_empty())
}

fn env(key: &str) -> Option<String> {
    std::env::var(key)
        .ok()
        .map(|value| value.trim().to_string())
        .filter(|value| !value.is_empty())
}

fn merge(
    file: &HashMap<String, String>,
    env: fn(&str) -> Option<String>,
) -> HashMap<String, String> {
    let mut options = file.clone();
    for key in ["INSTALLED_PROVIDERS", "DATASOURCE_URL", "PROVIDER"] {
        if let Some(value) = env(key) {
            options.insert(key.into(), value);
        }
    }
    options
}