Skip to main content

vfox/hooks/
mise_env.rs

1use mlua::prelude::LuaError;
2use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value};
3use std::path::PathBuf;
4
5use crate::Plugin;
6use crate::error::Result;
7use crate::hooks::env_keys::EnvKey;
8
9#[derive(Debug)]
10pub struct MiseEnvContext<T: serde::Serialize> {
11    pub args: Vec<String>,
12    pub options: T,
13}
14
15/// Result from a mise_env hook call
16/// Supports both legacy format (just array of env keys) and extended format
17/// with cache metadata
18#[derive(Debug, Default)]
19pub struct MiseEnvResult {
20    /// Environment variables to set
21    pub env: Vec<EnvKey>,
22    /// Whether this module's output can be cached
23    /// Defaults to false for backward compatibility
24    pub cacheable: bool,
25    /// Files to watch for cache invalidation
26    pub watch_files: Vec<PathBuf>,
27}
28
29impl Plugin {
30    pub async fn mise_env<T: serde::Serialize>(
31        &self,
32        ctx: MiseEnvContext<T>,
33    ) -> Result<MiseEnvResult> {
34        debug!("[vfox:{}] mise_env", &self.name);
35        let result = self
36            .eval_async(chunk! {
37                require "hooks/mise_env"
38                return PLUGIN:MiseEnv($ctx)
39            })
40            .await?;
41
42        Ok(result)
43    }
44}
45
46impl<T: serde::Serialize> IntoLua for MiseEnvContext<T> {
47    fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
48        let table = lua.create_table()?;
49        table.set("options", lua.to_value(&self.options)?)?;
50        Ok(Value::Table(table))
51    }
52}
53
54impl FromLua for MiseEnvResult {
55    fn from_lua(value: Value, lua: &Lua) -> std::result::Result<Self, LuaError> {
56        match value {
57            // Extended format: { cacheable = true, watch_files = {...}, env = {...} }
58            Value::Table(table) => {
59                // Check if this is extended format by looking for 'env' or 'cacheable' key
60                let has_env = table.contains_key("env")?;
61                let has_cacheable = table.contains_key("cacheable")?;
62                let has_watch_files = table.contains_key("watch_files")?;
63
64                if has_env || has_cacheable || has_watch_files {
65                    // Extended format
66                    let env: Vec<EnvKey> = table
67                        .get::<Option<Vec<EnvKey>>>("env")
68                        .map_err(|e| {
69                            LuaError::RuntimeError(format!(
70                                "Invalid 'env' field in MiseEnv result: expected array of {{key, value}} pairs. Error: {e}"
71                            ))
72                        })?
73                        .unwrap_or_default();
74                    let cacheable: bool = table
75                        .get::<Option<bool>>("cacheable")
76                        .map_err(|e| {
77                            LuaError::RuntimeError(format!(
78                                "Invalid 'cacheable' field in MiseEnv result: expected boolean. Error: {e}"
79                            ))
80                        })?
81                        .unwrap_or(false);
82                    let watch_files: Vec<String> = table
83                        .get::<Option<Vec<String>>>("watch_files")
84                        .map_err(|e| {
85                            LuaError::RuntimeError(format!(
86                                "Invalid 'watch_files' field in MiseEnv result: expected array of strings. Error: {e}"
87                            ))
88                        })?
89                        .unwrap_or_default();
90
91                    Ok(MiseEnvResult {
92                        env,
93                        cacheable,
94                        watch_files: watch_files.into_iter().map(PathBuf::from).collect(),
95                    })
96                } else {
97                    // Legacy format: table is actually an array of env keys
98                    // Try to parse as array
99                    let env: Vec<EnvKey> = Vec::from_lua(Value::Table(table), lua).map_err(|e| {
100                        LuaError::RuntimeError(format!(
101                            "Failed to parse MiseEnv hook result. Expected either:\n\
102                             - Legacy format: array of {{key, value}} pairs like {{{{\"KEY\", \"VALUE\"}}, ...}}\n\
103                             - Extended format: table with 'env' field like {{env = {{}}, cacheable = true}}\n\
104                             Error: {e}"
105                        ))
106                    })?;
107                    Ok(MiseEnvResult {
108                        env,
109                        cacheable: false,
110                        watch_files: vec![],
111                    })
112                }
113            }
114            // Empty/nil result
115            Value::Nil => Ok(MiseEnvResult::default()),
116            _ => Err(LuaError::RuntimeError(
117                "Expected table or nil from MiseEnv hook".to_string(),
118            )),
119        }
120    }
121}