amalgam_parser/
k8s_authoritative.rs

1//! Authoritative Kubernetes type definitions from Go source
2
3use 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
11/// Authoritative source for Kubernetes type definitions
12pub struct K8sAuthoritativeTypes {
13    /// Parsed Go type information
14    go_types: HashMap<String, GoTypeInfo>,
15    /// Mapping from Go qualified names to TypeReferences
16    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    /// Initialize with authoritative Kubernetes types from Go source
34    pub async fn initialize(&mut self) -> Result<(), ParserError> {
35        let mut parser = GoASTParser::new();
36
37        // Parse core Kubernetes types
38        let k8s_types = parser.parse_k8s_core_types().await?;
39        self.go_types = k8s_types;
40
41        // Build the type mapping
42        self.build_type_mapping()?;
43
44        Ok(())
45    }
46
47    /// Build mapping from Go qualified names to TypeReferences
48    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    /// Convert Go qualified name and type info to TypeReference
58    fn go_qualified_name_to_type_ref(
59        &self,
60        _qualified_name: &str,
61        type_info: &GoTypeInfo,
62    ) -> Option<TypeReference> {
63        // Parse package path to determine group and version
64        // Examples:
65        // - k8s.io/api/core/v1.ObjectMeta -> k8s.io, v1, ObjectMeta
66        // - k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta -> k8s.io, v1, ObjectMeta
67
68        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    /// Get authoritative type information for a Go type
109    pub fn get_go_type(&self, qualified_name: &str) -> Option<&GoTypeInfo> {
110        self.go_types.get(qualified_name)
111    }
112
113    /// Get TypeReference for a Go qualified name
114    pub fn get_type_reference(&self, qualified_name: &str) -> Option<&TypeReference> {
115        self.type_mapping.get(qualified_name)
116    }
117
118    /// Convert Go type to Nickel TypeDefinition using authoritative data
119    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    /// Check if a field name in a CRD schema should be replaced with a known k8s type
135    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                // Status fields could reference specific status types
142                None // For now, leave as-is
143            }
144            _ => None,
145        }
146    }
147
148    /// Get field replacements based on field patterns and context
149    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            // Core metadata
157            ("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            // Common volume patterns
162            ("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            // Container patterns
170            ("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            // Resource patterns
178            ("resources", _) if matches!(current_type, Type::Record { .. }) => {
179                Some("k8s.io/api/core/v1.ResourceRequirements".to_string())
180            }
181
182            // Selector patterns
183            ("selector", _) if matches!(current_type, Type::Record { .. }) => {
184                Some("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector".to_string())
185            }
186
187            // Environment variables
188            ("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 and scheduling
196            ("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()) // Keep as map, but with precise typing
204            }
205
206            // Security context
207            ("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
219/// Pre-built registry of common Kubernetes type patterns
220pub 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        // Add common field -> Go type mappings
235        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        // Resource requirements
269        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        // Environment variables
279        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        // Volume mounts
286        patterns.insert(
287            "volumeMounts".to_string(),
288            "[]k8s.io/api/core/v1.VolumeMount".to_string(),
289        );
290
291        // Security and scheduling
292        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    /// Get the Go type for a field path
309    pub fn get_go_type(&self, field_path: &str) -> Option<&String> {
310        self.patterns.get(field_path)
311    }
312
313    /// Get Go type for a field with context
314    pub fn get_contextual_type(&self, field_name: &str, context: &[&str]) -> Option<&String> {
315        // Try full path first
316        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        // Try just the field name
322        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}