use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::Result;
use async_trait::async_trait;
use serde_json::Value;
use serde_json::map::Map;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Environment {
Automatic,
Local,
Production,
}
impl Environment {
pub fn allows_local_resources(self) -> bool {
!matches!(self, Environment::Production)
}
}
#[derive(Debug, Clone)]
pub struct Context {
environment: Environment,
project_root: Option<PathBuf>,
metadata: Map<String, Value>,
}
impl Context {
pub fn new() -> Self {
Self::with_environment(Environment::Automatic)
}
pub fn with_environment(environment: Environment) -> Self {
Self {
environment,
project_root: None,
metadata: Map::new(),
}
}
pub fn environment(&self) -> Environment {
self.environment
}
pub fn project_root(&self) -> Option<&Path> {
self.project_root.as_deref()
}
pub(crate) fn set_project_root(&mut self, project_root: PathBuf) {
self.project_root = Some(project_root);
}
pub fn insert_metadata(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
self.metadata.insert(key.into(), value)
}
pub fn metadata(&self, key: &str) -> Option<&Value> {
self.metadata.get(key)
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default)]
pub struct LoaderAndRunner {
context: Context,
}
impl LoaderAndRunner {
pub fn new() -> Self {
Self::default()
}
pub fn with_context(context: Context) -> Self {
Self { context }
}
pub fn context(&self) -> &Context {
&self.context
}
pub fn context_mut(&mut self) -> &mut Context {
&mut self.context
}
pub async fn load<RB, Input, Output>(&self, builder: RB) -> Result<Output>
where
RB: ResourceInputBuilder<Input = Input>,
Input: IntoResource<Output> + Send,
Output: Send,
{
let resource_input = builder.build(self.context())?;
resource_input.into_resource(self.context()).await
}
}
pub trait ResourceInputBuilder {
type Input;
fn build(self, context: &Context) -> Result<Self::Input>;
}
#[async_trait]
pub trait IntoResource<Output>: Sized + Send {
async fn into_resource(self, context: &Context) -> Result<Output>;
}
pub type MetadataMap = HashMap<String, Value>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn context_defaults_to_automatic_resource_resolution() {
assert_eq!(Context::new().environment(), Environment::Automatic);
}
#[test]
fn only_production_forbids_local_resources() {
assert!(Environment::Automatic.allows_local_resources());
assert!(Environment::Local.allows_local_resources());
assert!(!Environment::Production.allows_local_resources());
}
}