1use crate::{
4 go_ast::{GoASTParser, GoTypeInfo},
5 imports::TypeReference,
6 ParserError,
7};
8use amalgam_core::{ir::TypeDefinition, types::Type};
9use std::collections::{BTreeMap, HashMap};
10
11pub struct K8sAuthoritativeTypes {
13 go_types: HashMap<String, GoTypeInfo>,
15 type_mapping: HashMap<String, TypeReference>,
17}
18
19impl Default for K8sAuthoritativeTypes {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl K8sAuthoritativeTypes {
26 pub fn new() -> Self {
27 Self {
28 go_types: HashMap::new(),
29 type_mapping: HashMap::new(),
30 }
31 }
32
33 pub async fn initialize(&mut self) -> Result<(), ParserError> {
35 let mut parser = GoASTParser::new();
36
37 let k8s_types = parser.parse_k8s_core_types().await?;
39 self.go_types = k8s_types;
40
41 self.build_type_mapping()?;
43
44 Ok(())
45 }
46
47 fn build_type_mapping(&mut self) -> Result<(), ParserError> {
49 for (qualified_name, type_info) in &self.go_types {
50 if let Some(type_ref) = self.go_qualified_name_to_type_ref(qualified_name, type_info) {
51 self.type_mapping.insert(qualified_name.clone(), type_ref);
52 }
53 }
54 Ok(())
55 }
56
57 fn go_qualified_name_to_type_ref(
59 &self,
60 _qualified_name: &str,
61 type_info: &GoTypeInfo,
62 ) -> Option<TypeReference> {
63 if type_info.package_path.starts_with("k8s.io/api/core/") {
69 let version = type_info.package_path.strip_prefix("k8s.io/api/core/")?;
70 Some(TypeReference::new(
71 "k8s.io".to_string(),
72 version.to_string(),
73 type_info.name.clone(),
74 ))
75 } else if type_info
76 .package_path
77 .starts_with("k8s.io/apimachinery/pkg/apis/meta/")
78 {
79 let version = type_info
80 .package_path
81 .strip_prefix("k8s.io/apimachinery/pkg/apis/meta/")?;
82 Some(TypeReference::new(
83 "k8s.io".to_string(),
84 version.to_string(),
85 type_info.name.clone(),
86 ))
87 } else if type_info.package_path.starts_with("k8s.io/api/apps/") {
88 let version = type_info.package_path.strip_prefix("k8s.io/api/apps/")?;
89 Some(TypeReference::new(
90 "apps.k8s.io".to_string(),
91 version.to_string(),
92 type_info.name.clone(),
93 ))
94 } else if type_info.package_path.starts_with("k8s.io/api/networking/") {
95 let version = type_info
96 .package_path
97 .strip_prefix("k8s.io/api/networking/")?;
98 Some(TypeReference::new(
99 "networking.k8s.io".to_string(),
100 version.to_string(),
101 type_info.name.clone(),
102 ))
103 } else {
104 None
105 }
106 }
107
108 pub fn get_go_type(&self, qualified_name: &str) -> Option<&GoTypeInfo> {
110 self.go_types.get(qualified_name)
111 }
112
113 pub fn get_type_reference(&self, qualified_name: &str) -> Option<&TypeReference> {
115 self.type_mapping.get(qualified_name)
116 }
117
118 pub fn go_type_to_nickel_definition(
120 &self,
121 go_type: &GoTypeInfo,
122 ) -> Result<TypeDefinition, ParserError> {
123 let parser = GoASTParser::new();
124 let nickel_type = parser.go_type_to_nickel(go_type)?;
125
126 Ok(TypeDefinition {
127 name: go_type.name.clone(),
128 ty: nickel_type,
129 documentation: go_type.documentation.clone(),
130 annotations: BTreeMap::new(),
131 })
132 }
133
134 pub fn should_replace_field(&self, field_name: &str, current_type: &Type) -> Option<String> {
136 match field_name {
137 "metadata" if matches!(current_type, Type::Record { fields, .. } if fields.is_empty()) => {
138 Some("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta".to_string())
139 }
140 "status" => {
141 None }
144 _ => None,
145 }
146 }
147
148 pub fn get_field_replacements(
150 &self,
151 field_name: &str,
152 current_type: &Type,
153 parent_context: Option<&str>,
154 ) -> Option<String> {
155 match (field_name, parent_context) {
156 ("metadata", _) if matches!(current_type, Type::Record { fields, .. } if fields.is_empty()) => {
158 Some("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta".to_string())
159 }
160
161 ("volumes", Some("spec")) if matches!(current_type, Type::Array(_)) => {
163 Some("[]k8s.io/api/core/v1.Volume".to_string())
164 }
165 ("volumeMounts", _) if matches!(current_type, Type::Array(_)) => {
166 Some("[]k8s.io/api/core/v1.VolumeMount".to_string())
167 }
168
169 ("containers", Some("spec")) if matches!(current_type, Type::Array(_)) => {
171 Some("[]k8s.io/api/core/v1.Container".to_string())
172 }
173 ("initContainers", Some("spec")) if matches!(current_type, Type::Array(_)) => {
174 Some("[]k8s.io/api/core/v1.Container".to_string())
175 }
176
177 ("resources", _) if matches!(current_type, Type::Record { .. }) => {
179 Some("k8s.io/api/core/v1.ResourceRequirements".to_string())
180 }
181
182 ("selector", _) if matches!(current_type, Type::Record { .. }) => {
184 Some("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector".to_string())
185 }
186
187 ("env", _) if matches!(current_type, Type::Array(_)) => {
189 Some("[]k8s.io/api/core/v1.EnvVar".to_string())
190 }
191 ("envFrom", _) if matches!(current_type, Type::Array(_)) => {
192 Some("[]k8s.io/api/core/v1.EnvFromSource".to_string())
193 }
194
195 ("affinity", _) if matches!(current_type, Type::Record { .. }) => {
197 Some("k8s.io/api/core/v1.Affinity".to_string())
198 }
199 ("tolerations", _) if matches!(current_type, Type::Array(_)) => {
200 Some("[]k8s.io/api/core/v1.Toleration".to_string())
201 }
202 ("nodeSelector", _) if matches!(current_type, Type::Map { .. }) => {
203 Some("map[string]string".to_string()) }
205
206 ("securityContext", _) if matches!(current_type, Type::Record { .. }) => {
208 Some("k8s.io/api/core/v1.SecurityContext".to_string())
209 }
210 ("podSecurityContext", _) if matches!(current_type, Type::Record { .. }) => {
211 Some("k8s.io/api/core/v1.PodSecurityContext".to_string())
212 }
213
214 _ => None,
215 }
216 }
217}
218
219pub struct K8sTypePatterns {
221 patterns: HashMap<String, String>,
222}
223
224impl Default for K8sTypePatterns {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230impl K8sTypePatterns {
231 pub fn new() -> Self {
232 let mut patterns = HashMap::new();
233
234 patterns.insert(
236 "metadata".to_string(),
237 "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta".to_string(),
238 );
239 patterns.insert(
240 "spec.volumes".to_string(),
241 "[]k8s.io/api/core/v1.Volume".to_string(),
242 );
243 patterns.insert(
244 "spec.containers".to_string(),
245 "[]k8s.io/api/core/v1.Container".to_string(),
246 );
247 patterns.insert(
248 "spec.initContainers".to_string(),
249 "[]k8s.io/api/core/v1.Container".to_string(),
250 );
251 patterns.insert(
252 "spec.template.spec.volumes".to_string(),
253 "[]k8s.io/api/core/v1.Volume".to_string(),
254 );
255 patterns.insert(
256 "spec.template.spec.containers".to_string(),
257 "[]k8s.io/api/core/v1.Container".to_string(),
258 );
259 patterns.insert(
260 "spec.selector".to_string(),
261 "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector".to_string(),
262 );
263 patterns.insert(
264 "spec.template.metadata".to_string(),
265 "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta".to_string(),
266 );
267
268 patterns.insert(
270 "resources".to_string(),
271 "k8s.io/api/core/v1.ResourceRequirements".to_string(),
272 );
273 patterns.insert(
274 "spec.resources".to_string(),
275 "k8s.io/api/core/v1.ResourceRequirements".to_string(),
276 );
277
278 patterns.insert("env".to_string(), "[]k8s.io/api/core/v1.EnvVar".to_string());
280 patterns.insert(
281 "envFrom".to_string(),
282 "[]k8s.io/api/core/v1.EnvFromSource".to_string(),
283 );
284
285 patterns.insert(
287 "volumeMounts".to_string(),
288 "[]k8s.io/api/core/v1.VolumeMount".to_string(),
289 );
290
291 patterns.insert(
293 "securityContext".to_string(),
294 "k8s.io/api/core/v1.SecurityContext".to_string(),
295 );
296 patterns.insert(
297 "affinity".to_string(),
298 "k8s.io/api/core/v1.Affinity".to_string(),
299 );
300 patterns.insert(
301 "tolerations".to_string(),
302 "[]k8s.io/api/core/v1.Toleration".to_string(),
303 );
304
305 Self { patterns }
306 }
307
308 pub fn get_go_type(&self, field_path: &str) -> Option<&String> {
310 self.patterns.get(field_path)
311 }
312
313 pub fn get_contextual_type(&self, field_name: &str, context: &[&str]) -> Option<&String> {
315 let full_path = format!("{}.{}", context.join("."), field_name);
317 if let Some(go_type) = self.patterns.get(&full_path) {
318 return Some(go_type);
319 }
320
321 self.patterns.get(field_name)
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_type_patterns() {
332 let patterns = K8sTypePatterns::new();
333
334 assert_eq!(
335 patterns.get_go_type("metadata"),
336 Some(&"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta".to_string())
337 );
338
339 assert_eq!(
340 patterns.get_contextual_type("volumes", &["spec"]),
341 Some(&"[]k8s.io/api/core/v1.Volume".to_string())
342 );
343 }
344}