2024-05-09 21:14:14 +00:00
|
|
|
use std::fmt;
|
|
|
|
|
|
2024-05-08 09:52:34 +00:00
|
|
|
use indexmap::{indexmap, IndexMap};
|
2024-05-09 20:17:00 +00:00
|
|
|
use schemars::JsonSchema;
|
2024-05-09 21:14:14 +00:00
|
|
|
use serde::de::{self, Unexpected, Visitor};
|
|
|
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
2024-05-08 09:52:34 +00:00
|
|
|
use stringlit::s;
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
pub fn my_trim<S>(v: &str, s: S) -> Result<S::Ok, S::Error>
|
2024-05-08 09:52:34 +00:00
|
|
|
where
|
|
|
|
|
S: Serializer,
|
|
|
|
|
{
|
|
|
|
|
s.serialize_str(v.trim())
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
2024-05-08 09:52:34 +00:00
|
|
|
pub struct Requirement {
|
|
|
|
|
pub name: String,
|
|
|
|
|
#[serde(serialize_with = "my_trim")]
|
|
|
|
|
pub description: String,
|
2024-05-08 12:18:03 +00:00
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub requires: Vec<String>,
|
2024-05-08 09:52:34 +00:00
|
|
|
}
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
2024-05-08 09:52:34 +00:00
|
|
|
pub struct Topic {
|
|
|
|
|
pub name: String,
|
|
|
|
|
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
|
|
|
|
|
pub requirements: IndexMap<String, Requirement>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
|
|
|
|
|
pub subtopics: IndexMap<String, Topic>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
2024-05-08 09:52:34 +00:00
|
|
|
pub struct Definition {
|
|
|
|
|
pub name: String,
|
|
|
|
|
pub value: String,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub additional_info: Vec<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
2024-05-08 09:52:34 +00:00
|
|
|
pub struct ConfigDefault {
|
|
|
|
|
pub name: String,
|
|
|
|
|
#[serde(rename = "type")]
|
|
|
|
|
pub typ: String,
|
2024-05-08 12:18:03 +00:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub valid_values: Option<Vec<String>>,
|
2024-05-08 09:52:34 +00:00
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub unit: Option<String>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub default_value: Option<String>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
|
|
|
pub hint: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 21:14:14 +00:00
|
|
|
#[derive(JsonSchema, Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct Version {
|
|
|
|
|
major: u64,
|
|
|
|
|
minor: u64,
|
|
|
|
|
patch: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for Version {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Serialization as before
|
|
|
|
|
fn serialize_version<S>(version: &Version, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
where
|
|
|
|
|
S: Serializer,
|
|
|
|
|
{
|
|
|
|
|
serializer.serialize_str(&version.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Custom deserialization
|
|
|
|
|
fn deserialize_version<'de, D>(deserializer: D) -> Result<Version, D::Error>
|
|
|
|
|
where
|
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
|
{
|
|
|
|
|
struct VersionVisitor;
|
|
|
|
|
|
|
|
|
|
impl<'de> Visitor<'de> for VersionVisitor {
|
|
|
|
|
type Value = Version;
|
|
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
formatter.write_str("a version string in the format 'major.minor.patch'")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Version, E>
|
|
|
|
|
where
|
|
|
|
|
E: de::Error,
|
|
|
|
|
{
|
|
|
|
|
let parts: Vec<&str> = value.split('.').collect();
|
|
|
|
|
if parts.len() != 3 {
|
|
|
|
|
return Err(E::invalid_value(Unexpected::Str(value), &self));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let major = parts[0]
|
|
|
|
|
.parse::<u64>()
|
|
|
|
|
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))?;
|
|
|
|
|
let minor = parts[1]
|
|
|
|
|
.parse::<u64>()
|
|
|
|
|
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))?;
|
|
|
|
|
let patch = parts[2]
|
|
|
|
|
.parse::<u64>()
|
|
|
|
|
.map_err(|_| E::invalid_value(Unexpected::Str(value), &self))?;
|
|
|
|
|
|
|
|
|
|
Ok(Version {
|
|
|
|
|
major,
|
|
|
|
|
minor,
|
|
|
|
|
patch,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deserializer.deserialize_str(VersionVisitor)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 20:17:00 +00:00
|
|
|
#[derive(JsonSchema, Debug, Deserialize, Serialize)]
|
2024-05-08 09:52:34 +00:00
|
|
|
pub struct Project {
|
|
|
|
|
pub name: String,
|
2024-05-09 21:14:14 +00:00
|
|
|
#[serde(
|
|
|
|
|
serialize_with = "serialize_version",
|
|
|
|
|
deserialize_with = "deserialize_version"
|
|
|
|
|
)]
|
|
|
|
|
pub version: Version,
|
2024-05-08 09:52:34 +00:00
|
|
|
#[serde(serialize_with = "my_trim")]
|
|
|
|
|
pub description: String,
|
|
|
|
|
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
|
|
|
|
|
pub topics: IndexMap<String, Topic>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub definitions: Vec<Definition>,
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub config_defaults: Vec<ConfigDefault>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn demo_project() -> Project {
|
|
|
|
|
Project {
|
|
|
|
|
name: s!("journal-uploader"),
|
2024-05-09 21:14:14 +00:00
|
|
|
version: Version {
|
|
|
|
|
major: 1,
|
|
|
|
|
minor: 0,
|
|
|
|
|
patch: 0,
|
|
|
|
|
},
|
2024-05-09 20:17:00 +00:00
|
|
|
description: s!(r"
|
2024-05-08 09:52:34 +00:00
|
|
|
The journal-uploader has two main functionalities.
|
|
|
|
|
- Take a stream of log messages and filter them depending on their severity
|
2024-05-09 20:17:00 +00:00
|
|
|
- Upload journal logs for a specified time when activated through cloud call"),
|
2024-05-08 09:52:34 +00:00
|
|
|
topics: indexmap! {
|
|
|
|
|
s!("FEAT-1") => Topic {
|
|
|
|
|
name: s!("Traced Logging"),
|
|
|
|
|
subtopics: indexmap! {
|
|
|
|
|
s!("SUB-1") => Topic {
|
|
|
|
|
name: s!("File Monitoring"),
|
|
|
|
|
requirements: indexmap! {
|
|
|
|
|
s!("REQ-1") => Requirement {
|
|
|
|
|
name: s!("Continuous Monitoring"),
|
2024-05-09 20:17:00 +00:00
|
|
|
description: s!(r"The tool must continuously monitor a designated directory."),
|
2024-05-08 12:18:03 +00:00
|
|
|
requires: vec! [],
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
subtopics: indexmap! {}
|
|
|
|
|
},
|
|
|
|
|
s!("SUB-2") => Topic {
|
|
|
|
|
name: s!("File Detection"),
|
|
|
|
|
requirements: indexmap! {
|
|
|
|
|
s!("REQ-1") => Requirement {
|
|
|
|
|
name: s!("Detection of New Files"),
|
2024-05-09 20:17:00 +00:00
|
|
|
description: s!(r"The tool must detect the addition of new files in the monitored directory."),
|
2024-05-08 12:18:03 +00:00
|
|
|
requires: vec! [],
|
|
|
|
|
},
|
|
|
|
|
s!("REQ-2") => Requirement {
|
|
|
|
|
name: s!("Avoid Re-processing"),
|
2024-05-09 20:17:00 +00:00
|
|
|
description: s!(r"The tool must not process files that have already been processed."),
|
2024-05-08 12:18:03 +00:00
|
|
|
requires: vec! [],
|
2024-05-08 09:52:34 +00:00
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
subtopics: indexmap! {}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
requirements: indexmap! {},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
definitions: vec![
|
|
|
|
|
Definition {
|
|
|
|
|
name: s!("Default Journal Directory"),
|
|
|
|
|
value: s!("/run/log/journal/<machine_id>"),
|
|
|
|
|
additional_info: vec![s!("Machine ID can be found at /etc/machine-id")],
|
|
|
|
|
},
|
|
|
|
|
Definition {
|
|
|
|
|
name: s!("Default Output Directory"),
|
|
|
|
|
value: s!("/run/log/filtered-journal"),
|
|
|
|
|
additional_info: vec![],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
config_defaults: vec![
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Journal Directory"),
|
|
|
|
|
typ: s!("Path"),
|
|
|
|
|
unit: None,
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: None,
|
|
|
|
|
hint: None,
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Output Directory"),
|
|
|
|
|
typ: s!("Path"),
|
|
|
|
|
unit: None,
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: None,
|
|
|
|
|
hint: None,
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Trigger Priority"),
|
|
|
|
|
typ: s!("Enum"),
|
|
|
|
|
unit: None,
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: Some(vec![
|
2024-05-08 09:52:34 +00:00
|
|
|
s!("Emergency"),
|
|
|
|
|
s!("Alert"),
|
|
|
|
|
s!("Critical"),
|
|
|
|
|
s!("Error"),
|
|
|
|
|
s!("Warning"),
|
|
|
|
|
s!("Notice"),
|
|
|
|
|
s!("Info"),
|
|
|
|
|
s!("Debug"),
|
2024-05-08 12:18:03 +00:00
|
|
|
]),
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: Some(s!("Warning")),
|
|
|
|
|
hint: None,
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Journal Context"),
|
|
|
|
|
typ: s!("Integer"),
|
|
|
|
|
unit: Some(s!("Seconds")),
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: Some(s!("15")),
|
|
|
|
|
hint: None,
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Max File Size"),
|
|
|
|
|
typ: s!("Integer"),
|
|
|
|
|
unit: Some(s!("Bytes")),
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: Some(s!("8388608")),
|
|
|
|
|
hint: Some(s!("(8 MB)")),
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("Max Directory Size"),
|
|
|
|
|
typ: s!("Integer"),
|
|
|
|
|
unit: Some(s!("Bytes")),
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: Some(s!("75497472")),
|
|
|
|
|
hint: Some(s!("(72 MB)")),
|
|
|
|
|
},
|
|
|
|
|
ConfigDefault {
|
|
|
|
|
name: s!("File Monitoring Interval"),
|
|
|
|
|
typ: s!("Integer"),
|
|
|
|
|
unit: Some(s!("Seconds")),
|
2024-05-08 12:18:03 +00:00
|
|
|
valid_values: None,
|
2024-05-08 09:52:34 +00:00
|
|
|
default_value: Some(s!("10")),
|
|
|
|
|
hint: None,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|