Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 26 additions & 149 deletions src/config/config_file/mise_toml.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use once_cell::sync::OnceCell;
use serde::de::Visitor;
use serde::{Deserializer, de};
use serde_derive::Deserialize;
use std::fmt::{Debug, Display, Formatter};
use std::fmt::{Debug, Formatter};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{
Expand Down Expand Up @@ -930,161 +930,38 @@ impl<'de> de::Deserialize<'de> for EnvList {
}
}
_ => {
enum Val {
Int(i64),
#[derive(Deserialize)]
#[serde(untagged)]
pub enum PrimitiveVal {
Str(String),
Int(i64),
Bool(bool),
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Val {
Comment on lines +933 to +942
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These enum definitions are declared inside a match arm, creating unnecessary nesting and reducing readability. Consider moving PrimitiveVal and Val to module level to improve code organization.

Copilot uses AI. Check for mistakes.
Primitive(PrimitiveVal),
Map {
value: Box<Val>,
tools: bool,
redact: bool,
value: PrimitiveVal,
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Map variant only accepts PrimitiveVal for the value field, which prevents nested objects. However, this change should be validated to ensure it doesn't break existing configurations that might rely on nested env objects.

Copilot uses AI. Check for mistakes.
#[serde(flatten)]
options: EnvDirectiveOptions,
},
}
impl Display for Val {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
match self {
Val::Int(i) => write!(f, "{i}"),
Val::Str(s) => write!(f, "{s}"),
Val::Bool(b) => write!(f, "{b}"),
Val::Map {
value,
tools,
redact,
} => {
write!(f, "{value}")?;
if *tools {
write!(f, " tools")?;
}
if *redact {
write!(f, " redact")?;
}
Ok(())
}
}
}
}

impl<'de> de::Deserialize<'de> for Val {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct ValVisitor;

impl<'de> Visitor<'de> for ValVisitor {
type Value = Val;
fn expecting(
&self,
formatter: &mut Formatter,
) -> std::fmt::Result
{
formatter.write_str("env value")
}

fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Val::Bool(v))
}

fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Val::Int(v))
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Val::Str(v.to_string()))
}

fn visit_map<A>(
self,
mut map: A,
) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let mut value: Option<Val> = None;
let mut tools = None;
let mut redact = None;
while let Some((key, val)) =
map.next_entry::<String, Val>()?
{
match key.as_str() {
"value" => {
value = Some(val);
}
"tools" => {
tools = Some(val);
}
"redact" => {
redact = Some(val);
}
_ => {
return Err(de::Error::unknown_field(
&key,
&["value", "tools", "redact"],
));
}
}
}
let value = value
.ok_or_else(|| de::Error::missing_field("value"))?;
let tools = if let Some(Val::Bool(tools)) = tools {
tools
} else {
false
};
Ok(Val::Map {
value: Box::new(value),
tools,
redact: redact
.map(|r| {
if let Val::Bool(b) = r { b } else { false }
})
.unwrap_or_default(),
})
}
}

deserializer.deserialize_any(ValVisitor)
}
}

let value = map.next_value::<Val>()?;
match value {
Val::Int(i) => {
env.push(EnvDirective::Val(
key,
i.to_string(),
Default::default(),
));
}
Val::Str(s) => {
env.push(EnvDirective::Val(key, s, Default::default()));
let (value, options) = match map.next_value::<Val>()? {
Val::Primitive(p) => (p, EnvDirectiveOptions::default()),
Val::Map { value, options } => (value, options),
};
let directive = match value {
PrimitiveVal::Str(s) => EnvDirective::Val(key, s, options),
PrimitiveVal::Int(i) => {
EnvDirective::Val(key, i.to_string(), options)
}
Val::Bool(true) => env.push(EnvDirective::Val(
key,
"true".into(),
Default::default(),
)),
Val::Bool(false) => {
env.push(EnvDirective::Rm(key, Default::default()))
PrimitiveVal::Bool(true) => {
EnvDirective::Val(key, "true".to_string(), options)
}
Val::Map {
value,
tools,
redact,
} => {
let opts = EnvDirectiveOptions { tools, redact };
env.push(EnvDirective::Val(key, value.to_string(), opts));
}
}
PrimitiveVal::Bool(false) => EnvDirective::Rm(key, options),
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Refactoring Breaks Compatibility and Hides Errors

This refactoring changes how map-like environment variables are deserialized. { value = false, ... } now creates an Rm directive instead of a Val("false"), which breaks backward compatibility. Additionally, unknown fields in these map directives are now silently ignored, potentially hiding configuration typos.

Fix in Cursor Fix in Web

Copy link
Contributor Author

@risu729 risu729 Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, should we preserve this behaviour for backward compatibility?
key="false" can be used to set the env var false, but someone might be using key={value=false} for this purpose.

Copy link
Owner

@jdx jdx Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, = false should always have been removing an env var so that was a bug

env.push(directive);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/config/env_directive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ mod path;
mod source;
mod venv;

#[derive(Debug, Clone, Default, PartialEq, serde::Serialize)]
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct EnvDirectiveOptions {
#[serde(default)]
pub(crate) tools: bool,
#[serde(default)]
pub(crate) redact: bool,
}

Expand Down
Loading