1
+ use crate :: js_runtime:: JsRuntime ;
1
2
use crate :: pipeline;
2
3
use crate :: pipeline:: DateTime ;
3
4
use crate :: pipeline:: FilterError ;
4
5
use crate :: pipeline:: Message ;
5
- use crate :: LoadError ;
6
+ use anyhow :: Context ;
6
7
use rquickjs:: Ctx ;
7
8
use rquickjs:: FromJs ;
8
9
use rquickjs:: IntoJs ;
9
- use rquickjs:: Object ;
10
10
use rquickjs:: Value ;
11
- use std:: collections:: HashMap ;
12
11
use std:: path:: Path ;
13
12
use std:: path:: PathBuf ;
14
13
use tracing:: debug;
@@ -20,7 +19,7 @@ pub struct JsFilter {
20
19
tick_every_seconds : u64 ,
21
20
}
22
21
23
- #[ derive( Clone , Default ) ]
22
+ #[ derive( Clone , Debug , Default ) ]
24
23
pub struct JsonValue ( serde_json:: Value ) ;
25
24
26
25
impl JsFilter {
@@ -32,6 +31,10 @@ impl JsFilter {
32
31
}
33
32
}
34
33
34
+ pub fn module_name ( & self ) -> String {
35
+ self . path . display ( ) . to_string ( )
36
+ }
37
+
35
38
pub fn with_config ( self , config : Option < serde_json:: Value > ) -> Self {
36
39
if let Some ( config) = config {
37
40
Self {
@@ -68,11 +71,16 @@ impl JsFilter {
68
71
timestamp : & DateTime ,
69
72
message : & Message ,
70
73
) -> Result < Vec < Message > , FilterError > {
71
- debug ! ( target: "MAPPING" , "{}: process({timestamp:?}, {message:?})" , self . path. display( ) ) ;
72
- let input = ( timestamp. clone ( ) , message. clone ( ) , self . config . clone ( ) ) ;
73
- js. call_function ( self , "process" , input)
74
+ debug ! ( target: "MAPPING" , "{}: process({timestamp:?}, {message:?})" , self . module_name( ) ) ;
75
+ let input = vec ! [
76
+ timestamp. clone( ) . into( ) ,
77
+ message. clone( ) . into( ) ,
78
+ self . config. clone( ) ,
79
+ ] ;
80
+ js. call_function ( & self . path , "process" , input)
74
81
. await
75
- . map_err ( pipeline:: error_from_js)
82
+ . map_err ( pipeline:: error_from_js) ?
83
+ . try_into ( )
76
84
}
77
85
78
86
/// Update the filter config using a metadata message
@@ -87,10 +95,10 @@ impl JsFilter {
87
95
js : & JsRuntime ,
88
96
message : & Message ,
89
97
) -> Result < ( ) , FilterError > {
90
- debug ! ( target: "MAPPING" , "{}: update_config({message:?})" , self . path . display ( ) ) ;
91
- let input = ( message. clone ( ) , self . config . clone ( ) ) ;
98
+ debug ! ( target: "MAPPING" , "{}: update_config({message:?})" , self . module_name ( ) ) ;
99
+ let input = vec ! [ message. clone( ) . into ( ) , self . config. clone( ) ] ;
92
100
let config = js
93
- . call_function ( self , "update_config" , input)
101
+ . call_function ( & self . path , "update_config" , input)
94
102
. await
95
103
. map_err ( pipeline:: error_from_js) ?;
96
104
self . config = config;
@@ -112,143 +120,78 @@ impl JsFilter {
112
120
if !timestamp. tick_now ( self . tick_every_seconds ) {
113
121
return Ok ( vec ! [ ] ) ;
114
122
}
115
- debug ! ( target: "MAPPING" , "{}: tick({timestamp:?})" , self . path . display ( ) ) ;
116
- let input = ( timestamp. clone ( ) , self . config . clone ( ) ) ;
117
- js. call_function ( self , "tick" , input)
123
+ debug ! ( target: "MAPPING" , "{}: tick({timestamp:?})" , self . module_name ( ) ) ;
124
+ let input = vec ! [ timestamp. clone( ) . into ( ) , self . config. clone( ) ] ;
125
+ js. call_function ( & self . path , "tick" , input)
118
126
. await
119
- . map_err ( pipeline:: error_from_js)
127
+ . map_err ( pipeline:: error_from_js) ?
128
+ . try_into ( )
120
129
}
121
130
}
122
131
123
- pub struct JsRuntime {
124
- context : rquickjs:: AsyncContext ,
125
- modules : HashMap < PathBuf , Vec < u8 > > ,
132
+ impl From < Message > for JsonValue {
133
+ fn from ( value : Message ) -> Self {
134
+ JsonValue ( value. json ( ) )
135
+ }
126
136
}
127
137
128
- impl JsRuntime {
129
- pub async fn try_new ( ) -> Result < Self , LoadError > {
130
- let runtime = rquickjs:: AsyncRuntime :: new ( ) ?;
131
- let context = rquickjs:: AsyncContext :: full ( & runtime) . await ?;
132
- let modules = HashMap :: new ( ) ;
133
- Ok ( JsRuntime { context, modules } )
138
+ impl From < DateTime > for JsonValue {
139
+ fn from ( value : DateTime ) -> Self {
140
+ JsonValue ( value. json ( ) )
134
141
}
142
+ }
135
143
136
- pub async fn load_file ( & mut self , path : impl AsRef < Path > ) -> Result < JsFilter , LoadError > {
137
- let path = path. as_ref ( ) ;
138
- let source = tokio:: fs:: read_to_string ( path) . await ?;
139
- self . load_js ( path, source)
140
- }
144
+ impl TryFrom < serde_json:: Value > for Message {
145
+ type Error = FilterError ;
141
146
142
- pub fn load_js (
143
- & mut self ,
144
- path : impl AsRef < Path > ,
145
- source : impl Into < Vec < u8 > > ,
146
- ) -> Result < JsFilter , LoadError > {
147
- let path = path. as_ref ( ) . to_path_buf ( ) ;
148
- self . modules . insert ( path. clone ( ) , source. into ( ) ) ;
149
- Ok ( JsFilter :: new ( path) )
147
+ fn try_from ( value : serde_json:: Value ) -> Result < Self , Self :: Error > {
148
+ let message = serde_json:: from_value ( value)
149
+ . with_context ( || "Couldn't extract message payload and topic" ) ?;
150
+ Ok ( message)
150
151
}
152
+ }
151
153
152
- pub fn loaded_module ( & self , path : PathBuf ) -> Result < JsFilter , LoadError > {
153
- match self . modules . get ( & path) {
154
- None => Err ( LoadError :: ScriptNotLoaded { path } ) ,
155
- Some ( _) => Ok ( JsFilter :: new ( path) ) ,
156
- }
157
- }
154
+ impl TryFrom < JsonValue > for Message {
155
+ type Error = FilterError ;
158
156
159
- pub async fn call_function < Args , Ret > (
160
- & self ,
161
- module : & JsFilter ,
162
- function : & str ,
163
- args : Args ,
164
- ) -> Result < Ret , LoadError >
165
- where
166
- for < ' a > Args : rquickjs:: function:: IntoArgs < ' a > + Send + ' a ,
167
- for < ' a > Ret : FromJs < ' a > + Send + ' a ,
168
- {
169
- let Some ( source) = self . modules . get ( & module. path ) else {
170
- return Err ( LoadError :: ScriptNotLoaded {
171
- path : module. path . clone ( ) ,
172
- } ) ;
173
- } ;
174
-
175
- let name = module. path . display ( ) . to_string ( ) ;
176
-
177
- rquickjs:: async_with!( self . context => |ctx| {
178
- debug!( target: "MAPPING" , "compile({name})" ) ;
179
- let m = rquickjs:: Module :: declare( ctx. clone( ) , name. clone( ) , source. clone( ) ) ?;
180
- let ( m, p) = m. eval( ) ?;
181
- let ( ) = p. finish( ) ?;
182
-
183
- debug!( target: "MAPPING" , "link({name})" ) ;
184
- let f: rquickjs:: Value = m. get( function) ?;
185
- let f = rquickjs:: Function :: from_value( f) ?;
186
-
187
- debug!( target: "MAPPING" , "execute({name})" ) ;
188
- let r = f. call( args) ;
189
- if r. is_err( ) {
190
- if let Some ( ex) = ctx. catch( ) . as_exception( ) {
191
- let err = anyhow:: anyhow!( "{ex}" ) ;
192
- Err ( err. context( "JS raised exception" ) . into( ) )
193
- } else {
194
- let err = r. err( ) . unwrap( ) ;
195
- debug!( target: "MAPPING" , "execute({name}) => {err:?}" ) ;
196
- Err ( err. into( ) )
197
- }
198
- } else {
199
- Ok ( r. unwrap( ) )
200
- }
201
- } )
202
- . await
157
+ fn try_from ( value : JsonValue ) -> Result < Self , Self :: Error > {
158
+ Message :: try_from ( value. 0 )
203
159
}
204
160
}
205
161
206
- impl < ' js > FromJs < ' js > for Message {
207
- fn from_js ( _ctx : & Ctx < ' js > , value : Value < ' js > ) -> rquickjs:: Result < Self > {
208
- debug ! ( target: "MAPPING" , "from_js(...)" ) ;
209
- match value. as_object ( ) {
210
- None => Ok ( Message {
211
- topic : "" . to_string ( ) ,
212
- payload : "" . to_string ( ) ,
213
- } ) ,
214
- Some ( object) => {
215
- let topic = object. get ( "topic" ) ;
216
- let payload = object. get ( "payload" ) ;
217
- debug ! ( target: "MAPPING" , "from_js(...) -> topic = {:?}, payload = {:?}" , topic, payload) ;
218
- Ok ( Message {
219
- topic : topic?,
220
- payload : payload?,
221
- } )
162
+ impl TryFrom < JsonValue > for Vec < Message > {
163
+ type Error = FilterError ;
164
+
165
+ fn try_from ( value : JsonValue ) -> Result < Self , Self :: Error > {
166
+ match value. 0 {
167
+ serde_json:: Value :: Array ( array) => array. into_iter ( ) . map ( Message :: try_from) . collect ( ) ,
168
+ serde_json:: Value :: Object ( map) => {
169
+ Message :: try_from ( serde_json:: Value :: Object ( map) ) . map ( |message| vec ! [ message] )
222
170
}
171
+ _ => Err ( anyhow:: anyhow!( "Filters are expected to return an array of messages" ) . into ( ) ) ,
223
172
}
224
173
}
225
174
}
226
175
227
- impl < ' js > IntoJs < ' js > for Message {
176
+ struct JsonValueRef < ' a > ( & ' a serde_json:: Value ) ;
177
+
178
+ impl < ' js > IntoJs < ' js > for JsonValue {
228
179
fn into_js ( self , ctx : & Ctx < ' js > ) -> rquickjs:: Result < Value < ' js > > {
229
- debug ! ( target: "MAPPING" , "into_js({self:?})" ) ;
230
- let msg = Object :: new ( ctx. clone ( ) ) ?;
231
- msg. set ( "topic" , self . topic ) ?;
232
- msg. set ( "payload" , self . payload ) ?;
233
- Ok ( Value :: from_object ( msg) )
180
+ JsonValueRef ( & self . 0 ) . into_js ( ctx)
234
181
}
235
182
}
236
183
237
- impl < ' js > IntoJs < ' js > for DateTime {
184
+ impl < ' js > IntoJs < ' js > for & JsonValue {
238
185
fn into_js ( self , ctx : & Ctx < ' js > ) -> rquickjs:: Result < Value < ' js > > {
239
- debug ! ( target: "MAPPING" , "into_js({self:?})" ) ;
240
- let msg = Object :: new ( ctx. clone ( ) ) ?;
241
- msg. set ( "seconds" , self . seconds ) ?;
242
- msg. set ( "nanoseconds" , self . nanoseconds ) ?;
243
- Ok ( Value :: from_object ( msg) )
186
+ JsonValueRef ( & self . 0 ) . into_js ( ctx)
244
187
}
245
188
}
246
189
247
- impl < ' js > IntoJs < ' js > for JsonValue {
190
+ impl < ' a , ' js > IntoJs < ' js > for JsonValueRef < ' a > {
248
191
fn into_js ( self , ctx : & Ctx < ' js > ) -> rquickjs:: Result < Value < ' js > > {
249
192
match self . 0 {
250
193
serde_json:: Value :: Null => Ok ( Value :: new_null ( ctx. clone ( ) ) ) ,
251
- serde_json:: Value :: Bool ( value) => Ok ( Value :: new_bool ( ctx. clone ( ) , value) ) ,
194
+ serde_json:: Value :: Bool ( value) => Ok ( Value :: new_bool ( ctx. clone ( ) , * value) ) ,
252
195
serde_json:: Value :: Number ( value) => {
253
196
if let Some ( n) = value. as_i64 ( ) {
254
197
if let Ok ( n) = i32:: try_from ( n) {
@@ -262,20 +205,20 @@ impl<'js> IntoJs<'js> for JsonValue {
262
205
Ok ( nan. into_value ( ) )
263
206
}
264
207
serde_json:: Value :: String ( value) => {
265
- let string = rquickjs:: String :: from_str ( ctx. clone ( ) , & value) ?;
208
+ let string = rquickjs:: String :: from_str ( ctx. clone ( ) , value) ?;
266
209
Ok ( string. into_value ( ) )
267
210
}
268
211
serde_json:: Value :: Array ( values) => {
269
212
let array = rquickjs:: Array :: new ( ctx. clone ( ) ) ?;
270
- for ( i, value) in values. into_iter ( ) . enumerate ( ) {
271
- array. set ( i, JsonValue ( value) ) ?;
213
+ for ( i, value) in values. iter ( ) . enumerate ( ) {
214
+ array. set ( i, JsonValueRef ( value) ) ?;
272
215
}
273
216
Ok ( array. into_value ( ) )
274
217
}
275
218
serde_json:: Value :: Object ( values) => {
276
219
let object = rquickjs:: Object :: new ( ctx. clone ( ) ) ?;
277
220
for ( key, value) in values. into_iter ( ) {
278
- object. set ( key, JsonValue ( value) ) ?;
221
+ object. set ( key, JsonValueRef ( value) ) ?;
279
222
}
280
223
Ok ( object. into_value ( ) )
281
224
}
@@ -327,7 +270,8 @@ mod tests {
327
270
async fn identity_filter ( ) {
328
271
let script = "export function process(t,msg) { return [msg]; };" ;
329
272
let mut runtime = JsRuntime :: try_new ( ) . await . unwrap ( ) ;
330
- let filter = runtime. load_js ( "id.js" , script) . unwrap ( ) ;
273
+ runtime. load_js ( "id.js" , script) . await . unwrap ( ) ;
274
+ let filter = JsFilter :: new ( "id.js" . into ( ) ) ;
331
275
332
276
let input = Message :: new ( "te/main/device///m/" , "hello world" ) ;
333
277
let output = input. clone ( ) ;
@@ -344,7 +288,8 @@ mod tests {
344
288
async fn error_filter ( ) {
345
289
let script = r#"export function process(t,msg) { throw new Error("Cannot process that message"); };"# ;
346
290
let mut runtime = JsRuntime :: try_new ( ) . await . unwrap ( ) ;
347
- let filter = runtime. load_js ( "err.js" , script) . unwrap ( ) ;
291
+ runtime. load_js ( "err.js" , script) . await . unwrap ( ) ;
292
+ let filter = JsFilter :: new ( "err.js" . into ( ) ) ;
348
293
349
294
let input = Message :: new ( "te/main/device///m/" , "hello world" ) ;
350
295
let error = filter
@@ -379,7 +324,8 @@ export function process (timestamp, message, config) {
379
324
}
380
325
"# ;
381
326
let mut runtime = JsRuntime :: try_new ( ) . await . unwrap ( ) ;
382
- let filter = runtime. load_js ( "collectd.js" , script) . unwrap ( ) ;
327
+ runtime. load_js ( "collectd.js" , script) . await . unwrap ( ) ;
328
+ let filter = JsFilter :: new ( "collectd.js" . into ( ) ) ;
383
329
384
330
let input = Message :: new (
385
331
"collectd/h/memory/percent-used" ,
0 commit comments