use std::{
borrow::Cow,
collections::{BTreeMap, BTreeSet},
};
use rattler_conda_types::{
package::RunExportsJson, BuildNumber, Flag, NoArchType, PackageRecord, PackageUrl,
VersionWithSource,
};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use super::{
package_selector::PackageSelector,
source_data::{PackageBuildSourceSerializer, SourceLocationSerializer},
};
use crate::{
conda::{
CondaSourceData, PackageBuildSource, PartialSourceMetadata, SourceMetadata, VariantValue,
},
source::SourceLocation,
utils::derived_fields,
CondaPackageData, ConversionError, SourceIdentifier,
};
#[serde_as]
#[derive(Serialize, Deserialize, Eq, PartialEq)]
pub(crate) struct SourcePackageDataModel<'a> {
pub conda_source: SourceIdentifier,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<Cow<'a, VersionWithSource>>,
#[serde(default, skip_serializing_if = "str::is_empty")]
pub build: Cow<'a, str>,
#[serde(default, skip_serializing_if = "is_zero")]
pub build_number: BuildNumber,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subdir: Option<Cow<'a, str>>,
#[serde(default, skip_serializing_if = "NoArchType::is_none")]
pub noarch: NoArchType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub variants: Option<Cow<'a, BTreeMap<String, VariantValue>>>,
#[serde(default, skip_serializing_if = "<[String]>::is_empty")]
pub depends: Cow<'a, [String]>,
#[serde(default, skip_serializing_if = "<[String]>::is_empty")]
pub constrains: Cow<'a, [String]>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde(rename = "extra_depends")]
pub experimental_extra_depends: Cow<'a, BTreeMap<String, Vec<String>>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub features: Cow<'a, Option<String>>,
#[serde(default, skip_serializing_if = "<[Flag]>::is_empty")]
pub flags: Cow<'a, [Flag]>,
#[serde(default, skip_serializing_if = "<[String]>::is_empty")]
pub track_features: Cow<'a, [String]>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Cow<'a, Option<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license_family: Cow<'a, Option<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub purls: Cow<'a, Option<BTreeSet<PackageUrl>>>,
#[serde(default, skip_serializing_if = "RunExportsJson::is_empty")]
pub run_exports: Cow<'a, RunExportsJson>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub size: Cow<'a, Option<u64>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<PackageBuildSourceSerializer>")]
pub source: Option<PackageBuildSource>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
#[serde_as(as = "BTreeMap<_, SourceLocationSerializer>")]
pub source_depends: BTreeMap<String, SourceLocation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub python_site_packages_path: Cow<'a, Option<String>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub build_packages: Vec<PackageSelector>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub host_packages: Vec<PackageSelector>,
}
fn is_zero(value: &BuildNumber) -> bool {
*value == 0
}
impl<'a> SourcePackageDataModel<'a> {
pub fn into_parts(self) -> Result<(SourceIdentifier, CondaSourceData), ConversionError> {
let (name, hash, location) = self.conda_source.clone().into_parts();
let metadata = if let (Some(version), Some(subdir)) = (self.version, self.subdir) {
let subdir = subdir.into_owned();
let build = self.build.into_owned();
let (arch, platform) = derived_fields::derive_arch_and_platform(&subdir);
SourceMetadata::Full(Box::new(PackageRecord {
name: name.clone(),
version: version.into_owned(),
subdir,
build,
build_number: self.build_number,
noarch: self.noarch,
arch,
platform,
constrains: self.constrains.into_owned(),
depends: self.depends.into_owned(),
experimental_extra_depends: self.experimental_extra_depends.into_owned(),
features: self.features.into_owned(),
flags: self.flags.into_owned(),
legacy_bz2_md5: None,
legacy_bz2_size: None,
license: self.license.into_owned(),
license_family: self.license_family.into_owned(),
md5: None,
purls: self.purls.into_owned(),
sha256: None,
size: self.size.into_owned(),
timestamp: None,
track_features: self.track_features.into_owned(),
run_exports: Some(self.run_exports.into_owned()),
python_site_packages_path: self.python_site_packages_path.into_owned(),
}))
} else {
SourceMetadata::Partial(Box::new(PartialSourceMetadata {
name,
depends: self.depends.into_owned(),
constrains: self.constrains.into_owned(),
experimental_extra_depends: self.experimental_extra_depends.into_owned(),
flags: self.flags.into_owned(),
license: self.license.into_owned(),
purls: self.purls.into_owned(),
run_exports: Some(self.run_exports.into_owned()),
}))
};
let source_data = CondaSourceData {
location,
variants: self.variants.map(Cow::into_owned).unwrap_or_default(),
package_build_source: self.source,
identifier_hash: Some(hash),
sources: self.source_depends,
source_data: crate::SourceData::default(),
metadata,
};
Ok((self.conda_source, source_data))
}
}
impl<'a> TryFrom<SourcePackageDataModel<'a>> for CondaPackageData {
type Error = ConversionError;
fn try_from(value: SourcePackageDataModel<'a>) -> Result<Self, Self::Error> {
let (_identifier, source_data) = value.into_parts()?;
Ok(CondaPackageData::Source(Box::new(source_data)))
}
}
impl<'a> From<&'a CondaSourceData> for SourcePackageDataModel<'a> {
fn from(value: &'a CondaSourceData) -> Self {
let variants = (!value.variants.is_empty()).then_some(Cow::Borrowed(&value.variants));
let identifier = SourceIdentifier::from_source_data(value);
match &value.metadata {
SourceMetadata::Full(full) => Self {
conda_source: identifier,
version: Some(Cow::Borrowed(&full.version)),
subdir: Some(Cow::Borrowed(&full.subdir)),
build: Cow::Borrowed(&full.build),
build_number: full.build_number,
noarch: full.noarch,
variants,
purls: Cow::Borrowed(&full.purls),
run_exports: full
.run_exports
.as_ref()
.map(Cow::Borrowed)
.unwrap_or_default(),
depends: Cow::Borrowed(&full.depends),
constrains: Cow::Borrowed(&full.constrains),
experimental_extra_depends: Cow::Borrowed(&full.experimental_extra_depends),
size: Cow::Borrowed(&full.size),
features: Cow::Borrowed(&full.features),
flags: Cow::Borrowed(&full.flags),
track_features: Cow::Borrowed(&full.track_features),
license: Cow::Borrowed(&full.license),
license_family: Cow::Borrowed(&full.license_family),
python_site_packages_path: Cow::Borrowed(&full.python_site_packages_path),
source: value.package_build_source.clone(),
source_depends: value.sources.clone(),
build_packages: Vec::new(),
host_packages: Vec::new(),
},
SourceMetadata::Partial(partial) => Self {
conda_source: identifier,
version: None,
subdir: None,
build: Cow::Borrowed(""),
build_number: 0,
noarch: NoArchType::default(),
variants,
purls: Cow::Borrowed(&partial.purls),
run_exports: partial
.run_exports
.as_ref()
.map(Cow::Borrowed)
.unwrap_or_default(),
depends: Cow::Borrowed(&partial.depends),
constrains: Cow::Borrowed(&partial.constrains),
experimental_extra_depends: Cow::Borrowed(&partial.experimental_extra_depends),
size: Cow::Owned(None),
features: Cow::Owned(None),
flags: Cow::Borrowed(&partial.flags),
track_features: Cow::Borrowed(&[]),
license: Cow::Borrowed(&partial.license),
license_family: Cow::Owned(None),
python_site_packages_path: Cow::Owned(None),
source: value.package_build_source.clone(),
source_depends: value.sources.clone(),
build_packages: Vec::new(),
host_packages: Vec::new(),
},
}
}
}