Skip to content

Commit 3fa645b

Browse files
authored
feat: Error when code injection fails (#9)
1 parent 1993f68 commit 3fa645b

File tree

11 files changed

+122
-35
lines changed

11 files changed

+122
-35
lines changed

src/error.rs

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,17 @@ use std::fmt::{self, Display, Formatter};
66

77
#[derive(Debug)]
88
pub enum OrchestrionError {
9-
IoError(std::io::Error),
10-
StrError(String),
9+
InjectionMatchFailure(Vec<String>),
1110
}
1211

13-
impl From<std::io::Error> for OrchestrionError {
14-
fn from(e: std::io::Error) -> Self {
15-
OrchestrionError::IoError(e)
16-
}
17-
}
18-
19-
impl From<String> for OrchestrionError {
20-
fn from(s: String) -> Self {
21-
OrchestrionError::StrError(s)
22-
}
23-
}
24-
25-
impl From<&str> for OrchestrionError {
26-
fn from(s: &str) -> Self {
27-
OrchestrionError::StrError(s.to_string())
28-
}
29-
}
12+
impl std::error::Error for OrchestrionError {}
3013

3114
impl Display for OrchestrionError {
3215
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
3316
match self {
34-
OrchestrionError::IoError(e) => write!(f, "IO error: {e}"),
35-
OrchestrionError::StrError(s) => write!(f, "String error: {s}"),
17+
OrchestrionError::InjectionMatchFailure(missing) => {
18+
write!(f, "Failed to find injection points for: {missing:?}")
19+
}
3620
}
3721
}
3822
}

src/instrumentation.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ macro_rules! ident {
2929
/// [`VisitMut`]: https://rustdoc.swc.rs/swc_core/ecma/visit/trait.VisitMut.html
3030
#[derive(Debug, Clone)]
3131
pub struct Instrumentation {
32-
config: InstrumentationConfig,
32+
pub(crate) config: InstrumentationConfig,
3333
count: usize,
3434
is_correct_class: bool,
35+
has_injected: bool,
3536
}
3637

3738
impl Instrumentation {
@@ -41,14 +42,24 @@ impl Instrumentation {
4142
config,
4243
count: 0,
4344
is_correct_class: false,
45+
has_injected: false,
4446
}
4547
}
4648

49+
#[must_use]
50+
pub fn has_injected(&self) -> bool {
51+
self.has_injected
52+
}
53+
4754
pub(crate) fn reset(&mut self) {
4855
self.count = 0;
4956
self.is_correct_class = false;
5057
}
5158

59+
pub(crate) fn reset_has_injected(&mut self) {
60+
self.has_injected = false;
61+
}
62+
5263
fn new_fn(&self, body: BlockStmt) -> ArrowExpr {
5364
ArrowExpr {
5465
params: vec![],
@@ -114,6 +125,8 @@ impl Instrumentation {
114125
trace = trace_ident
115126
),
116127
];
128+
129+
self.has_injected = true;
117130
}
118131

119132
fn insert_constructor_tracing(&mut self, body: &mut BlockStmt) {
@@ -160,6 +173,8 @@ impl Instrumentation {
160173
quote!("const $ctx = { arguments };" as Stmt, ctx = ctx_ident,),
161174
try_catch,
162175
];
176+
177+
self.has_injected = true;
163178
}
164179

165180
fn trace_expr_or_count(&mut self, func_expr: &mut FnExpr, name: &Atom) -> bool {

src/lib.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ pub use instrumentation::*;
4747
mod function_query;
4848
pub use function_query::*;
4949

50+
use crate::error::OrchestrionError;
51+
5052
#[cfg(feature = "wasm")]
5153
pub mod wasm;
5254

@@ -112,6 +114,33 @@ impl InstrumentationVisitor {
112114
!self.instrumentations.is_empty()
113115
}
114116

117+
#[must_use]
118+
pub fn get_failed_injections(&self) -> Option<Vec<String>> {
119+
let failed: Vec<String> = self
120+
.instrumentations
121+
.iter()
122+
.filter_map(|instr| {
123+
if instr.has_injected() {
124+
None
125+
} else {
126+
Some(instr.config.function_query.name().to_string())
127+
}
128+
})
129+
.collect();
130+
131+
if failed.is_empty() {
132+
None
133+
} else {
134+
Some(failed)
135+
}
136+
}
137+
138+
pub fn reset_has_injected(&mut self) {
139+
for instr in &mut self.instrumentations {
140+
instr.reset_has_injected();
141+
}
142+
}
143+
115144
/// Transform the given JavaScript code.
116145
/// # Errors
117146
/// Returns an error if the transformation fails.
@@ -125,7 +154,7 @@ impl InstrumentationVisitor {
125154
)));
126155

