use std::str::FromStr;
use thiserror::Error;
use crate::format::Format;
#[derive(Debug, PartialEq, Eq)]
pub struct SbomConfig {
pub format: Option<Format>,
pub included_dependencies: Option<IncludedDependencies>,
pub output_options: Option<OutputOptions>,
}
impl SbomConfig {
pub fn empty_config() -> Self {
Self {
format: None,
included_dependencies: None,
output_options: None,
}
}
pub fn merge(&self, other: &SbomConfig) -> SbomConfig {
SbomConfig {
format: other.format.or(self.format),
included_dependencies: other.included_dependencies.or(self.included_dependencies),
output_options: other
.output_options
.clone()
.or_else(|| self.output_options.clone()),
}
}
pub fn format(&self) -> Format {
self.format.unwrap_or_default()
}
pub fn included_dependencies(&self) -> IncludedDependencies {
self.included_dependencies.unwrap_or_default()
}
pub fn output_options(&self) -> OutputOptions {
self.output_options.clone().unwrap_or_default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IncludedDependencies {
TopLevelDependencies,
AllDependencies,
}
impl Default for IncludedDependencies {
fn default() -> Self {
Self::TopLevelDependencies
}
}
impl FromStr for IncludedDependencies {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"all" => Ok(Self::AllDependencies),
"top-level" => Ok(Self::TopLevelDependencies),
_ => Err(format!("Expected all or top-level, got `{}`", s)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OutputOptions {
pub cdx_extension: CdxExtension,
pub prefix: Prefix,
}
impl Default for OutputOptions {
fn default() -> Self {
Self {
cdx_extension: CdxExtension::default(),
prefix: Prefix::Pattern(Pattern::Bom),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CdxExtension {
Included,
NotIncluded,
}
impl CdxExtension {
pub fn extension(&self) -> String {
match &self {
CdxExtension::Included => ".cdx".to_string(),
CdxExtension::NotIncluded => "".to_string(),
}
}
}
impl Default for CdxExtension {
fn default() -> Self {
Self::NotIncluded
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Prefix {
Pattern(Pattern),
Custom(CustomPrefix),
}
impl Default for Prefix {
fn default() -> Self {
Self::Pattern(Pattern::default())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Pattern {
Bom,
Package,
}
impl Default for Pattern {
fn default() -> Self {
Self::Bom
}
}
impl FromStr for Pattern {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bom" => Ok(Self::Bom),
"package" => Ok(Self::Package),
_ => Err(format!("Expected bom or package, got `{}`", s)),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CustomPrefix(String);
impl CustomPrefix {
pub fn new(custom_prefix: impl Into<String>) -> Result<Self, PrefixError> {
let prefix = custom_prefix.into();
if prefix.contains(std::path::MAIN_SEPARATOR) {
Err(PrefixError::CustomPrefixError(
std::path::MAIN_SEPARATOR.to_string(),
))
} else {
Ok(Self(prefix))
}
}
}
impl ToString for CustomPrefix {
fn to_string(&self) -> String {
self.0.clone()
}
}
#[derive(Error, Debug, PartialEq, Eq)]
pub enum PrefixError {
#[error("Illegal characters in custom prefix string: {0}")]
CustomPrefixError(String),
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn it_should_error_for_a_prefix_with_a_path_separator() {
let prefix = format!("directory{}prefix", std::path::MAIN_SEPARATOR.to_string());
let actual = CustomPrefix::new(prefix)
.expect_err("Should not have been able to create CustomPrefix with path separator");
let expected = PrefixError::CustomPrefixError(std::path::MAIN_SEPARATOR.to_string());
assert_eq!(actual, expected);
}
#[test]
fn it_should_create_a_custom_prefix_from_a_valid_string() {
let prefix = "customprefix".to_string();
let actual = CustomPrefix::new(prefix.clone())
.expect("Should have been able to create CustomPrefix");
let expected = CustomPrefix(prefix);
assert_eq!(actual, expected);
}
}