use std::borrow::Cow;
use std::fmt::Display;
use std::sync::Arc;
use anyhow::{bail, ensure};
use mcvm_pkg::properties::PackageProperties;
use mcvm_shared::pkg::{is_valid_package_id, ArcPkgReq, PackageID, PackageStability};
use mcvm_shared::util::is_valid_identifier;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::pkg::eval::EvalPermissions;
use mcvm_pkg::{PkgRequest, PkgRequestSource};
#[derive(Clone, Debug)]
pub struct PackageConfig {
pub id: PackageID,
pub source: PackageConfigSource,
pub features: Vec<String>,
pub use_default_features: bool,
pub permissions: EvalPermissions,
pub stability: PackageStability,
pub worlds: Vec<String>,
pub content_version: Option<String>,
}
impl PackageConfig {
pub fn from_id(id: PackageID) -> Self {
Self {
id,
source: PackageConfigSource::Instance,
features: Vec::new(),
use_default_features: use_default_features_default(),
permissions: EvalPermissions::default(),
stability: PackageStability::default(),
worlds: Vec::new(),
content_version: None,
}
}
pub fn calculate_features(
&self,
properties: &PackageProperties,
) -> anyhow::Result<Vec<String>> {
let empty = Vec::new();
let allowed_features = properties.features.as_ref().unwrap_or(&empty);
for feature in &self.features {
ensure!(
allowed_features.contains(feature),
"Configured feature '{feature}' does not exist"
);
}
let mut out = self.features.clone();
if self.use_default_features {
let default_features = properties.default_features.clone().unwrap_or_default();
out.extend(default_features);
}
Ok(out)
}
pub fn get_request(&self) -> ArcPkgReq {
Arc::new(PkgRequest::parse(
self.id.clone(),
PkgRequestSource::UserRequire,
))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PackageConfigSource {
Profile,
Instance,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum PackageConfigDeser {
Basic(PackageID),
Full(FullPackageConfig),
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct FullPackageConfig {
pub id: PackageID,
#[serde(default)]
pub features: Vec<String>,
#[serde(default = "use_default_features_default")]
pub use_default_features: bool,
#[serde(default)]
pub permissions: EvalPermissions,
#[serde(default)]
pub stability: Option<PackageStability>,
#[serde(default)]
pub worlds: Vec<String>,
#[serde(default)]
pub content_version: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum PackageType {
Local,
}
fn use_default_features_default() -> bool {
true
}
impl Display for PackageConfigDeser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Basic(id) => id,
Self::Full(FullPackageConfig { id, .. }) => id,
}
)
}
}
impl PackageConfigDeser {
pub fn to_package_config(
self,
profile_stability: PackageStability,
source: PackageConfigSource,
) -> PackageConfig {
let id = self.get_pkg_id();
let content_version = self.get_content_version().cloned();
let (id, content_version) = if let Some((real_id, version)) = id.split_once('@') {
(real_id.into(), Some(version.into()))
} else {
(id, content_version)
};
PackageConfig {
id,
source,
features: self.get_features(),
use_default_features: self.get_use_default_features(),
permissions: self.get_permissions(),
stability: self.get_stability(profile_stability),
worlds: self.get_worlds().into_owned(),
content_version,
}
}
pub fn get_pkg_id(&self) -> PackageID {
match &self {
Self::Basic(id) => id.clone(),
Self::Full(cfg) => cfg.id.clone(),
}
}
pub fn get_features(&self) -> Vec<String> {
match &self {
Self::Basic(..) => Vec::new(),
Self::Full(cfg) => cfg.features.clone(),
}
}
pub fn get_use_default_features(&self) -> bool {
match &self {
Self::Basic(..) => use_default_features_default(),
Self::Full(cfg) => cfg.use_default_features,
}
}
pub fn get_permissions(&self) -> EvalPermissions {
match &self {
Self::Basic(..) => EvalPermissions::Standard,
Self::Full(cfg) => cfg.permissions,
}
}
pub fn get_stability(&self, profile_stability: PackageStability) -> PackageStability {
match &self {
Self::Basic(..) => profile_stability,
Self::Full(cfg) => cfg.stability.unwrap_or(profile_stability),
}
}
pub fn get_worlds(&self) -> Cow<[String]> {
match &self {
Self::Basic(..) => Cow::Owned(Vec::new()),
Self::Full(cfg) => Cow::Borrowed(&cfg.worlds),
}
}
pub fn get_content_version(&self) -> Option<&String> {
match &self {
Self::Basic(..) => None,
Self::Full(cfg) => cfg.content_version.as_ref(),
}
}
pub fn validate(&self) -> anyhow::Result<()> {
let id = self.get_pkg_id();
if !is_valid_package_id(&id) {
bail!("Invalid package ID '{id}'");
}
for feature in self.get_features() {
if !is_valid_identifier(&feature) {
bail!("Invalid string '{feature}'");
}
}
Ok(())
}
}