1use std::collections::HashMap;
2
3use anyhow::bail;
4use mcvm_shared::id::ProfileID;
5use mcvm_shared::Side;
6#[cfg(feature = "schema")]
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9
10use super::instance::{merge_instance_configs, InstanceConfig};
11use super::package::PackageConfigDeser;
12
13#[derive(Deserialize, Serialize, Clone)]
15#[cfg_attr(feature = "schema", derive(JsonSchema))]
16pub struct ProfileConfig {
17 #[serde(flatten)]
19 pub instance: InstanceConfig,
20 #[serde(default)]
22 pub packages: ProfilePackageConfiguration,
23}
24
25impl ProfileConfig {
26 pub fn merge(&mut self, other: Self) {
28 self.instance = merge_instance_configs(&self.instance, other.instance);
29 }
30}
31
32#[derive(Deserialize, Serialize, Debug, Clone)]
34#[cfg_attr(feature = "schema", derive(JsonSchema))]
35#[serde(untagged)]
36pub enum ProfilePackageConfiguration {
37 Simple(Vec<PackageConfigDeser>),
39 Full {
41 #[serde(default)]
43 global: Vec<PackageConfigDeser>,
44 #[serde(default)]
46 client: Vec<PackageConfigDeser>,
47 #[serde(default)]
49 server: Vec<PackageConfigDeser>,
50 },
51}
52
53impl Default for ProfilePackageConfiguration {
54 fn default() -> Self {
55 Self::Simple(Vec::new())
56 }
57}
58
59impl ProfilePackageConfiguration {
60 pub fn validate(&self) -> anyhow::Result<()> {
62 match &self {
63 Self::Simple(global) => {
64 for pkg in global {
65 pkg.validate()?;
66 }
67 }
68 Self::Full {
69 global,
70 client,
71 server,
72 } => {
73 for pkg in global.iter().chain(client.iter()).chain(server.iter()) {
74 pkg.validate()?;
75 }
76 }
77 }
78
79 Ok(())
80 }
81
82 pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = &'a PackageConfigDeser> + 'a> {
84 match &self {
85 Self::Simple(global) => Box::new(global.iter()),
86 Self::Full {
87 global,
88 client,
89 server,
90 } => Box::new(global.iter().chain(client.iter()).chain(server.iter())),
91 }
92 }
93
94 pub fn iter_global(&self) -> impl Iterator<Item = &PackageConfigDeser> {
96 match &self {
97 Self::Simple(global) => global,
98 Self::Full { global, .. } => global,
99 }
100 .iter()
101 }
102
103 pub fn iter_side(&self, side: Side) -> impl Iterator<Item = &PackageConfigDeser> {
105 match &self {
106 Self::Simple(..) => [].iter(),
107 Self::Full { client, server, .. } => match side {
108 Side::Client => client.iter(),
109 Side::Server => server.iter(),
110 },
111 }
112 }
113
114 pub fn add_global_package(&mut self, pkg: PackageConfigDeser) {
116 match self {
117 Self::Simple(global) => global.push(pkg),
118 Self::Full { global, .. } => global.push(pkg),
119 }
120 }
121
122 pub fn add_client_package(&mut self, pkg: PackageConfigDeser) {
124 match self {
125 Self::Simple(global) => {
126 *self = Self::Full {
127 global: global.clone(),
128 client: vec![pkg],
129 server: Vec::new(),
130 }
131 }
132 Self::Full { client, .. } => client.push(pkg),
133 }
134 }
135
136 pub fn add_server_package(&mut self, pkg: PackageConfigDeser) {
138 match self {
139 Self::Simple(global) => {
140 *self = Self::Full {
141 global: global.clone(),
142 client: Vec::new(),
143 server: vec![pkg],
144 }
145 }
146 Self::Full { server, .. } => server.push(pkg),
147 }
148 }
149}
150
151pub fn consolidate_profile_configs(
153 profiles: HashMap<ProfileID, ProfileConfig>,
154 global_profile: Option<&ProfileConfig>,
155) -> anyhow::Result<HashMap<ProfileID, ProfileConfig>> {
156 let mut out: HashMap<_, ProfileConfig> = HashMap::with_capacity(profiles.len());
157
158 let max_iterations = 10000;
159
160 let mut i = 0;
162 while out.len() != profiles.len() {
163 for (id, profile) in &profiles {
164 if out.contains_key(id) {
166 continue;
167 }
168
169 if profile.instance.common.from.is_empty() {
170 let mut profile = profile.clone();
172 if let Some(global_profile) = global_profile {
173 let overlay = profile;
174 profile = global_profile.clone();
175 profile.merge(overlay);
176 }
177 out.insert(id.clone(), profile);
178 } else {
179 for parent in profile.instance.common.from.iter() {
180 if let Some(parent) = out.get(&ProfileID::from(parent.clone())) {
182 let mut new = parent.clone();
183 new.merge(profile.clone());
184 out.insert(id.clone(), new);
185 } else {
186 bail!("Parent profile '{parent}' does not exist");
187 }
188 }
189 }
190 }
191
192 i += 1;
193 if i > max_iterations {
194 panic!("Max iterations exceeded while resolving profiles. This is a bug in MCVM.");
195 }
196 }
197
198 Ok(out)
199}