use rattler_conda_types::{compression_level::CompressionLevel, package::CondaArchiveType};
use serde::{Deserialize, Serialize, de::Error};
use std::str::FromStr;
use crate::config::{Config, MergeError, ValidationError};
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct PackageFormatAndCompression {
pub archive_type: CondaArchiveType,
pub compression_level: CompressionLevel,
}
#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub struct BuildConfig {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub package_format: Option<PackageFormatAndCompression>,
}
impl BuildConfig {
pub fn is_default(&self) -> bool {
self.package_format.is_none()
}
}
impl<'de> Deserialize<'de> for PackageFormatAndCompression {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = s.as_str();
PackageFormatAndCompression::from_str(s).map_err(D::Error::custom)
}
}
impl FromStr for PackageFormatAndCompression {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut split = s.split(':');
let package_format = split.next().ok_or("invalid")?;
let compression = split.next().unwrap_or("default");
let package_format = package_format
.chars()
.filter(|c| c.is_alphanumeric())
.collect::<String>();
let archive_type = match package_format.to_lowercase().as_str() {
"tarbz2" => CondaArchiveType::TarBz2,
"conda" => CondaArchiveType::Conda,
_ => return Err(format!("Unknown package format: {package_format}")),
};
let compression_level = match compression {
"max" | "highest" => CompressionLevel::Highest,
"default" | "normal" => CompressionLevel::Default,
"fast" | "lowest" | "min" => CompressionLevel::Lowest,
number if number.parse::<i32>().is_ok() => {
let number = number.parse::<i32>().unwrap_or_default();
match archive_type {
CondaArchiveType::TarBz2 => {
if !(1..=9).contains(&number) {
return Err("Compression level for .tar.bz2 must be between 1 and 9"
.to_string());
}
}
CondaArchiveType::Conda => {
if !(-7..=22).contains(&number) {
return Err(
"Compression level for conda packages (zstd) must be between -7 and 22".to_string()
);
}
}
}
CompressionLevel::Numeric(number)
}
_ => return Err(format!("Unknown compression level: {compression}")),
};
Ok(PackageFormatAndCompression {
archive_type,
compression_level,
})
}
}
impl Serialize for PackageFormatAndCompression {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let package_format = match self.archive_type {
CondaArchiveType::TarBz2 => "tarbz2",
CondaArchiveType::Conda => "conda",
};
let compression_level = match self.compression_level {
CompressionLevel::Default => "default",
CompressionLevel::Highest => "max",
CompressionLevel::Lowest => "min",
CompressionLevel::Numeric(level) => &level.to_string(),
};
serializer.serialize_str(format!("{package_format}:{compression_level}").as_str())
}
}
impl Config for BuildConfig {
fn merge_config(self, other: &Self) -> Result<Self, MergeError> {
Ok(Self {
package_format: other
.package_format
.as_ref()
.or(self.package_format.as_ref())
.cloned(),
})
}
fn validate(&self) -> Result<(), ValidationError> {
Ok(())
}
fn keys(&self) -> Vec<String> {
vec!["package-format".to_string()]
}
}