use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
static SEEN_INLINE_SOURCES: OnceLock<Mutex<HashSet<PathBuf>>> = OnceLock::new();
pub fn warn_pipeline_inline_sources(path: &Path, pipeline_name: &str) {
let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
let seen = SEEN_INLINE_SOURCES.get_or_init(|| Mutex::new(HashSet::new()));
let mut guard = seen.lock().expect("inline-sources warn mutex poisoned");
if !guard.insert(canonical) {
return;
}
drop(guard);
tracing::warn!(
pipeline = %pipeline_name,
path = %path.display(),
"pipeline declares inline 'sources:' block, which is deprecated; \
use '--source <file>' instead. Run 'rsigma rule migrate-sources' \
to extract sources into a standalone file. Pipeline-embedded \
sources will be removed in v1.0."
);
eprintln!(
"warning: pipeline '{}' ({}) declares an inline 'sources:' block, \
which is deprecated and will be removed in v1.0. Migrate with \
`rsigma rule migrate-sources -p {} -o sources.yml` and load via \
`--source sources.yml` on `rsigma engine daemon`.",
pipeline_name,
path.display(),
path.display(),
);
}
#[doc(hidden)]
pub fn reset_inline_sources_dedup_for_tests() {
if let Some(seen) = SEEN_INLINE_SOURCES.get() {
seen.lock()
.expect("inline-sources warn mutex poisoned")
.clear();
}
}
#[doc(hidden)]
pub fn tests_only_snapshot() -> HashSet<PathBuf> {
SEEN_INLINE_SOURCES
.get()
.map(|m| {
m.lock()
.expect("inline-sources warn mutex poisoned")
.clone()
})
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
static TEST_LOCK: Mutex<()> = Mutex::new(());
#[test]
fn dedup_suppresses_repeat_warnings_for_same_canonical_path() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let mut file = tempfile::Builder::new().suffix(".yml").tempfile().unwrap();
writeln!(file, "name: deprecated_pipeline").unwrap();
reset_inline_sources_dedup_for_tests();
warn_pipeline_inline_sources(file.path(), "deprecated_pipeline");
warn_pipeline_inline_sources(file.path(), "deprecated_pipeline");
let canonical = file.path().canonicalize().unwrap();
let seen = tests_only_snapshot();
assert!(
seen.contains(&canonical),
"canonical path should be recorded in dedup set"
);
}
#[test]
fn dedup_distinguishes_distinct_canonical_paths() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let a = tempfile::Builder::new().suffix(".yml").tempfile().unwrap();
let b = tempfile::Builder::new().suffix(".yml").tempfile().unwrap();
reset_inline_sources_dedup_for_tests();
warn_pipeline_inline_sources(a.path(), "a");
warn_pipeline_inline_sources(b.path(), "b");
let seen = tests_only_snapshot();
assert!(seen.contains(&a.path().canonicalize().unwrap()));
assert!(seen.contains(&b.path().canonicalize().unwrap()));
}
}