sarif_rust/types/
artifact.rs

1//! SARIF artifact-related types
2//!
3//! This module defines structures for representing artifacts (files) and their locations.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use url::Url;
8
9/// Specifies the location of an artifact
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct ArtifactLocation {
13    /// A string containing a valid relative or absolute URI
14    pub uri: Option<String>,
15
16    /// A string which indirectly specifies the absolute URI
17    pub uri_base_id: Option<String>,
18
19    /// The index within the 'artifacts' array of the artifact object
20    pub index: Option<i32>,
21
22    /// A short description of the artifact location
23    pub description: Option<crate::types::Message>,
24
25    /// Key/value pairs that provide additional information about the artifact location
26    #[serde(flatten)]
27    pub properties: Option<HashMap<String, serde_json::Value>>,
28}
29
30/// A single file
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct Artifact {
34    /// The location of the artifact
35    pub location: Option<ArtifactLocation>,
36
37    /// Identifies the index of the immediate parent of the artifact
38    pub parent_index: Option<i32>,
39
40    /// The offset in bytes of the artifact within its containing artifact
41    pub offset: Option<i32>,
42
43    /// The length of the artifact in bytes
44    pub length: Option<i32>,
45
46    /// The role or roles played by the artifact in the analysis
47    pub roles: Option<Vec<ArtifactRole>>,
48
49    /// The MIME type of the artifact
50    pub mime_type: Option<String>,
51
52    /// The contents of the artifact
53    pub contents: Option<ArtifactContent>,
54
55    /// Specifies the encoding for an artifact object that refers to a text file
56    pub encoding: Option<String>,
57
58    /// Specifies the source language for any artifact that contains source code
59    pub source_language: Option<String>,
60
61    /// An array of hash objects
62    pub hashes: Option<Vec<Hash>>,
63
64    /// The Coordinated Universal Time (UTC) date and time at which the artifact was most recently modified
65    pub last_modified_time_utc: Option<String>,
66
67    /// A short description of the artifact
68    pub description: Option<crate::types::Message>,
69
70    /// Key/value pairs that provide additional information about the artifact
71    #[serde(flatten)]
72    pub properties: Option<HashMap<String, serde_json::Value>>,
73}
74
75/// The contents of an artifact
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct ArtifactContent {
79    /// UTF-8-encoded content from a text artifact
80    pub text: Option<String>,
81
82    /// MIME Base64-encoded content from a binary artifact, or from a text artifact in its original encoding
83    pub binary: Option<String>,
84
85    /// An alternate rendered representation of the artifact
86    pub rendered: Option<MultiformatMessageString>,
87
88    /// Key/value pairs that provide additional information about the artifact content
89    #[serde(flatten)]
90    pub properties: Option<HashMap<String, serde_json::Value>>,
91}
92
93/// A message string or message format string rendered in multiple formats
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct MultiformatMessageString {
97    /// A plain text message string
98    pub text: Option<String>,
99
100    /// A Markdown message string
101    pub markdown: Option<String>,
102
103    /// Key/value pairs that provide additional information
104    #[serde(flatten)]
105    pub properties: Option<HashMap<String, serde_json::Value>>,
106}
107
108/// The role or roles played by the artifact in the analysis
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase")]
111pub enum ArtifactRole {
112    /// The artifact is source code
113    AnalysisTarget,
114    /// The artifact contains or represents an attachment
115    Attachment,
116    /// The artifact is a response file
117    ResponseFile,
118    /// The artifact is a result file
119    ResultFile,
120    /// The artifact is a standard stream
121    StandardStream,
122    /// The artifact contains tracing information
123    TracedFile,
124    /// The artifact was generated during the build
125    Unmodified,
126    /// The artifact was modified during the build
127    Modified,
128    /// The artifact was added during the build
129    Added,
130    /// The artifact was deleted during the build
131    Deleted,
132    /// The artifact was renamed during the build
133    Renamed,
134    /// The artifact was translated during the build
135    Translated,
136    /// The artifact is a configuration file
137    ConfigurationFile,
138    /// The artifact is a policy file
139    PolicyFile,
140    /// The artifact is a debug file
141    DebugOutputFile,
142}
143
144/// A hash value
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct Hash {
148    /// The cryptographic hash of the artifact
149    pub value: String,
150
151    /// The name of the hash algorithm
152    pub algorithm: String,
153
154    /// Key/value pairs that provide additional information about the hash
155    #[serde(flatten)]
156    pub properties: Option<HashMap<String, serde_json::Value>>,
157}
158
159impl ArtifactLocation {
160    /// Create a new artifact location with a URI
161    pub fn new(uri: impl Into<String>) -> Self {
162        Self {
163            uri: Some(uri.into()),
164            uri_base_id: None,
165            index: None,
166            description: None,
167            properties: None,
168        }
169    }
170
171    /// Create an artifact location from an index
172    pub fn from_index(index: i32) -> Self {
173        Self {
174            uri: None,
175            uri_base_id: None,
176            index: Some(index),
177            description: None,
178            properties: None,
179        }
180    }
181
182    /// Set the URI base ID
183    pub fn with_uri_base_id(mut self, uri_base_id: impl Into<String>) -> Self {
184        self.uri_base_id = Some(uri_base_id.into());
185        self
186    }
187
188    /// Set the description
189    pub fn with_description(mut self, description: impl Into<crate::types::Message>) -> Self {
190        self.description = Some(description.into());
191        self
192    }
193
194    /// Parse the URI if present
195    pub fn parse_uri(&self) -> Result<Option<Url>, url::ParseError> {
196        match &self.uri {
197            Some(uri) => Ok(Some(Url::parse(uri)?)),
198            None => Ok(None),
199        }
200    }
201}
202
203impl Artifact {
204    /// Create a new artifact with a location
205    pub fn new(location: ArtifactLocation) -> Self {
206        Self {
207            location: Some(location),
208            parent_index: None,
209            offset: None,
210            length: None,
211            roles: None,
212            mime_type: None,
213            contents: None,
214            encoding: None,
215            source_language: None,
216            hashes: None,
217            last_modified_time_utc: None,
218            description: None,
219            properties: None,
220        }
221    }
222
223    /// Add a role to the artifact
224    pub fn add_role(mut self, role: ArtifactRole) -> Self {
225        self.roles.get_or_insert_with(Vec::new).push(role);
226        self
227    }
228
229    /// Set the MIME type
230    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
231        self.mime_type = Some(mime_type.into());
232        self
233    }
234
235    /// Set text content
236    pub fn with_text_content(mut self, text: impl Into<String>) -> Self {
237        let content = ArtifactContent {
238            text: Some(text.into()),
239            binary: None,
240            rendered: None,
241            properties: None,
242        };
243        self.contents = Some(content);
244        self
245    }
246}
247
248impl Hash {
249    /// Create a new hash
250    pub fn new(algorithm: impl Into<String>, value: impl Into<String>) -> Self {
251        Self {
252            algorithm: algorithm.into(),
253            value: value.into(),
254            properties: None,
255        }
256    }
257
258    /// Create an MD5 hash
259    pub fn md5(value: impl Into<String>) -> Self {
260        Self::new("md5", value)
261    }
262
263    /// Create a SHA-1 hash
264    pub fn sha1(value: impl Into<String>) -> Self {
265        Self::new("sha1", value)
266    }
267
268    /// Create a SHA-256 hash
269    pub fn sha256(value: impl Into<String>) -> Self {
270        Self::new("sha256", value)
271    }
272}