use std::collections::HashMap;
use anyhow::bail;
use nitro_shared::id::ProfileID;
use nitro_shared::Side;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::instance::InstanceConfig;
use super::package::PackageConfigDeser;
#[derive(Deserialize, Serialize, Clone, Default)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct ProfileConfig {
#[serde(flatten)]
pub instance: InstanceConfig,
#[serde(default)]
pub loader: ProfileLoaderConfiguration,
#[serde(default)]
pub packages: ProfilePackageConfiguration,
}
impl ProfileConfig {
pub fn merge(&mut self, other: Self) {
self.instance.merge(other.instance);
self.loader.merge(&other.loader);
self.packages.merge(other.packages);
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum ProfileLoaderConfiguration {
Simple(Option<String>),
Full {
client: Option<String>,
server: Option<String>,
},
}
impl Default for ProfileLoaderConfiguration {
fn default() -> Self {
Self::Simple(None)
}
}
impl ProfileLoaderConfiguration {
pub fn client(&self) -> Option<&String> {
match self {
Self::Simple(loader) => loader.as_ref(),
Self::Full { client, .. } => client.as_ref(),
}
}
pub fn server(&self) -> Option<&String> {
match self {
Self::Simple(loader) => loader.as_ref(),
Self::Full { server, .. } => server.as_ref(),
}
}
pub fn merge(&mut self, other: &Self) {
let out = Self::Full {
client: other.client().or(self.client()).cloned(),
server: other.server().or(self.server()).cloned(),
};
*self = if out.client() == out.server() {
Self::Simple(out.client().cloned())
} else {
out
};
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum ProfilePackageConfiguration {
Simple(Vec<PackageConfigDeser>),
Full {
#[serde(default)]
global: Vec<PackageConfigDeser>,
#[serde(default)]
client: Vec<PackageConfigDeser>,
#[serde(default)]
server: Vec<PackageConfigDeser>,
},
}
impl Default for ProfilePackageConfiguration {
fn default() -> Self {
Self::Simple(Vec::new())
}
}
impl ProfilePackageConfiguration {
pub fn merge(&mut self, other: Self) {
match (&mut *self, other) {
(Self::Simple(left), Self::Simple(right)) => {
left.extend(right);
}
(Self::Full { global, .. }, Self::Simple(right)) => {
global.extend(right);
}
(
Self::Simple(left),
Self::Full {
global,
client,
server,
},
) => {
left.extend(global);
*self = Self::Full {
global: left.clone(),
client,
server,
};
}
(
Self::Full {
global: global1,
client: client1,
server: server1,
},
Self::Full {
global: global2,
client: client2,
server: server2,
},
) => {
global1.extend(global2);
client1.extend(client2);
server1.extend(server2);
}
}
}
pub fn validate(&self) -> anyhow::Result<()> {
match &self {
Self::Simple(global) => {
for pkg in global {
pkg.validate()?;
}
}
Self::Full {
global,
client,
server,
} => {
for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
pkg.validate()?;
}
}
}
Ok(())
}
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
match &self {
Self::Simple(global) => Box::new(global.iter()),
Self::Full {
global,
client,
server,
} => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
}
}
pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
match &self {
Self::Simple(global) => global,
Self::Full { global, .. } => global,
}
.iter()
}
pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
match &self {
Self::Simple(..) => [].iter(),
Self::Full { client, server, .. } => match side {
Side::Client => client.iter(),
Side::Server => server.iter(),
},
}
}
pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => global.push(pkg),
Self::Full { global, .. } => global.push(pkg),
}
}
pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => {
*self = Self::Full {
global: global.clone(),
client: vec![pkg],
server: Vec::new(),
}
}
Self::Full { client, .. } => client.push(pkg),
}
}
pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
match self {
Self::Simple(global) => {
*self = Self::Full {
global: global.clone(),
client: Vec::new(),
server: vec![pkg],
}
}
Self::Full { server, .. } => server.push(pkg),
}
}
}
pub fn consolidate_profile_configs(
profiles: HashMap<ProfileID, ProfileConfig>,
global_profile: Option<&ProfileConfig>,
) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
let max_iterations = 10000;
let mut i = 0;
while out.len() != profiles.len() {
for (id, profile) in &profiles {
if out.contains_key(id) {
continue;
}
if profile.instance.common.from.is_empty() {
let mut profile = profile.clone();
if let Some(global_profile) = global_profile {
let overlay = profile;
profile = global_profile.clone();
profile.merge(overlay);
}
out.insert(id.clone(), profile);
} else {
for parent in profile.instance.common.from.iter() {
if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
let mut new = parent.clone();
new.merge(profile.clone());
out.insert(id.clone(), new);
} else {
bail!("Parent profile '{parent}' does not exist");
}
}
}
}
i += 1;
if i > max_iterations {
panic!(
"Max iterations exceeded while resolving profiles. This is a bug in Nitrolaunch."
);
}
}
Ok(out)
}