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}