Skip to content
Closed
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
11 changes: 11 additions & 0 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ use uv_warnings::warn_user;
pub use crate::combine::*;
pub use crate::settings::*;

use crate::validation::{Context, ValidationError, Validator};

mod combine;
mod settings;
mod validation;

/// The [`Options`] as loaded from a configuration file on disk.
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -47,6 +50,7 @@ impl FilesystemOptions {
match read_file(&file) {
Ok(options) => {
tracing::debug!("Found user configuration in: `{}`", file.display());
options.validate(&Context { path: &file })?;
validate_uv_toml(&file, &options)?;
Comment on lines +53 to 54

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I didn't consolidate validate_uv_toml into the validator trait impl because it actually doesn't get called in the pyproject.toml branch.

Ok(Some(Self(options)))
}
Expand All @@ -71,6 +75,7 @@ impl FilesystemOptions {

tracing::debug!("Found system configuration in: `{}`", file.display());
let options = read_file(&file)?;
options.validate(&Context { path: &file })?;
validate_uv_toml(&file, &options)?;
Ok(Some(Self(options)))
}
Expand Down Expand Up @@ -130,6 +135,7 @@ impl FilesystemOptions {
}

tracing::debug!("Found workspace configuration at `{}`", path.display());
options.validate(&Context { path: &path })?;
validate_uv_toml(&path, &options)?;
return Ok(Some(Self(options)));
}
Expand Down Expand Up @@ -162,6 +168,7 @@ impl FilesystemOptions {
let options = options.relative_to(&std::path::absolute(dir)?)?;

tracing::debug!("Found workspace configuration at `{}`", path.display());
options.validate(&Context { path: &path })?;
return Ok(Some(Self(options)));
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Expand All @@ -177,6 +184,7 @@ impl FilesystemOptions {
tracing::debug!("Reading user configuration from: `{}`", path.display());

let options = read_file(path)?;
options.validate(&Context { path })?;
validate_uv_toml(path, &options)?;
Ok(Self(options))
}
Expand Down Expand Up @@ -577,6 +585,9 @@ pub enum Error {
value: String,
err: String,
},

#[error(transparent)]
Validation(#[from] ValidationError),
}

#[derive(Copy, Clone, Debug)]
Expand Down
48 changes: 48 additions & 0 deletions crates/uv-settings/src/validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use std::path::{Path, PathBuf};

use uv_fs::Simplified;

use crate::{GlobalOptions, Options};

pub(crate) trait Validator<Context = (), Err = ValidationError> {
fn validate(&self, ctx: &Context) -> Result<(), Err>;
}
Comment on lines +7 to +9

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This trait might be a bit over-engineered. If we think that there will be lots of additional validation rules in the future, something like this makes sense. If not, simplifying to something like validate_uv_toml would be better.

YAGNI?


pub(crate) struct Context<'p> {
pub path: &'p Path,
}

impl<'path> Validator<Context<'path>> for Options {
/// Validate that an [`Options`] struct has correct values.
fn validate(&self, ctx: &Context<'path>) -> Result<(), ValidationError> {
let Self { globals, .. } = self;
globals.validate(ctx)
}
}

#[derive(thiserror::Error, Debug)]
pub enum ValidationError {
#[error(transparent)]
GlobalOptions(#[from] GlobalOptionsError),
}

impl<'path> Validator<Context<'path>> for GlobalOptions {
/// Validate that a [`GlobalOptions`] struct has correct values.
fn validate(&self, ctx: &Context<'path>) -> Result<(), ValidationError> {
let Self {
preview,
preview_features,
..
} = self;
if preview.is_some() && preview_features.is_some() {
return Err(GlobalOptionsError::PreviewFeatures(ctx.path.to_path_buf()).into());
}
Ok(())
}
}

#[derive(thiserror::Error, Debug)]
pub enum GlobalOptionsError {
#[error("Failed to parse: `{}`. Cannot specify both `preview` and `preview-features`.", _0.user_display())]
PreviewFeatures(PathBuf),
}
Loading
Loading