From f7c27eccaae22b070be9a9fe07ef5bccabae9086 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 22 May 2025 20:24:35 +0100 Subject: [PATCH] feat: Allow matching absolute paths --- src/config.rs | 78 +++++++++++++++++++++++++++++------------- src/instrumentation.rs | 21 +++++++----- src/lib.rs | 8 ++--- tests/common/mod.rs | 18 ++++++---- 4 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/config.rs b/src/config.rs index 771108c..157d7c4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,53 +7,83 @@ use nodejs_semver::{Range, SemverError, Version}; use std::path::PathBuf; #[derive(Debug, Clone)] -pub struct ModuleMatcher { +pub enum CodeMatcher { + Dependency { + name: String, + version_range: Range, + relative_path: PathBuf, + }, + AbsolutePaths { + absolute_paths: Vec, + }, +} + +#[derive(Debug, Clone)] +pub struct Dependency { pub name: String, - pub version_range: Range, - pub file_path: PathBuf, + pub version: Version, + pub relative_path: PathBuf, } -impl ModuleMatcher { - /// Creates a new `ModuleMatcher` instance. +impl CodeMatcher { + /// Creates a new `Dependency` code matcher. /// # Errors /// Returns an error if the version range cannot be parsed. - pub fn new(name: &str, version_range: &str, file_path: &str) -> Result { - Ok(Self { + pub fn dependency( + name: &str, + version_range: &str, + relative_path: &str, + ) -> Result { + Ok(CodeMatcher::Dependency { name: name.to_string(), version_range: Range::parse(version_range)?, - file_path: PathBuf::from(file_path), + relative_path: PathBuf::from(relative_path), }) } #[must_use] - pub fn matches(&self, module_name: &str, version: &str, file_path: &PathBuf) -> bool { - let version: Version = match version.parse() { - Ok(v) => v, - Err(e) => { - println!("Failed to parse version {version}: {e}"); - return false; - } - }; + pub fn absolute_paths(absolute_paths: Vec) -> Self { + CodeMatcher::AbsolutePaths { absolute_paths } + } - self.name == module_name - && version.satisfies(&self.version_range) - && self.file_path == *file_path + #[must_use] + pub fn matches(&self, absolute_path: &PathBuf, dependency: Option<&Dependency>) -> bool { + match self { + CodeMatcher::Dependency { + name, + version_range, + relative_path, + } => { + if let Some(dependency) = dependency { + *name == dependency.name + && dependency.version.satisfies(version_range) + && *relative_path == dependency.relative_path + } else { + false + } + } + CodeMatcher::AbsolutePaths { absolute_paths } => absolute_paths.contains(absolute_path), + } } } #[derive(Debug, Clone)] pub struct InstrumentationConfig { pub channel_name: String, - pub module: ModuleMatcher, + pub code_matcher: CodeMatcher, pub function_query: FunctionQuery, } impl InstrumentationConfig { #[must_use] - pub fn new(channel_name: &str, module: ModuleMatcher, function_query: FunctionQuery) -> Self { + pub fn new( + channel_name: &str, + code_matcher: CodeMatcher, + function_query: FunctionQuery, + ) -> Self { Self { channel_name: channel_name.to_string(), - module, + code_matcher, function_query, } } @@ -82,7 +112,7 @@ impl Config { impl InstrumentationConfig { #[must_use] - pub fn matches(&self, module_name: &str, version: &str, file_path: &PathBuf) -> bool { - self.module.matches(module_name, version, file_path) + pub fn matches(&self, absolute_path: &PathBuf, dependency: Option<&Dependency>) -> bool { + self.code_matcher.matches(absolute_path, dependency) } } diff --git a/src/instrumentation.rs b/src/instrumentation.rs index f2d0247..d4ef7c9 100644 --- a/src/instrumentation.rs +++ b/src/instrumentation.rs @@ -3,6 +3,7 @@ * This product includes software developed at Datadog (/). Copyright 2025 Datadog, Inc. **/ use crate::config::InstrumentationConfig; +use crate::Dependency; use std::path::PathBuf; use swc_core::common::{Span, SyntaxContext}; use swc_core::ecma::{ @@ -27,7 +28,7 @@ macro_rules! ident { /// /// [`Instrumentation`]: Instrumentation /// [`VisitMut`]: https://rustdoc.swc.rs/swc_core/ecma/visit/trait.VisitMut.html -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Instrumentation { config: InstrumentationConfig, count: usize, @@ -64,13 +65,17 @@ impl Instrumentation { fn create_tracing_channel(&self) -> Stmt { let ch_str = ident!(format!("tr_ch_apm${}", self.config.channel_name)); + let ch_string_value = match &self.config.code_matcher { + crate::CodeMatcher::Dependency { name, .. } => { + format!("orchestrion:{}:{}", name, self.config.channel_name) + } + crate::CodeMatcher::AbsolutePaths { .. } => { + format!("orchestrion:{}", self.config.channel_name) + } + }; let channel_string = Expr::Lit(Lit::Str(Str { span: Span::default(), - value: format!( - "orchestrion:{}:{}", - self.config.module.name, self.config.channel_name - ) - .into(), + value: ch_string_value.into(), raw: None, })); let define_channel = quote!( @@ -177,8 +182,8 @@ impl Instrumentation { } #[must_use] - pub fn matches(&self, module_name: &str, version: &str, file_path: &PathBuf) -> bool { - self.config.matches(module_name, version, file_path) + pub fn matches(&self, absolute_path: &PathBuf, dependency: Option<&Dependency>) -> bool { + self.config.matches(absolute_path, dependency) } // The rest of these functions are from `VisitMut`, except they return a boolean to indicate diff --git a/src/lib.rs b/src/lib.rs index d027f03..5ecc0af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,16 +65,16 @@ impl Instrumentor { /// For a given module name, version, and file path within the module, return all /// `Instrumentation` instances that match. + #[must_use] pub fn get_matching_instrumentations<'a>( &'a mut self, - module_name: &'a str, - version: &'a str, - file_path: &'a PathBuf, + absolute_path: &'a PathBuf, + dependency: Option<&'a Dependency>, ) -> InstrumentationVisitor<'a> { let instrumentations = self .instrumentations .iter_mut() - .filter(|instr| instr.matches(module_name, version, file_path)); + .filter(move |instr| instr.matches(absolute_path, dependency)); InstrumentationVisitor::new(instrumentations, self.dc_module.as_ref()) } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 6554d66..b188b90 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -3,6 +3,7 @@ * This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc. **/ use assert_cmd::prelude::*; +use nodejs_semver::Version; use orchestrion_js::*; use std::io::prelude::*; use std::path::PathBuf; @@ -83,8 +84,8 @@ fn transpile( .unwrap() } -static TEST_MODULE_NAME: &'static str = "undici"; -static TEST_MODULE_PATH: &'static str = "index.mjs"; +static TEST_MODULE_NAME: &str = "undici"; +static TEST_MODULE_PATH: &str = "index.mjs"; pub fn transpile_and_test(test_file: &str, mjs: bool, config: Config) { let test_file = PathBuf::from(test_file); @@ -92,8 +93,13 @@ pub fn transpile_and_test(test_file: &str, mjs: bool, config: Config) { let file_path = PathBuf::from("index.mjs"); let mut instrumentor = Instrumentor::new(config); - let mut instrumentations = - instrumentor.get_matching_instrumentations(TEST_MODULE_NAME, "0.0.1", &file_path); + let dep = Dependency { + name: TEST_MODULE_NAME.to_string(), + version: Version::parse("0.0.1").unwrap(), + relative_path: file_path, + }; + let abs = PathBuf::from(""); + let mut instrumentations = instrumentor.get_matching_instrumentations(&abs, Some(&dep)); let extension = if mjs { "mjs" } else { "js" }; let instrumentable = test_dir.join(format!("mod.{}", extension)); @@ -117,6 +123,6 @@ pub fn transpile_and_test(test_file: &str, mjs: bool, config: Config) { .success(); } -pub fn test_module_matcher() -> ModuleMatcher { - ModuleMatcher::new(TEST_MODULE_NAME, ">=0.0.1", TEST_MODULE_PATH).unwrap() +pub fn test_module_matcher() -> CodeMatcher { + CodeMatcher::dependency(TEST_MODULE_NAME, ">=0.0.1", TEST_MODULE_PATH).unwrap() }