use std::collections::HashMap;
use anyhow::bail;
use nitro_shared::id::TemplateID;
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 TemplateConfig {
#[serde(flatten)]
pub instance: InstanceConfig,
#[serde(default)]
pub loader: TemplateLoaderConfiguration,
#[serde(default)]
pub packages: TemplatePackageConfiguration,
}
impl TemplateConfig {
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 TemplateLoaderConfiguration {
Simple(Option<String>),
Full {
client: Option<String>,
server: Option<String>,
},
}
impl Default for TemplateLoaderConfiguration {
fn default() -> Self {
Self::Simple(None)
}
}
impl TemplateLoaderConfiguration {
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 TemplatePackageConfiguration {
Simple(Vec<PackageConfigDeser>),
Full {
#[serde(default)]
global: Vec<PackageConfigDeser>,
#[serde(default)]
client: Vec<PackageConfigDeser>,
#[serde(default)]
server: Vec<PackageConfigDeser>,
},
}
impl Default for TemplatePackageConfiguration {
fn default() -> Self {
Self::Simple(Vec::new())
}
}
impl TemplatePackageConfiguration {
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_template_configs(
templates: HashMap<TemplateID, TemplateConfig>,
base_template: Option<&TemplateConfig>,
) -> anyhow::Result<HashMap<TemplateID, TemplateConfig>> {
let mut out: HashMap<_, TemplateConfig> = HashMap::with_capacity(templates.len());
let max_iterations = 10000;
let mut i = 0;
while out.len() != templates.len() {
for (id, template) in &templates {
if out.contains_key(id) {
continue;
}
if template.instance.from.is_empty() {
let mut template = template.clone();
if let Some(base_template) = base_template {
let overlay = template;
template = base_template.clone();
template.merge(overlay);
}
out.insert(id.clone(), template);
} else {
for parent in template.instance.from.iter() {
let parent_id = TemplateID::from(parent.clone());
if let Some(parent) = out.get(&parent_id) {
let mut new = parent.clone();
new.merge(template.clone());
out.insert(id.clone(), new);
} else {
if !templates.contains_key(&parent_id) {
bail!("Parent template '{parent}' does not exist");
}
}
}
}
}
i += 1;
if i > max_iterations {
bail!(
"Max iterations exceeded while resolving templates. You likely have cyclic templates."
);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use nitro_shared::util::DeserListOrSingle;
use super::*;
#[test]
fn test_consolidated_still_exists() {
let mut templates = HashMap::new();
templates.insert(TemplateID::from("foo"), TemplateConfig::default());
templates.insert(
TemplateID::from("bar"),
TemplateConfig {
instance: InstanceConfig {
from: DeserListOrSingle::Single("foo".into()),
..Default::default()
},
..Default::default()
},
);
for _ in 0..30 {
let consolidated = consolidate_template_configs(templates.clone(), None)
.expect("Failed to consolidte");
assert!(consolidated.contains_key(&TemplateID::from("foo")));
assert!(consolidated.contains_key(&TemplateID::from("bar")));
}
}
}