runmat-config 0.6.2

Shared configuration schema and loaders for RunMat
Documentation
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::path::PathBuf;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ProjectDependency {
    #[serde(default)]
    pub path: Option<PathBuf>,
    #[serde(default)]
    pub package: Option<String>,
    #[serde(default)]
    pub registry: Option<String>,
    #[serde(default)]
    pub git: Option<String>,
    #[serde(default)]
    pub project: Option<String>,
    #[serde(default)]
    pub service: Option<String>,
    #[serde(default)]
    pub version: Option<String>,
    #[serde(default)]
    pub rev: Option<String>,
    #[serde(default)]
    pub tag: Option<String>,
    #[serde(default)]
    pub branch: Option<String>,
    #[serde(default)]
    pub snapshot: Option<String>,
    #[serde(default)]
    pub subdir: Option<PathBuf>,
    #[serde(default)]
    pub optional: bool,
    #[serde(default = "default_features_enabled", rename = "default-features")]
    pub default_features: bool,
    #[serde(default)]
    pub features: Vec<String>,
}

const fn default_features_enabled() -> bool {
    true
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectDependencyLocator {
    Path,
    Registry,
    Git,
    ServerProject,
}

impl ProjectDependency {
    pub fn locator(&self) -> Result<ProjectDependencyLocator, String> {
        let locator_count = [
            self.path.is_some(),
            self.package.is_some(),
            self.git.is_some(),
            self.project.is_some(),
        ]
        .into_iter()
        .filter(|present| *present)
        .count();
        if locator_count != 1 {
            return Err(
                "must select exactly one locator: `path`, registry `package`, `git`, or `project`"
                    .to_string(),
            );
        }
        if self.registry.is_some() && self.package.is_none() {
            return Err("`registry` is valid only with a registry `package` locator".to_string());
        }
        if self.subdir.is_some() && self.git.is_none() {
            return Err("`subdir` is valid only with a `git` locator".to_string());
        }
        let git_selectors = [
            self.rev.is_some(),
            self.tag.is_some(),
            self.branch.is_some(),
        ]
        .into_iter()
        .filter(|present| *present)
        .count();
        if self.git.is_some() {
            if git_selectors != 1 {
                return Err(
                    "a `git` locator must select exactly one of `rev`, `tag`, or `branch`"
                        .to_string(),
                );
            }
        } else if git_selectors != 0 {
            return Err("`rev`, `tag`, and `branch` are valid only with `git`".to_string());
        }
        if self.snapshot.is_some() && self.project.is_none() {
            return Err("`snapshot` is valid only with a Server `project` locator".to_string());
        }
        if self.service.is_some() && self.project.is_none() {
            return Err("`service` is valid only with a Server `project` locator".to_string());
        }
        if self.package.is_some() && self.version.as_deref().is_none_or(str::is_empty) {
            return Err("a registry `package` locator must set a non-empty `version`".to_string());
        }
        if self
            .features
            .iter()
            .any(|feature| feature.trim().is_empty())
        {
            return Err("feature requests must be non-empty".to_string());
        }
        Ok(if self.path.is_some() {
            ProjectDependencyLocator::Path
        } else if self.package.is_some() {
            ProjectDependencyLocator::Registry
        } else if self.git.is_some() {
            ProjectDependencyLocator::Git
        } else {
            ProjectDependencyLocator::ServerProject
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ProjectTargetDependencies {
    #[serde(default)]
    pub dependencies: BTreeMap<String, ProjectDependency>,
    #[serde(default, rename = "dev-dependencies")]
    pub dev_dependencies: BTreeMap<String, ProjectDependency>,
    #[serde(default, rename = "test-dependencies")]
    pub test_dependencies: BTreeMap<String, ProjectDependency>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ProjectCapabilities {
    #[serde(default)]
    pub required: Vec<String>,
    #[serde(default)]
    pub optional: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectRegistry {
    pub index: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ProjectSourceReplacement {
    #[serde(rename = "replace-with")]
    pub replace_with: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct ProjectPublication {
    #[serde(default)]
    pub registry: Option<String>,
    #[serde(default)]
    pub include: Vec<String>,
    #[serde(default)]
    pub exclude: Vec<String>,
    #[serde(default)]
    pub license: Option<String>,
    #[serde(default)]
    pub readme: Option<PathBuf>,
}