use crate::parser::{SarifValidator, ValidationResult};
use crate::types::{
ArtifactLocation, MultiformatMessage, ReportingDescriptor, Tool, ToolComponent,
ToolComponentContents, ToolComponentReference, TranslationMetadata,
};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ToolBuilder {
driver: ToolComponent,
extensions: Vec<ToolComponent>,
}
impl ToolBuilder {
pub fn new(driver_name: impl Into<String>) -> Self {
Self {
driver: ToolComponent::new(driver_name),
extensions: Vec::new(),
}
}
pub fn with_driver(driver: ToolComponent) -> Self {
Self {
driver,
extensions: Vec::new(),
}
}
pub fn with_driver_builder<F>(mut self, f: F) -> Self
where
F: FnOnce(ToolComponentBuilder) -> ToolComponentBuilder,
{
let builder = ToolComponentBuilder::from_component(self.driver);
self.driver = f(builder).build();
self
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.driver.version = Some(version.into());
self
}
pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
self.driver.organization = Some(organization.into());
self
}
pub fn add_rule(mut self, rule: ReportingDescriptor) -> Self {
if self.driver.rules.is_none() {
self.driver.rules = Some(Vec::new());
}
self.driver.rules.as_mut().unwrap().push(rule);
self
}
pub fn add_rules(mut self, rules: impl IntoIterator<Item = ReportingDescriptor>) -> Self {
if self.driver.rules.is_none() {
self.driver.rules = Some(Vec::new());
}
self.driver.rules.as_mut().unwrap().extend(rules);
self
}
pub fn add_simple_rule(self, id: impl Into<String>, name: impl Into<String>) -> Self {
let rule = ReportingDescriptor::new(id.into()).with_name(name.into());
self.add_rule(rule)
}
pub fn add_extension(mut self, extension: ToolComponent) -> Self {
self.extensions.push(extension);
self
}
pub fn add_extensions(mut self, extensions: impl IntoIterator<Item = ToolComponent>) -> Self {
self.extensions.extend(extensions);
self
}
pub fn validate(&self, validator: &SarifValidator) -> ValidationResult<()> {
let tool = self.clone().build();
validator.validate_tool(&tool)
}
pub fn build(self) -> Tool {
Tool {
driver: self.driver,
extensions: if self.extensions.is_empty() {
None
} else {
Some(self.extensions)
},
properties: None,
}
}
pub fn build_validated(self, validator: &SarifValidator) -> ValidationResult<Tool> {
self.validate(validator)?;
Ok(self.build())
}
}
#[derive(Debug, Clone)]
pub struct ToolComponentBuilder {
guid: Option<String>,
name: String,
organization: Option<String>,
product: Option<String>,
product_suite: Option<String>,
short_description: Option<MultiformatMessage>,
full_description: Option<MultiformatMessage>,
full_name: Option<String>,
version: Option<String>,
semantic_version: Option<String>,
dotted_quad_file_version: Option<String>,
release_date_utc: Option<String>,
download_uri: Option<String>,
information_uri: Option<String>,
global_message_strings: Option<HashMap<String, MultiformatMessage>>,
notifications: Vec<ReportingDescriptor>,
rules: Vec<ReportingDescriptor>,
taxa: Vec<ReportingDescriptor>,
locations: Vec<ArtifactLocation>,
language: Option<String>,
contents: Option<Vec<ToolComponentContents>>,
is_comprehensive: Option<bool>,
localized_data_semantic_version: Option<String>,
minimum_required_localized_data_semantic_version: Option<String>,
associated_component: Option<ToolComponentReference>,
translation_metadata: Option<TranslationMetadata>,
supported_taxonomies: Vec<ToolComponentReference>,
}
impl ToolComponentBuilder {
pub fn new(name: impl Into<String>) -> Self {
Self {
guid: None,
name: name.into(),
organization: None,
product: None,
product_suite: None,
short_description: None,
full_description: None,
full_name: None,
version: None,
semantic_version: None,
dotted_quad_file_version: None,
release_date_utc: None,
download_uri: None,
information_uri: None,
global_message_strings: None,
notifications: Vec::new(),
rules: Vec::new(),
taxa: Vec::new(),
locations: Vec::new(),
language: None,
contents: None,
is_comprehensive: None,
localized_data_semantic_version: None,
minimum_required_localized_data_semantic_version: None,
associated_component: None,
translation_metadata: None,
supported_taxonomies: Vec::new(),
}
}
pub fn from_component(component: ToolComponent) -> Self {
Self {
guid: component.guid,
name: component.name,
organization: component.organization,
product: component.product,
product_suite: component.product_suite,
short_description: component.short_description,
full_description: component.full_description,
full_name: component.full_name,
version: component.version,
semantic_version: component.semantic_version,
dotted_quad_file_version: component.dotted_quad_file_version,
release_date_utc: component.release_date_utc,
download_uri: component.download_uri,
information_uri: component.information_uri,
global_message_strings: component.global_message_strings,
notifications: component.notifications.unwrap_or_default(),
rules: component.rules.unwrap_or_default(),
taxa: component.taxa.unwrap_or_default(),
locations: component.locations.unwrap_or_default(),
language: component.language,
contents: component.contents,
is_comprehensive: component.is_comprehensive,
localized_data_semantic_version: component.localized_data_semantic_version,
minimum_required_localized_data_semantic_version: component
.minimum_required_localized_data_semantic_version,
associated_component: component.associated_component,
translation_metadata: component.translation_metadata,
supported_taxonomies: component.supported_taxonomies.unwrap_or_default(),
}
}
pub fn with_guid(mut self, guid: impl Into<String>) -> Self {
self.guid = Some(guid.into());
self
}
pub fn with_organization(mut self, organization: impl Into<String>) -> Self {
self.organization = Some(organization.into());
self
}
pub fn with_product(mut self, product: impl Into<String>) -> Self {
self.product = Some(product.into());
self
}
pub fn with_product_suite(mut self, product_suite: impl Into<String>) -> Self {
self.product_suite = Some(product_suite.into());
self
}
pub fn with_short_description(mut self, description: MultiformatMessage) -> Self {
self.short_description = Some(description);
self
}
pub fn with_short_description_text(mut self, text: impl Into<String>) -> Self {
self.short_description = Some(MultiformatMessage {
text: text.into(),
markdown: None,
properties: None,
});
self
}
pub fn with_full_description(mut self, description: MultiformatMessage) -> Self {
self.full_description = Some(description);
self
}
pub fn with_full_description_text(mut self, text: impl Into<String>) -> Self {
self.full_description = Some(MultiformatMessage {
text: text.into(),
markdown: None,
properties: None,
});
self
}
pub fn with_full_name(mut self, full_name: impl Into<String>) -> Self {
self.full_name = Some(full_name.into());
self
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
pub fn with_semantic_version(mut self, semantic_version: impl Into<String>) -> Self {
self.semantic_version = Some(semantic_version.into());
self
}
pub fn with_download_uri(mut self, uri: impl Into<String>) -> Self {
self.download_uri = Some(uri.into());
self
}
pub fn with_information_uri(mut self, uri: impl Into<String>) -> Self {
self.information_uri = Some(uri.into());
self
}
pub fn with_language(mut self, language: impl Into<String>) -> Self {
self.language = Some(language.into());
self
}
pub fn with_is_comprehensive(mut self, is_comprehensive: bool) -> Self {
self.is_comprehensive = Some(is_comprehensive);
self
}
pub fn add_rule(mut self, rule: ReportingDescriptor) -> Self {
self.rules.push(rule);
self
}
pub fn add_rules(mut self, rules: impl IntoIterator<Item = ReportingDescriptor>) -> Self {
self.rules.extend(rules);
self
}
pub fn add_simple_rule(mut self, id: impl Into<String>, name: impl Into<String>) -> Self {
let rule = ReportingDescriptor::new(id.into()).with_name(name.into());
self.rules.push(rule);
self
}
pub fn add_notification(mut self, notification: ReportingDescriptor) -> Self {
self.notifications.push(notification);
self
}
pub fn add_taxon(mut self, taxon: ReportingDescriptor) -> Self {
self.taxa.push(taxon);
self
}
pub fn add_location(mut self, location: ArtifactLocation) -> Self {
self.locations.push(location);
self
}
pub fn add_global_message_string(
mut self,
key: impl Into<String>,
message: MultiformatMessage,
) -> Self {
if self.global_message_strings.is_none() {
self.global_message_strings = Some(HashMap::new());
}
self.global_message_strings
.as_mut()
.unwrap()
.insert(key.into(), message);
self
}
pub fn add_supported_taxonomy(mut self, taxonomy: ToolComponentReference) -> Self {
self.supported_taxonomies.push(taxonomy);
self
}
pub fn validate(&self, validator: &SarifValidator) -> ValidationResult<()> {
if let Some(ref uri) = self.download_uri {
validator.validate_uri(uri)?;
}
if let Some(ref uri) = self.information_uri {
validator.validate_uri(uri)?;
}
for rule in &self.rules {
validator.validate_reporting_descriptor(rule)?;
}
for notification in &self.notifications {
validator.validate_reporting_descriptor(notification)?;
}
for taxon in &self.taxa {
validator.validate_reporting_descriptor(taxon)?;
}
for location in &self.locations {
validator.validate_artifact_location(location)?;
}
Ok(())
}
pub fn build(self) -> ToolComponent {
ToolComponent {
guid: self.guid,
name: self.name,
organization: self.organization,
product: self.product,
product_suite: self.product_suite,
short_description: self.short_description,
full_description: self.full_description,
full_name: self.full_name,
version: self.version,
semantic_version: self.semantic_version,
dotted_quad_file_version: self.dotted_quad_file_version,
release_date_utc: self.release_date_utc,
download_uri: self.download_uri,
information_uri: self.information_uri,
global_message_strings: self.global_message_strings,
notifications: if self.notifications.is_empty() {
None
} else {
Some(self.notifications)
},
rules: if self.rules.is_empty() {
None
} else {
Some(self.rules)
},
taxa: if self.taxa.is_empty() {
None
} else {
Some(self.taxa)
},
locations: if self.locations.is_empty() {
None
} else {
Some(self.locations)
},
language: self.language,
contents: self.contents,
is_comprehensive: self.is_comprehensive,
localized_data_semantic_version: self.localized_data_semantic_version,
minimum_required_localized_data_semantic_version: self
.minimum_required_localized_data_semantic_version,
associated_component: self.associated_component,
translation_metadata: self.translation_metadata,
supported_taxonomies: if self.supported_taxonomies.is_empty() {
None
} else {
Some(self.supported_taxonomies)
},
properties: None,
}
}
pub fn build_validated(self, validator: &SarifValidator) -> ValidationResult<ToolComponent> {
self.validate(validator)?;
Ok(self.build())
}
}