mirror of
https://github.com/openai/codex.git
synced 2026-04-29 02:41:12 +03:00
This PR attempts to solve two problems by introducing a `AbsolutePathBuf` type with a special deserializer: - `AbsolutePathBuf` attempts to be a generally useful abstraction, as it ensures, by constructing, that it represents a value that is an absolute, normalized path, which is a stronger guarantee than an arbitrary `PathBuf`. - Values in `config.toml` that can be either an absolute or relative path should be resolved against the folder containing the `config.toml` in the relative path case. This PR makes this easy to support: the main cost is ensuring `AbsolutePathBufGuard` is used inside `deserialize_config_toml_with_base()`. While `AbsolutePathBufGuard` may seem slightly distasteful because it relies on thread-local storage, this seems much cleaner to me than using than my various experiments with https://docs.rs/serde/latest/serde/de/trait.DeserializeSeed.html. Further, since the `deserialize()` method from the `Deserialize` trait is not async, we do not really have to worry about the deserialization work being spread across multiple threads in a way that would interfere with `AbsolutePathBufGuard`. To start, this PR introduces the use of `AbsolutePathBuf` in `OtelTlsConfig`. Note how this simplifies `otel_provider.rs` because it no longer requires `settings.codex_home` to be threaded through. Furthermore, this sets us up better for a world where multiple `config.toml` files from different folders could be loaded and then merged together, as the absolutifying of the paths must be done against the correct parent folder.
153 lines
4.4 KiB
Rust
153 lines
4.4 KiB
Rust
use path_absolutize::Absolutize;
|
|
use serde::Deserialize;
|
|
use serde::Deserializer;
|
|
use serde::Serialize;
|
|
use serde::de::Error as SerdeError;
|
|
use std::cell::RefCell;
|
|
use std::path::Display;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
/// A path that is guaranteed to be absolute and normalized (though it is not
|
|
/// guaranteed to be canonicalized or exist on the filesystem).
|
|
///
|
|
/// IMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set
|
|
/// using `AbsolutePathBufGuard::new(base_path)`. If no base path is set, the
|
|
/// deserialization will fail unless the path being deserialized is already
|
|
/// absolute.
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
pub struct AbsolutePathBuf(PathBuf);
|
|
|
|
impl AbsolutePathBuf {
|
|
pub fn resolve_path_against_base<P, B>(path: P, base_path: B) -> std::io::Result<Self>
|
|
where
|
|
P: AsRef<Path>,
|
|
B: AsRef<Path>,
|
|
{
|
|
let absolute_path = path.as_ref().absolutize_from(base_path.as_ref())?;
|
|
Ok(Self(absolute_path.into_owned()))
|
|
}
|
|
|
|
pub fn from_absolute_path<P>(path: P) -> std::io::Result<Self>
|
|
where
|
|
P: AsRef<Path>,
|
|
{
|
|
let absolute_path = path.as_ref().absolutize()?;
|
|
Ok(Self(absolute_path.into_owned()))
|
|
}
|
|
|
|
pub fn as_path(&self) -> &Path {
|
|
&self.0
|
|
}
|
|
|
|
pub fn into_path_buf(self) -> PathBuf {
|
|
self.0
|
|
}
|
|
|
|
pub fn to_path_buf(&self) -> PathBuf {
|
|
self.0.clone()
|
|
}
|
|
|
|
pub fn display(&self) -> Display<'_> {
|
|
self.0.display()
|
|
}
|
|
}
|
|
|
|
thread_local! {
|
|
static ABSOLUTE_PATH_BASE: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
|
|
}
|
|
|
|
pub struct AbsolutePathBufGuard;
|
|
|
|
impl AbsolutePathBufGuard {
|
|
pub fn new(base_path: &Path) -> Self {
|
|
ABSOLUTE_PATH_BASE.with(|cell| {
|
|
*cell.borrow_mut() = Some(base_path.to_path_buf());
|
|
});
|
|
Self
|
|
}
|
|
}
|
|
|
|
impl Drop for AbsolutePathBufGuard {
|
|
fn drop(&mut self) {
|
|
ABSOLUTE_PATH_BASE.with(|cell| {
|
|
*cell.borrow_mut() = None;
|
|
});
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for AbsolutePathBuf {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
let path = PathBuf::deserialize(deserializer)?;
|
|
ABSOLUTE_PATH_BASE.with(|cell| match cell.borrow().as_deref() {
|
|
Some(base) => {
|
|
Ok(Self::resolve_path_against_base(path, base).map_err(SerdeError::custom)?)
|
|
}
|
|
None if path.is_absolute() => {
|
|
Self::from_absolute_path(path).map_err(SerdeError::custom)
|
|
}
|
|
None => Err(SerdeError::custom(
|
|
"AbsolutePathBuf deserialized without a base path",
|
|
)),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl AsRef<Path> for AbsolutePathBuf {
|
|
fn as_ref(&self) -> &Path {
|
|
self.as_path()
|
|
}
|
|
}
|
|
|
|
impl From<AbsolutePathBuf> for PathBuf {
|
|
fn from(path: AbsolutePathBuf) -> Self {
|
|
path.into_path_buf()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use tempfile::tempdir;
|
|
|
|
#[test]
|
|
fn create_with_absolute_path_ignores_base_path() {
|
|
let base_dir = tempdir().expect("base dir");
|
|
let absolute_dir = tempdir().expect("absolute dir");
|
|
let base_path = base_dir.path();
|
|
let absolute_path = absolute_dir.path().join("file.txt");
|
|
let abs_path_buf =
|
|
AbsolutePathBuf::resolve_path_against_base(absolute_path.clone(), base_path)
|
|
.expect("failed to create");
|
|
assert_eq!(abs_path_buf.as_path(), absolute_path.as_path());
|
|
}
|
|
|
|
#[test]
|
|
fn relative_path_is_resolved_against_base_path() {
|
|
let temp_dir = tempdir().expect("base dir");
|
|
let base_dir = temp_dir.path();
|
|
let abs_path_buf = AbsolutePathBuf::resolve_path_against_base("file.txt", base_dir)
|
|
.expect("failed to create");
|
|
assert_eq!(abs_path_buf.as_path(), base_dir.join("file.txt").as_path());
|
|
}
|
|
|
|
#[test]
|
|
fn guard_used_in_deserialization() {
|
|
let temp_dir = tempdir().expect("base dir");
|
|
let base_dir = temp_dir.path();
|
|
let relative_path = "subdir/file.txt";
|
|
let abs_path_buf = {
|
|
let _guard = AbsolutePathBufGuard::new(base_dir);
|
|
serde_json::from_str::<AbsolutePathBuf>(&format!(r#""{relative_path}""#))
|
|
.expect("failed to deserialize")
|
|
};
|
|
assert_eq!(
|
|
abs_path_buf.as_path(),
|
|
base_dir.join(relative_path).as_path()
|
|
);
|
|
}
|
|
}
|