Skip to main content

ch_rds/runtime/
mod.rs

1//! Runtime context and resource loading used by `ch_rds::run`.
2
3use 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/// Describes the execution environment used to resolve resources.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Environment {
14    /// Resolve local resources only when explicit URLs and stack settings require them.
15    Automatic,
16    /// Local development mode where explicitly configured local stacks may start.
17    Local,
18    /// Production execution where remote resources are expected.
19    Production,
20}
21
22impl Environment {
23    /// Returns `true` when explicitly configured local resources are allowed.
24    pub fn allows_local_resources(self) -> bool {
25        !matches!(self, Environment::Production)
26    }
27}
28
29/// Shared runtime context forwarded to resource builders and loaders.
30#[derive(Debug, Clone)]
31pub struct Context {
32    environment: Environment,
33    project_root: Option<PathBuf>,
34    metadata: Map<String, Value>,
35}
36
37impl Context {
38    /// Constructs a context in automatic mode without reading process environment.
39    pub fn new() -> Self {
40        Self::with_environment(Environment::Automatic)
41    }
42
43    /// Builds a context for a specific environment, primarily for tests.
44    pub fn with_environment(environment: Environment) -> Self {
45        Self {
46            environment,
47            project_root: None,
48            metadata: Map::new(),
49        }
50    }
51
52    /// Returns the selected execution environment.
53    pub fn environment(&self) -> Environment {
54        self.environment
55    }
56
57    /// Returns the application root discovered by [`crate::Runtime::bootstrap`].
58    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    /// Inserts metadata that can be reused by subsequent resources.
67    pub fn insert_metadata(&mut self, key: impl Into<String>, value: Value) -> Option<Value> {
68        self.metadata.insert(key.into(), value)
69    }
70
71    /// Fetches a metadata value written by a previous resource loader.
72    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/// Coordinates resource loading before executing the user supplied future.
84#[derive(Debug, Default)]
85pub struct LoaderAndRunner {
86    context: Context,
87}
88
89impl LoaderAndRunner {
90    /// Creates a new loader in automatic mode.
91    pub fn new() -> Self {
92        Self::default()
93    }
94
95    /// Creates a loader with a custom context, useful for tests.
96    pub fn with_context(context: Context) -> Self {
97        Self { context }
98    }
99
100    /// Borrows the runtime context shared with resource builders.
101    pub fn context(&self) -> &Context {
102        &self.context
103    }
104
105    /// Borrows the runtime context mutably for advanced integrations.
106    pub fn context_mut(&mut self) -> &mut Context {
107        &mut self.context
108    }
109
110    /// Loads a resource using the supplied builder and returns the value.
111    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
122/// Provides a synchronous hook that converts builder values into inputs.
123pub trait ResourceInputBuilder {
124    /// Intermediate resource representation forwarded to [`IntoResource`].
125    type Input;
126
127    /// Produces the intermediate representation for the given context.
128    fn build(self, context: &Context) -> Result<Self::Input>;
129}
130
131/// Converts a builder input into the final resource asynchronously.
132#[async_trait]
133pub trait IntoResource<Output>: Sized + Send {
134    /// Consumes the builder input and yields the resource instance.
135    async fn into_resource(self, context: &Context) -> Result<Output>;
136}
137
138/// Map type alias exposed to simplify metadata manipulation.
139pub 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}