figure 0.1.1

Library for runtime configuration management
Documentation
use std::{
    path::{Path, PathBuf},
    time::Duration,
};

use anyhow::{Context, Result};
use reqwest::Url;
use serde_json::Value;

#[derive(Clone)]
pub(crate) enum Layer {
    File(FileSpec),
    JsonUrl(Url),
}

impl Layer {
    pub(crate) fn json_file(path: impl Into<PathBuf>) -> Self {
        Self::File(FileSpec::Json(path.into()))
    }

    pub(crate) fn yaml_file(path: impl Into<PathBuf>) -> Self {
        Self::File(FileSpec::Yaml(path.into()))
    }

    pub(crate) fn load(&self) -> Result<Value> {
        Ok(match self {
            Self::File(spec) => {
                let path = spec.path();
                let file = std::fs::File::open(spec.path())?;
                match &spec {
                    FileSpec::Json(_) => serde_json::from_reader(file).map_err(anyhow::Error::from),
                    FileSpec::Yaml(_) => serde_yaml::from_reader(file).map_err(anyhow::Error::from),
                }
                .with_context(|| format!("Failed loading configuration overlay from {path:?}"))?
            }
            Self::JsonUrl(url) => {
                let client = reqwest::blocking::Client::builder()
                    .timeout(Duration::from_secs(5))
                    .build()?;
                client
                    .get(url.to_owned())
                    .send()
                    .and_then(|resp| resp.json())
                    .with_context(|| format!("Failed reading configuration from {url}"))?
            }
        })
    }
}

#[derive(Clone)]
pub(crate) enum FileSpec {
    Json(PathBuf),
    Yaml(PathBuf),
}

impl FileSpec {
    fn path(&self) -> &Path {
        match self {
            FileSpec::Json(p) => p,
            FileSpec::Yaml(p) => p,
        }
    }
}