use std::borrow::Cow;
use std::fmt::Display;
use anyhow::bail;
use nitro_shared::pkg::{PackageStability, PkgRequest, PkgRequestSource, is_valid_package_id};
use nitro_shared::util::{DefaultExt, is_valid_identifier};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum PackageConfigDeser {
Basic(String),
Full(FullPackageConfig),
}
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct FullPackageConfig {
#[serde(alias = "req")]
pub id: String,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub features: Vec<String>,
#[serde(default = "use_default_features_default")]
#[serde(skip_serializing_if = "DefaultExt::is_default")]
pub use_default_features: bool,
#[serde(default)]
#[serde(skip_serializing_if = "DefaultExt::is_default")]
pub permissions: EvalPermissions,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub stability: Option<PackageStability>,
#[serde(default)]
#[serde(skip_serializing_if = "Vec::is_empty")]
pub worlds: Vec<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub content_version: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "DefaultExt::is_default")]
pub optional: bool,
}
#[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 get_id(&self) -> &str {
match &self {
Self::Basic(req) => req,
Self::Full(cfg) => &cfg.id,
}
}
pub fn get_req(&self) -> PkgRequest {
PkgRequest::parse(self.get_id(), PkgRequestSource::UserRequire)
}
pub fn set_id(&mut self, new_id: String) {
match self {
Self::Basic(req) => *req = new_id,
Self::Full(cfg) => cfg.id = new_id,
}
}
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, default_stability: PackageStability) -> PackageStability {
match &self {
Self::Basic(..) => default_stability,
Self::Full(cfg) => cfg.stability.unwrap_or(default_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 get_optional(&self) -> bool {
match &self {
Self::Basic(..) => false,
Self::Full(cfg) => cfg.optional,
}
}
pub fn validate(&self) -> anyhow::Result<()> {
let req = self.get_req();
if !is_valid_package_id(&req.id) {
bail!("Invalid package ID '{req}'");
}
for feature in self.get_features() {
if !is_valid_identifier(&feature) {
bail!("Invalid package feature string '{feature}'");
}
}
Ok(())
}
}
pub fn add_or_update_package_config(
configs: &mut Vec<PackageConfigDeser>,
new_config: PackageConfigDeser,
) {
for pkg in configs.iter_mut() {
if pkg.get_req() == new_config.get_req() {
pkg.set_id(new_config.get_id().to_string());
return;
}
}
configs.push(new_config);
}
#[derive(Deserialize, Serialize, Debug, Copy, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum EvalPermissions {
Restricted,
#[default]
Standard,
Elevated,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_empty() {
let mut configs = Vec::new();
let new_config = PackageConfigDeser::Basic("pkg1".to_string());
add_or_update_package_config(&mut configs, new_config);
assert_eq!(configs.len(), 1);
}
#[test]
fn test_add_update() {
let mut configs = vec![PackageConfigDeser::Basic("pkg1".to_string())];
let new_config = PackageConfigDeser::Basic("pkg1".to_string());
add_or_update_package_config(&mut configs, new_config);
assert_eq!(configs.len(), 1);
}
#[test]
fn test_add_new() {
let mut configs = vec![PackageConfigDeser::Basic("pkg1".to_string())];
let new_config = PackageConfigDeser::Basic("pkg2".to_string());
add_or_update_package_config(&mut configs, new_config);
assert_eq!(configs.len(), 2);
}
}