tf_bindgen/codegen/
mod.rs

1use std::collections::HashMap;
2
3use heck::ToUpperCamelCase;
4use itertools::Itertools;
5use semver::{Comparator, Op, VersionReq};
6use tf_bindgen_schema::provider::v1_0::{Attribute, Block, BlockType, Type};
7use tf_bindgen_schema::provider::Schema;
8
9use crate::codegen::type_info::TypeInfo;
10
11use self::field_info::FieldInfo;
12use self::path::Path;
13use self::struct_info::{StructInfo, StructType};
14use self::type_info::Wrapper;
15
16pub mod field_info;
17pub mod path;
18pub mod struct_info;
19pub mod type_info;
20
21pub use tf_bindgen_codegen::resource;
22pub use tf_bindgen_codegen::Construct;
23
24pub struct Generator {
25    pub providers: Vec<Provider>,
26}
27
28#[derive(Debug)]
29pub struct Provider {
30    pub provider: StructInfo,
31    pub resources: Vec<StructInfo>,
32    pub data_sources: Vec<StructInfo>,
33}
34
35pub struct Nested(Vec<StructInfo>);
36pub struct Constructs(Vec<StructInfo>);
37pub struct Fields(Vec<FieldInfo>);
38
39impl Generator {
40    pub fn from_schema(schema: Schema, versions: HashMap<String, VersionReq>) -> Self {
41        let schemas = match schema {
42            Schema::V1_0 { provider_schemas } => provider_schemas
43                .iter()
44                .map(|(url, schema)| {
45                    let name = url.split('/').last().unwrap();
46                    let version = versions
47                        .iter()
48                        .find(|(n, _)| n.split('/').last().unwrap() == name)
49                        .unwrap()
50                        .1
51                        .comparators
52                        .iter()
53                        .map(|comp| cargo_simplify_version(comp.clone()))
54                        .join(",");
55                    let provider =
56                        StructInfo::from_provider(name, version, url, &schema.provider.block);
57                    let resources = schema
58                        .resource_schemas
59                        .iter()
60                        .map(|(name, schema)| {
61                            let path = Path::empty();
62                            let this_path = Path::new(vec![name.to_string()]);
63                            let fields = Fields::from_schema(&this_path, &schema.block).0;
64                            let nested = Nested::from_schema(&this_path, &schema.block).0;
65                            let ty = StructType::Construct {
66                                ty: name.clone(),
67                                nested,
68                            };
69                            StructInfo::builder()
70                                .ty(ty)
71                                .name(name.clone())
72                                .path(path)
73                                .fields(fields)
74                                .build()
75                                .unwrap()
76                        })
77                        .collect();
78                    let data_sources = schema
79                        .data_source_schemas
80                        .iter()
81                        .map(|(name, schema)| {
82                            let path = Path::new(vec!["data".to_string()]);
83                            let this_path = Path::new(vec!["data".to_string(), name.to_string()]);
84                            let nested = Nested::from_schema(&this_path, &schema.block).0;
85                            let ty = StructType::Construct {
86                                ty: name.clone(),
87                                nested,
88                            };
89                            let fields = Fields::from_schema(&this_path, &schema.block).0;
90                            StructInfo::builder()
91                                .ty(ty)
92                                .name(name.clone())
93                                .path(path)
94                                .fields(fields)
95                                .build()
96                                .unwrap()
97                        })
98                        .collect();
99                    Provider {
100                        provider,
101                        resources,
102                        data_sources,
103                    }
104                })
105                .collect(),
106            Schema::Unknown => unimplemented!("unsupported provider version"),
107        };
108        Generator { providers: schemas }
109    }
110}
111
112impl StructInfo {
113    pub fn from_provider(
114        name: impl Into<String>,
115        version: impl Into<String>,
116        url: impl Into<String>,
117        schema: &Block,
118    ) -> Self {
119        let name = name.into();
120        let path = Path::empty();
121        let nested = Nested::from_schema(&path, schema);
122        let ty = StructType::Provider {
123            ty: url.into(),
124            ver: version.into(),
125            nested: nested.0,
126        };
127        let fields = Fields::from_schema(&path, schema);
128        StructInfo::builder()
129            .ty(ty)
130            .path(path)
131            .name(name)
132            .fields(fields.0)
133            .build()
134            .unwrap()
135    }
136}
137
138fn get_fields(ty: &BlockType) -> Option<&HashMap<String, BlockType>> {
139    match ty {
140        BlockType::Set(inner) | BlockType::Map(inner) | BlockType::List(inner) => get_fields(inner),
141        BlockType::Object(fields) => Some(fields),
142        _ => None,
143    }
144}
145
146impl Nested {
147    pub fn from_schema(path: &Path, block: &Block) -> Self {
148        let iter = block
149            .block_types
150            .iter()
151            .flatten()
152            .flat_map(|(name, field)| {
153                let mut this_path = path.clone();
154                this_path.push(name);
155                let schema = match field {
156                    Type::Single { block } => block,
157                    Type::List { block, .. } => block,
158                };
159                let this = StructInfo::from_type(path, name, field);
160                Nested::from_schema(&this_path, schema)
161                    .0
162                    .into_iter()
163                    .chain(vec![this])
164                    .collect::<Vec<_>>()
165            });
166        let nested = block
167            .attributes
168            .iter()
169            .flatten()
170            .flat_map(|(name, field)| {
171                let mut this_path = path.clone();
172                this_path.push(name);
173                let fields = get_fields(&field.r#type);
174                let this = fields
175                    .iter()
176                    .map(|fields| StructInfo::from_fields(path, name, fields));
177                fields
178                    .iter()
179                    .flat_map(move |fields| Nested::from_fields(&this_path, fields).0)
180                    .chain(this)
181                    .collect::<Vec<_>>()
182            })
183            .chain(iter)
184            .collect();
185        Nested(nested)
186    }
187
188    pub fn from_fields(path: &Path, fields: &HashMap<String, BlockType>) -> Self {
189        let nested = fields
190            .iter()
191            .filter_map(|(name, ty)| Some((name, get_fields(ty)?)))
192            .flat_map(|(name, fields)| {
193                let this = StructInfo::from_fields(path, name, fields);
194                let mut this_path = path.clone();
195                this_path.push(name);
196                Nested::from_fields(&this_path, fields)
197                    .0
198                    .into_iter()
199                    .chain(vec![this])
200                    .collect::<Vec<_>>()
201            })
202            .collect();
203        Nested(nested)
204    }
205}
206
207impl Fields {
208    pub fn from_schema(path: &Path, schema: &Block) -> Self {
209        let attr_fields = schema
210            .attributes
211            .iter()
212            .flatten()
213            .map(|(name, field)| FieldInfo::from_field(path, name, field));
214        let fields = schema
215            .block_types
216            .iter()
217            .flatten()
218            .map(|(name, field)| FieldInfo::from_type(path, name, field))
219            .chain(attr_fields)
220            .collect();
221        Fields(fields)
222    }
223
224    pub fn from_fields(path: &Path, fields: &HashMap<String, BlockType>) -> Self {
225        let fields = fields
226            .iter()
227            .map(|(name, ty)| {
228                FieldInfo::builder()
229                    .path(path.clone())
230                    .name(name.clone())
231                    .type_info(TypeInfo::from_schema(path, name, ty))
232                    .optional(true)
233                    .computed(false)
234                    .description(None)
235                    .build()
236                    .unwrap()
237            })
238            .collect();
239        Fields(fields)
240    }
241}
242
243impl StructInfo {
244    pub fn from_type(path: &Path, name: impl Into<String>, ty: &Type) -> Self {
245        let name = name.into();
246        let mut this_path = path.clone();
247        this_path.push(&name);
248        let ty: &Block = match ty {
249            Type::Single { block } => block,
250            Type::List { block, .. } => block,
251        };
252        let fields = Fields::from_schema(&this_path, ty).0;
253        StructInfo::builder()
254            .ty(StructType::Nested)
255            .path(path.clone())
256            .name(name)
257            .fields(fields)
258            .build()
259            .unwrap()
260    }
261
262    pub fn from_fields(
263        path: &Path,
264        name: impl Into<String>,
265        fields: &HashMap<String, BlockType>,
266    ) -> Self {
267        let name = name.into();
268        let mut this_path = path.clone();
269        this_path.push(&name);
270        let fields = Fields::from_fields(&this_path, fields);
271        StructInfo::builder()
272            .ty(StructType::Nested)
273            .path(path.clone())
274            .name(name)
275            .fields(fields.0)
276            .build()
277            .unwrap()
278    }
279}
280
281impl FieldInfo {
282    pub fn from_field(path: &Path, name: impl Into<String>, field: &Attribute) -> Self {
283        let opt = field.optional.unwrap_or(false);
284        let comp = field.computed.unwrap_or(false);
285        let req = field.required.unwrap_or(comp && !opt);
286        assert_ne!(opt, req, "Field must be optional and required not both.");
287        let name = name.into();
288        let type_info = TypeInfo::from_schema(path, &name, &field.r#type);
289        FieldInfo::builder()
290            .path(path.clone())
291            .name(name)
292            .type_info(type_info)
293            .description(field.description.clone())
294            .optional(opt)
295            .computed(comp)
296            .build()
297            .unwrap()
298    }
299
300    pub fn from_type(path: &Path, name: impl Into<String>, field: &Type) -> Self {
301        let name = name.into();
302        let req = match field {
303            Type::Single { .. } => false,
304            Type::List {
305                min_items: Some(1),
306                max_items: Some(1),
307                ..
308            } => true,
309            Type::List { .. } => false,
310        };
311        let type_name = path.type_name() + &name.to_upper_camel_case();
312        let type_wrapper = match field {
313            Type::Single { .. } => Wrapper::Type,
314            Type::List { .. } => Wrapper::List,
315        };
316        FieldInfo::builder()
317            .path(path.clone())
318            .name(name)
319            .type_info(TypeInfo::new(type_wrapper, type_name))
320            .optional(!req)
321            .computed(false)
322            .description(None)
323            .build()
324            .unwrap()
325    }
326}
327
328pub fn cargo_simplify_version(constraint: Comparator) -> String {
329    let major = constraint.major;
330    let minor = constraint.minor.unwrap_or(0);
331    let patch = constraint.patch.unwrap_or(0);
332    assert!(
333        constraint.pre.is_empty(),
334        "pre release constraints are not supported"
335    );
336    match constraint.op {
337        Op::Tilde if constraint.minor.is_some() => {
338            format!(">={major}{minor}{patch},<{major}{}.0", minor + 1)
339        }
340        Op::Caret if major == 0 && constraint.minor.is_none() => ">=0.0.0,<1.0.0".to_string(),
341        Op::Caret if major == 0 && minor == 0 && constraint.patch.is_some() => {
342            format!(">=0.0.{patch},<0.0.{}", patch + 1)
343        }
344        Op::Caret if major == 0 => {
345            format!(">=0.{minor}.0,<0.{}.0", minor + 1)
346        }
347        Op::Wildcard if constraint.minor.is_some() => {
348            format!(">={major}.{minor}.0,<{major}.{}.0", minor + 1)
349        }
350        Op::Tilde | Op::Caret | Op::Wildcard => {
351            format!(">={major}.{minor}.{patch},<{}.0.0", major + 1)
352        }
353        Op::Exact => format!("={major}.{minor}.{patch}"),
354        Op::Greater => format!(">{major}.{minor}.{patch}"),
355        Op::GreaterEq => format!(">={major}.{minor}.{patch}"),
356        Op::Less => format!("<{major}.{minor}.{patch}"),
357        Op::LessEq => format!("<={major}.{minor}.{patch}"),
358        _ => unimplemented!(),
359    }
360}