127156
#[allow(clippy::redundant_closure_for_method_calls)]
128-
Ok(try_with_handler(
157+
let result = try_with_handler(
129158
compiler.cm.clone(),
130159
HandlerOpts {
131160
color: ColorConfig::Never,
@@ -165,10 +194,20 @@ impl InstrumentationVisitor {
165194
..Default::default()
166195
},
167196
)?;
197+
168198
Ok(result.code)
169199
},
170200
)
171-
.map_err(|e| e.to_pretty_error())?)
201+
.map_err(|e| e.to_pretty_error())?;
202+
203+
let failed_injections = self.get_failed_injections();
204+
self.reset_has_injected();
205+
206+
if let Some(failed) = failed_injections {
207+
Err(Box::new(OrchestrionError::InjectionMatchFailure(failed)))
208+
} else {
209+
Ok(result)
210+
}
172211
}
173212
}
174213

src/wasm.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
use crate::*;
2-
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
1+
use crate::{Config, InstrumentationConfig, InstrumentationVisitor, Instrumentor};
2+
use std::path::PathBuf;
3+
use swc::config::IsModule;
4+
use wasm_bindgen::prelude::*;
35

46
#[wasm_bindgen]
57
pub struct InstrumentationMatcher(Instrumentor);
@@ -31,11 +33,11 @@ pub struct Transformer(InstrumentationVisitor);
3133
#[wasm_bindgen]
3234
impl Transformer {
3335
#[wasm_bindgen]
34-
pub fn transform(&mut self, contents: &str, is_module: bool) -> Result<String, JsValue> {
36+
pub fn transform(&mut self, contents: &str, is_module: bool) -> Result<String, JsError> {
3537
let is_module = IsModule::Bool(is_module);
3638
self.0
3739
.transform(contents, is_module)
38-
.map_err(|e| JsValue::from_str(&e.to_string()))
40+
.map_err(|e| JsError::new(&e.to_string()))
3941
}
4042
}
4143

tests/common/mod.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,11 @@ pub fn transpile_and_test(test_file: &str, mjs: bool, config: Config) {
2727
let mut contents = String::new();
2828
file.read_to_string(&mut contents).unwrap();
2929

30-
let result = instrumentations
31-
.transform(&contents, IsModule::Bool(mjs))
32-
.unwrap();
33-
34-
let instrumented_file = test_dir.join(format!("instrumented.{}", extension));
35-
let mut file = std::fs::File::create(&instrumented_file).unwrap();
36-
file.write_all(result.as_bytes()).unwrap();
30+
if let Ok(result) = instrumentations.transform(&contents, IsModule::Bool(mjs)) {
31+
let instrumented_file = test_dir.join(format!("instrumented.{}", extension));
32+
let mut file = std::fs::File::create(&instrumented_file).unwrap();
33+
file.write_all(result.as_bytes()).unwrap();
34+
}
3735

3836
let test_file = format!("test.{}", extension);
3937
Command::new("node")

tests/injection_failure/mod.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
const fetch = async function (url) {
6+
return 42;
7+
}
8+
9+
export { fetch };

tests/injection_failure/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use crate::common::*;
2+
use orchestrion_js::*;
3+
4+
#[test]
5+
fn injection_failure() {
6+
transpile_and_test(
7+
file!(),
8+
true,
9+
Config::new_single(InstrumentationConfig::new(
10+
"some_expr",
11+
test_module_matcher(),
12+
FunctionQuery::function_expression("some", FunctionKind::Async),
13+
)),
14+
);
15+
}

tests/injection_failure/test.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2025 Datadog, Inc.
4+
**/
5+
import { existsSync } from 'node:fs'
6+
import { assert } from '../common/preamble.js';
7+
8+
const instrumented = existsSync('./instrumented.js');
9+
assert.strictEqual(instrumented, false, 'instrumented.js should not exist');

tests/instrumentor_test.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ mod decl_mjs_mismatched_type;
1414
mod expr_cjs;
1515
mod expr_mjs;
1616
mod index_cjs;
17+
mod injection_failure;
1718
mod multiple_class_method_cjs;
1819
mod multiple_load_cjs;
1920
mod object_method_cjs;

tests/wasm/testdata/no-match.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export class Down {
2+
constructor() {
3+
console.log('constructor')
4+
}
5+
6+
fetch() {
7+
console.log('fetch')
8+
}
9+
}

0 commit comments

Comments
 (0)