tf_bindgen/codegen/
field_info.rs

1use itertools::Itertools;
2
3use super::{
4    path::Path,
5    type_info::{TypeInfo, Wrapper},
6};
7
8#[derive(derive_builder::Builder, Clone, Debug)]
9pub struct FieldInfo {
10    path: Path,
11    name: String,
12    type_info: TypeInfo,
13    description: Option<String>,
14    optional: bool,
15    computed: bool,
16}
17
18impl FieldInfo {
19    pub fn builder() -> FieldInfoBuilder {
20        FieldInfoBuilder::default()
21    }
22
23    pub fn gen_field(&self) -> String {
24        let name = self.name();
25        if self.is_computed() && !self.is_optional() {
26            let type_name = self.type_info.source();
27            return format!(
28                "#[serde(skip_serializing)] pub {name}: ::tf_bindgen::value::Cell<::tf_bindgen::value::Computed<{type_name}>>"
29            );
30        }
31        let type_name = self.field_type();
32        format!("pub {name}: {type_name}")
33    }
34
35    pub fn gen_builder_field(&self) -> String {
36        let name = self.name();
37        let type_name = self.type_info.source();
38        format!("{name}: ::std::option::Option<{type_name}>")
39    }
40
41    pub fn name(&self) -> &str {
42        fix_ident(&self.name)
43    }
44
45    pub fn raw_name(&self) -> &str {
46        &self.name
47    }
48
49    /// Returns `true` if this field is optional.
50    pub fn is_optional(&self) -> bool {
51        self.optional
52    }
53
54    /// Returns `true` if this field can be calculated by Terraform.
55    pub fn is_computed(&self) -> bool {
56        self.computed
57    }
58
59    /// Name of the reference used by terraform.
60    pub fn path_ref(&self) -> String {
61        self.path
62            .segments()
63            .chain(Some(&self.name).into_iter())
64            .join(".")
65    }
66
67    fn ty(&self) -> String {
68        let type_name = self.type_info.source();
69        if self.is_optional() {
70            format!("::std::option::Option<{type_name}>")
71        } else {
72            type_name
73        }
74    }
75
76    /// Type of field used inside of resources and nested types. Will be wrapped inside of
77    /// [`std::rc::Rc`] and [`crate::value::Cell`].
78    pub fn field_type(&self) -> String {
79        let type_name = self.ty();
80        format!("::tf_bindgen::value::Cell<{type_name}>")
81    }
82
83    /// Generated the builder's setter function.
84    pub fn builder_setter_impl(&self) -> String {
85        let name = self.name();
86        let fn_name = match name {
87            "build" => "build_",
88            _ => name,
89        };
90        let type_name = self.type_info.type_name();
91        let convert = match self.type_info.wrapper() {
92            Wrapper::List => "into_value_list()",
93            Wrapper::Map => "into_value_map()",
94            Wrapper::Type => "into_value()",
95            Wrapper::Set => "into_value_set()",
96        };
97        let impl_type = match self.type_info.wrapper() {
98            Wrapper::List => "IntoValueList",
99            Wrapper::Map => "IntoValueMap",
100            Wrapper::Type => "IntoValue",
101            Wrapper::Set => "IntoValueSet",
102        };
103        let body_impl = match self.type_info.wrapper() {
104            Wrapper::List => format!(
105                r#"let new_list = value.{convert};
106				if let Some(list) = &mut self.{name} {{
107					list.extend(new_list);
108				}} else {{
109					self.{name} = Some(new_list);
110				}}
111				self"#
112            ),
113            _ => format!(r#"self.{name} = Some(value.{convert}); self"#),
114        };
115        format!(
116            r#"pub fn {fn_name}(&mut self, value: impl ::tf_bindgen::value::{impl_type}<{type_name}>) -> &mut Self {{
117				{body_impl}
118			}}"#
119        )
120    }
121
122    /// Generate doc comment for field builder. Will be empty if node description was specified.
123    pub fn doc_str(&self) -> String {
124        self.description
125            .iter()
126            .flat_map(|desc| desc.split('\n'))
127            .map(|desc| "///".to_owned() + desc)
128            .join("\n")
129    }
130}
131
132/// Replace rust keywords with raw names.
133fn fix_ident(input: &str) -> &str {
134    assert!(!input.is_empty(), "ident: '{input}' is empty");
135    match input {
136        "type" => "r#type",
137        "as" => "r#as",
138        "async" => "r#async",
139        "await" => "r#await",
140        "box" => "r#box",
141        "break" => "r#break",
142        "const" => "r#const",
143        "continue" => "r#continue",
144        "dyn" => "r#dyn",
145        "else" => "r#else",
146        "enum" => "r#enum",
147        "extern" => "r#extern",
148        "fn" => "r#final",
149        "for" => "r#for",
150        "if" => "r#if",
151        "impl" => "r#impl",
152        "in" => "r#in",
153        "let" => "r#let",
154        "loop" => "r#loop",
155        "macro" => "r#macro",
156        "match" => "r#match",
157        "mod" => "r#mod",
158        "move" => "r#move",
159        "mut" => "r#mut",
160        "pub" => "r#pub",
161        "ref" => "r#ref",
162        "return" => "r#return",
163        "self" => "r#self",
164        "static" => "r#static",
165        "super" => "r#super",
166        "trait" => "r#trait",
167        "union" => "r#union",
168        "unsafe" => "r#unsafe",
169        "use" => "r#use",
170        "where" => "r#where",
171        "while" => "r#while",
172        "yield" => "r#yield",
173        _ => input,
174    }
175}