1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use anyhow::Result;
7use async_trait::async_trait;
8use serde_json::Value;
9use serde_json::map::Map;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Environment {
14 Automatic,
16 Local,
18 Production,
20}
21
22impl Environment {
23 pub fn allows_local_resources(self) -> bool {
25 !matches!(self, Environment::Production)
26 }
27}
28
29#[derive(Debug, Clone)]
31pub struct Context {
32 environment: Environment,
33 project_root: Option<PathBuf>,
34 metadata: Map<String, Value>,
35}
36
37impl Context {
38 pub fn new() -> Self {
40 Self::with_environment(Environment::Automatic)
41 }
42
43 pub fn with_environment(environment: Environment) -> Self {
45 Self {
46 environment,
47 project_root: None,
48 metadata: Map::new(),
49 }
50 }
51
52 pub fn environment(&self) -> Environment {
54 self.environment
55 }
56
57 pub fn project_root(&self) -> Option<&Path> {
59 self.project_root.as_deref()
60 }
61
62 pub(crate) fn set_project_root(&mut self, project_root: PathBuf) {
63 self.project_root = Some(project_root);
64 }
65
66 pub fn insert_metadata(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
68 self.metadata.insert(key.into(), value)
69 }
70
71 pub fn metadata(&self, key: &str) -> Option<&Value> {
73 self.metadata.get(key)
74 }
75}
76
77impl Default for Context {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83#[derive(Debug, Default)]
85pub struct LoaderAndRunner {
86 context: Context,
87}
88
89impl LoaderAndRunner {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn with_context(context: Context) -> Self {
97 Self { context }
98 }
99
100 pub fn context(&self) -> &Context {
102 &self.context
103 }
104
105 pub fn context_mut(&mut self) -> &mut Context {
107 &mut self.context
108 }
109
110 pub async fn load<RB, Input, Output>(&self, builder: RB) -> Result<Output>
112 where
113 RB: ResourceInputBuilder<Input = Input>,
114 Input: IntoResource<Output> + Send,
115 Output: Send,
116 {
117 let resource_input = builder.build(self.context())?;
118 resource_input.into_resource(self.context()).await
119 }
120}
121
122pub trait ResourceInputBuilder {
124 type Input;
126
127 fn build(self, context: &Context) -> Result<Self::Input>;
129}
130
131#[async_trait]
133pub trait IntoResource<Output>: Sized + Send {
134 async fn into_resource(self, context: &Context) -> Result<Output>;
136}
137
138pub type MetadataMap = HashMap<String, Value>;
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn context_defaults_to_automatic_resource_resolution() {
147 assert_eq!(Context::new().environment(), Environment::Automatic);
148 }
149
150 #[test]
151 fn only_production_forbids_local_resources() {
152 assert!(Environment::Automatic.allows_local_resources());
153 assert!(Environment::Local.allows_local_resources());
154 assert!(!Environment::Production.allows_local_resources());
155 }
156}