1use std::collections::BTreeMap;
16
17use magma_cty::CtyType;
18use magma_protocol::tfplugin6::schema::{self, Attribute, Block, NestedBlock, Object};
19
20pub use magma_provider_api::SchemaError;
23
24pub fn block_implied_type(block: &Block) -> Result<CtyType, SchemaError> {
27 let mut attrs: BTreeMap<String, CtyType> = BTreeMap::new();
28 for attr in &block.attributes {
29 attrs.insert(attr.name.clone(), attribute_type(attr)?);
30 }
31 for nb in &block.block_types {
32 attrs.insert(nb.type_name.clone(), nested_block_type(nb)?);
33 }
34 Ok(CtyType::Object(attrs))
35}
36
37fn attribute_type(attr: &Attribute) -> Result<CtyType, SchemaError> {
40 if let Some(obj) = &attr.nested_type {
41 return object_implied_type(obj, &attr.name);
42 }
43 if attr.r#type.is_empty() {
44 return Err(SchemaError::AttributeNoType(attr.name.clone()));
45 }
46 let json: serde_json::Value = serde_json::from_slice(&attr.r#type).map_err(|e| {
47 SchemaError::Cty(attr.name.clone(), magma_cty::CtyError::Type(e.to_string()))
48 })?;
49 CtyType::from_json(&json).map_err(|e| SchemaError::Cty(attr.name.clone(), e))
50}
51
52fn object_implied_type(obj: &Object, label: &str) -> Result<CtyType, SchemaError> {
54 let mut attrs = BTreeMap::new();
55 for attr in &obj.attributes {
56 attrs.insert(attr.name.clone(), attribute_type(attr)?);
57 }
58 let inner = CtyType::Object(attrs);
59 let nesting = schema::object::NestingMode::try_from(obj.nesting)
60 .map_err(|_| SchemaError::BadNesting(obj.nesting, label.to_string()))?;
61 Ok(match nesting {
62 schema::object::NestingMode::Single | schema::object::NestingMode::Invalid => inner,
63 schema::object::NestingMode::List => CtyType::list(inner),
64 schema::object::NestingMode::Set => CtyType::set(inner),
65 schema::object::NestingMode::Map => CtyType::map(inner),
66 })
67}
68
69fn nested_block_type(nb: &NestedBlock) -> Result<CtyType, SchemaError> {
72 let block = nb
73 .block
74 .as_ref()
75 .ok_or_else(|| SchemaError::EmptyNestedBlock(nb.type_name.clone()))?;
76 let inner = block_implied_type(block)?;
77 let nesting = schema::nested_block::NestingMode::try_from(nb.nesting)
78 .map_err(|_| SchemaError::BadNesting(nb.nesting, nb.type_name.clone()))?;
79 Ok(match nesting {
80 schema::nested_block::NestingMode::Single
81 | schema::nested_block::NestingMode::Group
82 | schema::nested_block::NestingMode::Invalid => inner,
83 schema::nested_block::NestingMode::List => CtyType::list(inner),
84 schema::nested_block::NestingMode::Set => CtyType::set(inner),
85 schema::nested_block::NestingMode::Map => CtyType::map(inner),
86 })
87}
88
89use magma_protocol::tfplugin5;
99
100pub fn block5_implied_type(block: &tfplugin5::schema::Block) -> Result<CtyType, SchemaError> {
103 block_implied_type(&block5_to_v6(block))
104}
105
106fn block5_to_v6(b: &tfplugin5::schema::Block) -> Block {
107 Block {
108 version: b.version,
109 attributes: b.attributes.iter().map(attr5_to_v6).collect(),
110 block_types: b.block_types.iter().map(nb5_to_v6).collect(),
111 ..Default::default()
112 }
113}
114
115fn attr5_to_v6(a: &tfplugin5::schema::Attribute) -> Attribute {
116 Attribute {
117 name: a.name.clone(),
118 r#type: a.r#type.clone(),
119 nested_type: None, ..Default::default()
121 }
122}
123
124fn nb5_to_v6(nb: &tfplugin5::schema::NestedBlock) -> NestedBlock {
125 NestedBlock {
126 type_name: nb.type_name.clone(),
127 block: nb.block.as_ref().map(block5_to_v6),
128 nesting: nb.nesting, ..Default::default()
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn ty_bytes(v: serde_json::Value) -> Vec<u8> {
139 serde_json::to_vec(&v).unwrap()
140 }
141
142 fn attr(name: &str, ty: serde_json::Value) -> Attribute {
143 Attribute {
144 name: name.into(),
145 r#type: ty_bytes(ty),
146 ..Default::default()
147 }
148 }
149
150 fn block(attributes: Vec<Attribute>, block_types: Vec<NestedBlock>) -> Block {
151 Block {
152 attributes,
153 block_types,
154 ..Default::default()
155 }
156 }
157
158 #[test]
159 fn simple_scalar_attributes() {
160 let b = block(
161 vec![
162 attr("name", serde_json::json!("string")),
163 attr("private", serde_json::json!("bool")),
164 attr("retries", serde_json::json!("number")),
165 ],
166 vec![],
167 );
168 let ty = block_implied_type(&b).unwrap();
169 let expected = CtyType::object([
170 ("name".into(), CtyType::String),
171 ("private".into(), CtyType::Bool),
172 ("retries".into(), CtyType::Number),
173 ]);
174 assert_eq!(ty, expected);
175 }
176
177 #[test]
178 fn collection_attribute_types() {
179 let b = block(
180 vec![
181 attr("topics", serde_json::json!(["list", "string"])),
182 attr("labels", serde_json::json!(["map", "string"])),
183 ],
184 vec![],
185 );
186 let ty = block_implied_type(&b).unwrap();
187 let expected = CtyType::object([
188 ("topics".into(), CtyType::list(CtyType::String)),
189 ("labels".into(), CtyType::map(CtyType::String)),
190 ]);
191 assert_eq!(ty, expected);
192 }
193
194 #[test]
195 fn nested_block_list_becomes_list_of_object() {
196 let inner = block(vec![attr("branch", serde_json::json!("string"))], vec![]);
198 let nb = NestedBlock {
199 type_name: "pages".into(),
200 block: Some(inner),
201 nesting: schema::nested_block::NestingMode::List as i32,
202 ..Default::default()
203 };
204 let b = block(vec![attr("name", serde_json::json!("string"))], vec![nb]);
205 let ty = block_implied_type(&b).unwrap();
206 let expected = CtyType::object([
207 ("name".into(), CtyType::String),
208 (
209 "pages".into(),
210 CtyType::list(CtyType::object([("branch".into(), CtyType::String)])),
211 ),
212 ]);
213 assert_eq!(ty, expected);
214 }
215
216 #[test]
217 fn nested_block_single_is_bare_object() {
218 let inner = block(vec![attr("id".into(), serde_json::json!("string"))], vec![]);
219 let nb = NestedBlock {
220 type_name: "template".into(),
221 block: Some(inner),
222 nesting: schema::nested_block::NestingMode::Single as i32,
223 ..Default::default()
224 };
225 let b = block(vec![], vec![nb]);
226 let ty = block_implied_type(&b).unwrap();
227 let expected = CtyType::object([(
228 "template".into(),
229 CtyType::object([("id".into(), CtyType::String)]),
230 )]);
231 assert_eq!(ty, expected);
232 }
233
234 #[test]
235 fn nested_type_object_map() {
236 let obj = Object {
238 attributes: vec![attr("v", serde_json::json!("string"))],
239 nesting: schema::object::NestingMode::Map as i32,
240 ..Default::default()
241 };
242 let a = Attribute {
243 name: "entries".into(),
244 nested_type: Some(obj),
245 ..Default::default()
246 };
247 let b = block(vec![a], vec![]);
248 let ty = block_implied_type(&b).unwrap();
249 let expected = CtyType::object([(
250 "entries".into(),
251 CtyType::map(CtyType::object([("v".into(), CtyType::String)])),
252 )]);
253 assert_eq!(ty, expected);
254 }
255
256 #[test]
257 fn attribute_without_type_is_an_error() {
258 let a = Attribute {
259 name: "broken".into(),
260 ..Default::default()
261 };
262 let b = block(vec![a], vec![]);
263 assert!(matches!(
264 block_implied_type(&b),
265 Err(SchemaError::AttributeNoType(_))
266 ));
267 }
268}