tfparser_core/exporter/
schema.rs1use std::sync::Arc;
11
12use arrow::datatypes::{DataType, Field, Schema, TimeUnit};
13
14pub const PARSER_VERSION: &str = env!("CARGO_PKG_VERSION");
19
20pub const SCHEMA_MAJOR: u32 = 0;
22pub const SCHEMA_MINOR: u32 = 1;
24
25#[must_use]
27pub fn resources_schema() -> Schema {
28 Schema::new(vec![
29 utf8_field("workspace_root"),
30 utf8_field("component_path"),
31 utf8_field("module_path"),
32 utf8_field("address"),
33 utf8_field("kind"),
34 utf8_field("resource_type"),
35 utf8_field("resource_name"),
36 utf8_field("provider_local"),
37 utf8_field("provider_source"),
38 utf8_field("account_id"),
39 utf8_field("account_name"),
40 utf8_field("region"),
41 utf8_field("environment"),
42 utf8_field("count_expr"),
43 utf8_field("for_each_expr"),
44 Field::new(
45 "depends_on",
46 DataType::List(Arc::new(Field::new("item", DataType::Utf8, false))),
47 false,
48 ),
49 utf8_field("attributes_json"),
50 utf8_field("state_account_id"),
51 utf8_field("state_region"),
52 utf8_field("file"),
53 Field::new("line", DataType::UInt32, false),
54 Field::new("column", DataType::UInt32, false),
55 utf8_field("parser_version"),
56 Field::new(
57 "parsed_at",
58 DataType::Timestamp(TimeUnit::Millisecond, Some(Arc::from("UTC"))),
59 false,
60 ),
61 ])
62}
63
64#[must_use]
69pub fn schema_field_names() -> Vec<&'static str> {
70 vec![
71 "workspace_root",
72 "component_path",
73 "module_path",
74 "address",
75 "kind",
76 "resource_type",
77 "resource_name",
78 "provider_local",
79 "provider_source",
80 "account_id",
81 "account_name",
82 "region",
83 "environment",
84 "count_expr",
85 "for_each_expr",
86 "depends_on",
87 "attributes_json",
88 "state_account_id",
89 "state_region",
90 "file",
91 "line",
92 "column",
93 "parser_version",
94 "parsed_at",
95 ]
96}
97
98fn utf8_field(name: &'static str) -> Field {
99 Field::new(name, DataType::Utf8, false)
100}
101
102#[cfg(test)]
103#[allow(
104 clippy::unwrap_used,
105 clippy::expect_used,
106 clippy::panic,
107 clippy::indexing_slicing
108)]
109mod tests {
110 use super::*;
111
112 #[test]
113 fn test_schema_has_24_columns() {
114 let s = resources_schema();
115 assert_eq!(s.fields().len(), 24);
116 }
117
118 #[test]
119 fn test_schema_columns_match_documented_names() {
120 let s = resources_schema();
121 let got: Vec<&str> = s.fields().iter().map(|f| f.name().as_str()).collect();
122 assert_eq!(got, schema_field_names());
123 }
124
125 #[test]
126 fn test_depends_on_is_non_null_list_of_utf8() {
127 let s = resources_schema();
128 let depends_on = s.field_with_name("depends_on").unwrap();
129 assert!(!depends_on.is_nullable());
130 match depends_on.data_type() {
131 DataType::List(item) => {
132 assert_eq!(item.data_type(), &DataType::Utf8);
133 assert!(!item.is_nullable());
134 }
135 other => panic!("expected List<Utf8>, got {other:?}"),
136 }
137 }
138
139 #[test]
140 fn test_parsed_at_is_timestamp_ms_utc() {
141 let s = resources_schema();
142 let f = s.field_with_name("parsed_at").unwrap();
143 match f.data_type() {
144 DataType::Timestamp(TimeUnit::Millisecond, tz) => {
145 assert_eq!(tz.as_deref(), Some("UTC"));
146 }
147 other => panic!("expected Timestamp(ms, UTC), got {other:?}"),
148 }
149 }
150
151 #[test]
152 fn test_no_nullable_columns() {
153 let s = resources_schema();
154 for f in s.fields() {
155 assert!(!f.is_nullable(), "column `{}` is nullable", f.name());
156 }
157 }
158
159 #[test]
160 fn test_field_names_match_schema_size() {
161 let s = resources_schema();
162 assert_eq!(s.fields().len(), schema_field_names().len());
163 }
164}