Skip to main content

mantra_schema/
annotations.rs

1use crate::path::RelativePathBuf;
2
3use crate::{ConversionError, FmtHash, Line, LineSpan, Origin, Properties};
4
5use super::requirements::ReqId;
6
7/// Defines the schema to exchange mantra annotation related information.
8/// [req("exchange.traces.schema")]
9#[derive(
10    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
11)]
12#[serde(rename_all = "snake_case", deny_unknown_fields)]
13pub struct AnnotationSchema {
14    /// The schema version.
15    /// [req("exchange.versioned")]
16    #[serde(serialize_with = "crate::serialize_schema_version")]
17    pub schema_version: Option<String>,
18    /// List of files that contain mantra annotations.
19    pub files: Vec<FileAnnotations>,
20    /// Optional properties related to detected traces in all files in this entry.
21    ///
22    /// **Note:** If a trace sets a property key directly,
23    /// the value set at the trace will be taken.
24    pub trace_properties: Option<Properties>,
25    /// Optional base origin of the files in this entry.
26    /// e.g. specific branch or commit from a git repository
27    pub origin: Option<Origin>,
28}
29
30/// The annotation information per file.
31/// [req("changes.track.traces.files")]
32#[derive(
33    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
34)]
35#[serde(rename_all = "snake_case", deny_unknown_fields)]
36pub struct FileAnnotations {
37    /// File that contains traces and/or elements.
38    /// [req("trace.origin")]
39    #[schemars(with = "String")]
40    pub filepath: RelativePathBuf,
41    /// Hash of the file content to detect changes.
42    pub file_hash: FmtHash,
43    /// Annotations found in the file.
44    pub annotations: Annotations,
45    /// Content of the file.
46    pub content: Option<String>,
47}
48
49/// The annotation information mantra can collect.
50#[derive(
51    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
52)]
53#[serde(rename_all = "snake_case", deny_unknown_fields)]
54pub struct Annotations {
55    /// Traces detected in the file.
56    #[serde(default)]
57    pub traces: Vec<Trace>,
58    /// Elements detected in the file.
59    /// [req("trace.element", "testcov.static_approx")]
60    #[serde(default)]
61    pub elements: Vec<Element>,
62    /// Coverage excludes detected in the file.
63    ///
64    /// TODO: add requirement trace
65    #[serde(default)]
66    pub coverage_excludes: Vec<CoverageExclude>,
67}
68
69/// Coverage exclusion information found in a file.
70/// e.g. markers in code files may be used to exclude uncoverable lines from being considered for code coverage metrics.
71///
72/// TODO: add requirement trace
73#[derive(
74    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
75)]
76#[serde(deny_unknown_fields)]
77pub struct CoverageExclude {
78    /// The kind of coverage exclusion.
79    pub kind: CoverageExcludeKind,
80    /// Mandatory comment on why the exclusion is acceptable.
81    pub comment: String,
82}
83
84impl CoverageExclude {
85    /// The start line the coverage exclusion starts.
86    pub fn start_line(&self) -> Line {
87        self.kind.start_line()
88    }
89}
90
91/// The kind of coverage exclusion that was found in a file.
92#[derive(
93    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
94)]
95#[serde(rename_all = "snake_case", deny_unknown_fields)]
96pub enum CoverageExcludeKind {
97    /// Excludes a code span from coverage metrics.
98    /// Both lines are inclusive!
99    Block { start: Line, end: Line },
100    /// Excludes one line from coverage metrics.
101    Line(Line),
102}
103
104impl CoverageExcludeKind {
105    /// The start line the coverage exclusion starts.
106    pub fn start_line(&self) -> Line {
107        match self {
108            CoverageExcludeKind::Block { start, end: _ } => *start,
109            CoverageExcludeKind::Line(line) => *line,
110        }
111    }
112}
113
114/// A *mantra* trace.
115/// [req("trace")]
116#[derive(
117    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
118)]
119#[serde(rename_all = "snake_case", deny_unknown_fields)]
120pub struct Trace {
121    /// The requirement IDs that are referenced by the trace.
122    /// [req("trace.id", "trace.mult_reqs")]
123    pub ids: Vec<ReqId>,
124    /// The line the trace is defined at.
125    /// [req("trace.origin")]
126    pub line: Line,
127    /// Optional related code block or element that is linked to the trace.
128    /// [req("trace.code_block", "trace.element")]
129    pub related_code: Option<TraceRelatedCodeVariant>,
130    /// Trace kind.
131    /// [req("trace.kind`")]
132    pub kind: TraceKind,
133    /// List of custom properties that may be set on a trace.
134    /// [req("trace.properties")]
135    pub properties: Option<Properties>,
136}
137
138impl std::fmt::Display for Trace {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        write!(
141            f,
142            "Traces req({}) at line '{}'.",
143            self.ids
144                .iter()
145                .map(|id| id.to_string())
146                .collect::<Vec<String>>()
147                .join(","),
148            self.line
149        )?;
150
151        if let Some(code) = &self.related_code {
152            match code {
153                TraceRelatedCodeVariant::CodeBlock(code_block) => write!(
154                    f,
155                    " Related code block spans lines '{}..{}'.",
156                    code_block.span.start, code_block.span.end
157                )?,
158                TraceRelatedCodeVariant::ElementAtLine(line) => {
159                    write!(f, " Related element defined at line '{line}'.")?
160                }
161            }
162        }
163
164        Ok(())
165    }
166}
167
168/// The trace kind.
169/// [req("trace.kind")]
170#[derive(
171    Debug,
172    Clone,
173    Copy,
174    PartialEq,
175    Eq,
176    Hash,
177    serde::Serialize,
178    serde::Deserialize,
179    schemars::JsonSchema,
180)]
181#[serde(rename_all = "snake_case", deny_unknown_fields)]
182pub enum TraceKind {
183    /// Trace links to an artifact that provides clarification for a requirement.
184    Clarifies = 0,
185    /// Trace links to an artifact that satisfies a requirement.
186    Satisfies = 1,
187    /// Trace links to an artifact that verifies a requirement.
188    Verifies = 2,
189    /// Trace link that provides no additional information.
190    Links = 3,
191}
192
193impl TraceKind {
194    pub fn as_nr(&self) -> i32 {
195        *self as i32
196    }
197}
198
199impl TryFrom<i64> for TraceKind {
200    type Error = ConversionError;
201
202    fn try_from(value: i64) -> Result<Self, Self::Error> {
203        match value {
204            0 => Ok(TraceKind::Clarifies),
205            1 => Ok(TraceKind::Satisfies),
206            2 => Ok(TraceKind::Verifies),
207            3 => Ok(TraceKind::Links),
208            _ => Err(ConversionError::UnknownKind),
209        }
210    }
211}
212
213/// Possible related code variants for a trace.
214/// [req("trace.code_block", "trace.element")]
215#[derive(
216    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
217)]
218#[serde(untagged)]
219pub enum TraceRelatedCodeVariant {
220    /// Code block that is linked to the trace.
221    /// [req("trace.code_block")]
222    CodeBlock(CodeBlock),
223    /// Definition line of an element the trace is related to in the source file.
224    ///
225    /// e.g. line of a function definition.
226    /// [req("trace.element")]
227    ElementAtLine(Line),
228}
229
230/// A generic code block that is linked to a trace.
231/// [req("trace.code_block")]
232#[derive(
233    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
234)]
235#[serde(rename_all = "snake_case", deny_unknown_fields)]
236pub struct CodeBlock {
237    /// The kind of the code block.
238    pub kind: CodeBlockKind,
239    /// The line span of the code block.
240    /// [req("trace.code_block.span")]
241    pub span: LineSpan,
242    /// The SHA256 content hash of the code block.
243    pub content_hash: Option<FmtHash>,
244}
245
246/// The code block kind.
247#[derive(
248    Debug,
249    Clone,
250    Copy,
251    PartialEq,
252    Eq,
253    Hash,
254    serde::Serialize,
255    serde::Deserialize,
256    schemars::JsonSchema,
257)]
258#[serde(rename_all = "snake_case")]
259pub enum CodeBlockKind {
260    Other = 0,
261    If = 1,
262    ElseIf = 2,
263    Else = 3,
264    Loop = 4,
265    While = 5,
266    For = 6,
267    #[serde(alias = "switch", alias = "case")]
268    Match = 7,
269}
270
271impl CodeBlockKind {
272    pub fn as_nr(&self) -> i32 {
273        *self as i32
274    }
275}
276
277impl TryFrom<i64> for CodeBlockKind {
278    type Error = ConversionError;
279
280    fn try_from(value: i64) -> Result<Self, Self::Error> {
281        match value {
282            0 => Ok(CodeBlockKind::Other),
283            1 => Ok(CodeBlockKind::If),
284            2 => Ok(CodeBlockKind::ElseIf),
285            3 => Ok(CodeBlockKind::Else),
286            4 => Ok(CodeBlockKind::Loop),
287            5 => Ok(CodeBlockKind::While),
288            6 => Ok(CodeBlockKind::For),
289            7 => Ok(CodeBlockKind::Match),
290            _ => Err(ConversionError::UnknownKind),
291        }
292    }
293}
294
295/// A generic code element.
296/// e.g. function, module, type, ...
297/// [req("trace.element")]
298#[derive(
299    Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, schemars::JsonSchema,
300)]
301#[serde(rename_all = "snake_case", deny_unknown_fields)]
302pub struct Element {
303    /// The fully qualified identifier of the element.
304    /// [req("trace.element.ident")]
305    pub ident: Option<String>,
306    /// The element name.
307    ///
308    /// **Note:** This is not the fully qualified identifier.
309    pub name: String,
310    /// The line the element is defined at.
311    ///
312    /// **Note:** This might differ from `span.start`,
313    /// because in Rust for example, attributes & doc-comments are part of the span,
314    /// but the definition of an element starts below them.
315    ///
316    /// TODO: trace req
317    pub definition_line: Line,
318    /// The line span of the element.
319    /// [req("trace.element.span")]
320    pub span: LineSpan,
321    /// The kind of the element.
322    /// [req("trace.element.kind")]
323    pub kind: ElementKind,
324    /// The SHA256 content hash of the element.
325    pub content_hash: Option<FmtHash>,
326}
327
328impl std::fmt::Display for Element {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        if self.kind == ElementKind::Test {
331            write!(f, "test: ")?;
332        }
333
334        write!(f, "`{}` @{}..{}", self.name, self.span.start, self.span.end)
335    }
336}
337
338/// Defines supported element kinds.
339/// [req("trace.element.kind")]
340#[derive(
341    Debug,
342    Clone,
343    Copy,
344    PartialEq,
345    Eq,
346    Hash,
347    serde::Serialize,
348    serde::Deserialize,
349    schemars::JsonSchema,
350)]
351#[serde(rename_all = "snake_case")]
352pub enum ElementKind {
353    /// Variant that should be used if no other one fits.
354    Other = 0,
355    /// Marks an element as a test or test case.
356    #[serde(alias = "test_case")]
357    Test = 1,
358    /// A module or package.
359    #[serde(alias = "mod", alias = "package")]
360    Module = 2,
361    /// A function or method.
362    #[serde(alias = "fn", alias = "method")]
363    Function = 3,
364    /// A variable or static.
365    #[serde(alias = "var", alias = "static")]
366    Variable = 4,
367    /// A constant.
368    Const = 5,
369    /// A type, struct, enum, class, or union.
370    #[serde(alias = "struct", alias = "enum", alias = "class", alias = "union")]
371    Type = 6,
372    /// A field or property.
373    #[serde(alias = "property")]
374    Field = 7,
375    /// A trait, interface, or other abstract type.
376    #[serde(alias = "interface", alias = "abstract_type")]
377    Trait = 8,
378    /// A function signature or virtual function that has no *body*. It is likely declared inside a trait/interface.
379    #[serde(alias = "virtual_function")]
380    FunctionSignature = 9,
381}
382
383impl ElementKind {
384    pub fn as_nr(&self) -> i32 {
385        *self as i32
386    }
387}
388
389impl TryFrom<i64> for ElementKind {
390    type Error = ConversionError;
391
392    fn try_from(value: i64) -> Result<Self, Self::Error> {
393        match value {
394            0 => Ok(ElementKind::Other),
395            1 => Ok(ElementKind::Test),
396            2 => Ok(ElementKind::Module),
397            3 => Ok(ElementKind::Function),
398            4 => Ok(ElementKind::Variable),
399            5 => Ok(ElementKind::Const),
400            6 => Ok(ElementKind::Type),
401            7 => Ok(ElementKind::Field),
402            8 => Ok(ElementKind::Trait),
403            _ => Err(ConversionError::UnknownKind),
404        }
405    }
406}