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}