tf_provider/diagnostics.rs
1// This file is part of the tf-provider project
2//
3// Copyright (C) ANEO, 2024-2024. All rights reserved.
4//
5// Licensed under the Apache License, Version 2.0 (the "License")
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//! [`Diagnostics`] module
18
19use std::{
20 backtrace::{Backtrace, BacktraceStatus},
21 borrow::Cow,
22};
23
24use crate::{attribute_path::AttributePath, tfplugin6, utils::CollectDiagnostics};
25
26/// List of Errors and Warnings to send back to Terraform
27#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
28pub struct Diagnostics {
29 /// List of errors
30 pub errors: Vec<Diagnostic>,
31 /// List of warnings
32 pub warnings: Vec<Diagnostic>,
33}
34
35impl Diagnostics {
36 /// Add an error diagnostic
37 ///
38 /// # Arguments
39 ///
40 /// * `diag` - diagnostic
41 pub fn add_error(&mut self, diag: Diagnostic) {
42 self.errors.push(diag)
43 }
44
45 /// Add a warning diagnostic
46 ///
47 /// # Arguments
48 ///
49 /// * `diag` - diagnostic
50 pub fn add_warning(&mut self, diag: Diagnostic) {
51 self.warnings.push(diag)
52 }
53
54 /// Add an error
55 ///
56 /// # Arguments
57 ///
58 /// * `summary` - Summary of the diagnostic component
59 /// * `detail` - Detail of the diagnostic component
60 /// * `attribute` - Attribute path for the diagnostic component
61 pub fn error<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
62 &mut self,
63 summary: S,
64 detail: D,
65 attribute: AttributePath,
66 ) {
67 self.add_error(Diagnostic::new(summary, detail, attribute))
68 }
69
70 /// Add an error without [`AttributePath`]
71 ///
72 /// # Arguments
73 ///
74 /// * `summary` - Summary of the diagnostic component
75 /// * `detail` - Detail of the diagnostic component
76 pub fn root_error<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
77 &mut self,
78 summary: S,
79 detail: D,
80 ) {
81 self.add_error(Diagnostic::root(summary, detail))
82 }
83
84 /// Add an error without details
85 ///
86 /// # Arguments
87 ///
88 /// * `summary` - Summary of the diagnostic component
89 /// * `attribute` - Attribute path for the diagnostic component
90 pub fn error_short<S: Into<Cow<'static, str>>>(
91 &mut self,
92 summary: S,
93 attribute: AttributePath,
94 ) {
95 self.add_error(Diagnostic::short(summary, attribute))
96 }
97
98 /// Add an error without [`AttributePath`] nor details
99 ///
100 /// # Arguments
101 ///
102 /// * `summary` - Summary of the diagnostic component
103 pub fn root_error_short<S: Into<Cow<'static, str>>>(&mut self, summary: S) {
104 self.add_error(Diagnostic::root_short(summary))
105 }
106
107 /// Add a warning
108 ///
109 /// # Arguments
110 ///
111 /// * `summary` - Summary of the diagnostic component
112 /// * `detail` - Detail of the diagnostic component
113 /// * `attribute` - Attribute path for the diagnostic component
114 pub fn warning<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
115 &mut self,
116 summary: S,
117 detail: D,
118 attribute: AttributePath,
119 ) {
120 self.add_warning(Diagnostic::new(summary, detail, attribute))
121 }
122
123 /// Add a warning without [`AttributePath`]
124 ///
125 /// # Arguments
126 ///
127 /// * `summary` - Summary of the diagnostic component
128 /// * `detail` - Detail of the diagnostic component
129 pub fn root_warning<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
130 &mut self,
131 summary: S,
132 detail: D,
133 ) {
134 self.add_warning(Diagnostic::root(summary, detail))
135 }
136
137 /// Add a warning without details
138 ///
139 /// # Arguments
140 ///
141 /// * `summary` - Summary of the diagnostic component
142 /// * `attribute` - Attribute path for the diagnostic component
143 pub fn warning_short<S: Into<Cow<'static, str>>>(
144 &mut self,
145 summary: S,
146 attribute: AttributePath,
147 ) {
148 self.add_warning(Diagnostic::short(summary, attribute))
149 }
150
151 /// Add a warning without [`AttributePath`] nor details
152 ///
153 /// # Arguments
154 ///
155 /// * `summary` - Summary of the diagnostic component
156 pub fn root_warning_short<S: Into<Cow<'static, str>>>(&mut self, summary: S) {
157 self.add_warning(Diagnostic::root_short(summary))
158 }
159
160 /// Append other diagnostics
161 pub fn add_diagnostics(&mut self, mut diags: Diagnostics) {
162 self.errors.append(&mut diags.errors);
163 self.warnings.append(&mut diags.warnings);
164 }
165
166 /// Add an internal error if there is no existing errors
167 pub fn internal_error(&mut self) {
168 Option::<()>::None.collect_diagnostics(self);
169 }
170
171 /// Create an error for a function argument
172 ///
173 /// # Arguments
174 ///
175 /// * `index` - index of the argument triggering the diagnostics
176 /// * `message` - Short message of the diagnostics
177 pub fn function_error<S: Into<Cow<'static, str>>>(&mut self, index: i64, message: S) {
178 self.add_error(Diagnostic::function(index, message))
179 }
180}
181
182/// Diagnostic component
183#[derive(Clone, PartialEq, Eq, Hash, Debug)]
184pub struct Diagnostic {
185 /// Summary of the diagnostic component
186 pub summary: Cow<'static, str>,
187 /// Detail of the diagnostic component
188 pub detail: Cow<'static, str>,
189 /// Attribute path for the diagnostic component
190 pub attribute: AttributePath,
191}
192
193/// Diagnostic
194impl Diagnostic {
195 /// Create a diagnostic
196 ///
197 /// # Arguments
198 ///
199 /// * `summary` - Summary of the diagnostic component
200 /// * `detail` - Detail of the diagnostic component
201 /// * `attribute` - Attribute path for the diagnostic component
202 pub fn new<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
203 summary: S,
204 detail: D,
205 attribute: AttributePath,
206 ) -> Self {
207 let backtrace = Backtrace::capture();
208 let mut detail = detail.into();
209 if backtrace.status() == BacktraceStatus::Captured {
210 if detail.is_empty() {
211 detail = format!("{}", backtrace).into();
212 } else {
213 detail = format!("{}\n{}", detail, backtrace).into();
214 }
215 }
216 Self {
217 summary: summary.into(),
218 detail,
219 attribute,
220 }
221 }
222
223 /// Create a diagnostic for a function argument
224 ///
225 /// # Arguments
226 ///
227 /// * `index` - index of the argument triggering the diagnostics
228 /// * `message` - Short message of the diagnostics
229 pub fn function<S: Into<Cow<'static, str>>>(index: i64, message: S) -> Self {
230 Self::new(
231 message,
232 String::default(),
233 AttributePath::function_argument(index),
234 )
235 }
236
237 /// Create a diagnostic without [`AttributePath`]
238 ///
239 /// # Arguments
240 ///
241 /// * `summary` - Summary of the diagnostic component
242 /// * `detail` - Detail of the diagnostic component
243 pub fn root<S: Into<Cow<'static, str>>, D: Into<Cow<'static, str>>>(
244 summary: S,
245 detail: D,
246 ) -> Self {
247 Self::new(summary, detail, Default::default())
248 }
249
250 /// Create a diagnostic without details
251 ///
252 /// # Arguments
253 ///
254 /// * `summary` - Summary of the diagnostic component
255 /// * `attribute` - Attribute path for the diagnostic component
256 pub fn short<S: Into<Cow<'static, str>>>(summary: S, attribute: AttributePath) -> Self {
257 Self::new(summary, String::default(), attribute)
258 }
259 /// Create a diagnostic [`AttributePath`] nor details
260 ///
261 /// # Arguments
262 ///
263 /// * `summary` - Summary of the diagnostic component
264 pub fn root_short<S: Into<Cow<'static, str>>>(summary: S) -> Self {
265 Self::new(summary, String::default(), Default::default())
266 }
267}
268
269impl From<Diagnostics> for ::prost::alloc::vec::Vec<tfplugin6::Diagnostic> {
270 fn from(value: Diagnostics) -> Self {
271 use tfplugin6::diagnostic::Severity;
272 let map_cvt = |vec: Vec<Diagnostic>, severity: Severity| {
273 vec.into_iter().map(move |diag| tfplugin6::Diagnostic {
274 severity: severity.into(),
275 summary: diag.summary.into_owned(),
276 detail: diag.detail.into_owned(),
277 attribute: if diag.attribute.steps.is_empty() {
278 None
279 } else {
280 Some(diag.attribute.into())
281 },
282 })
283 };
284 map_cvt(value.errors, Severity::Error)
285 .chain(map_cvt(value.warnings, Severity::Warning))
286 .collect()
287 }
288}