app_bridge 2.0.1 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.editorconfig +18 -0
- data/Cargo.lock +919 -210
- data/Rakefile +6 -3
- data/ext/app_bridge/Cargo.toml +8 -3
- data/ext/app_bridge/src/app_state.rs +21 -6
- data/ext/app_bridge/src/component.rs +34 -9
- data/ext/app_bridge/src/lib.rs +30 -10
- data/ext/app_bridge/src/request_builder.rs +39 -1
- data/ext/app_bridge/src/wrappers/action_context.rs +79 -0
- data/ext/app_bridge/src/wrappers/action_response.rs +32 -0
- data/ext/app_bridge/src/wrappers/app.rs +257 -14
- data/ext/app_bridge/src/wrappers/{account.rs → connection.rs} +12 -12
- data/ext/app_bridge/src/wrappers/mod.rs +3 -1
- data/ext/app_bridge/src/wrappers/trigger_context.rs +35 -14
- data/ext/app_bridge/wit/world.wit +88 -7
- data/lib/app_bridge/app.rb +31 -3
- data/lib/app_bridge/version.rb +4 -1
- data/lib/app_bridge.rb +8 -6
- data/sig/app_bridge.rbs +26 -4
- data/tasks/rust_test.rake +11 -0
- metadata +7 -3
@@ -1,13 +1,19 @@
|
|
1
1
|
use magnus::{Error, TryConvert, Value};
|
2
2
|
use std::cell::RefCell;
|
3
|
+
use std::collections::HashMap;
|
3
4
|
use wasmtime::Store;
|
4
5
|
|
5
6
|
use crate::app_state::AppState;
|
6
|
-
use crate::component::standout::app::types::{
|
7
|
+
use crate::component::standout::app::types::{
|
8
|
+
AppError, ErrorCode, TriggerContext, TriggerResponse, ActionContext, ActionResponse
|
9
|
+
};
|
7
10
|
use crate::component::{app, build_engine, build_linker, build_store, Bridge};
|
8
|
-
use super::
|
9
|
-
|
10
|
-
|
11
|
+
use super::{
|
12
|
+
trigger_context::RTriggerContext,
|
13
|
+
trigger_response::RTriggerResponse,
|
14
|
+
action_context::RActionContext,
|
15
|
+
action_response::RActionResponse,
|
16
|
+
};
|
11
17
|
|
12
18
|
#[derive(Default)]
|
13
19
|
pub struct RApp {
|
@@ -21,17 +27,40 @@ pub struct RApp {
|
|
21
27
|
pub struct MutRApp(RefCell<RApp>);
|
22
28
|
|
23
29
|
impl MutRApp {
|
24
|
-
pub fn initialize(&self, component_path: String) {
|
30
|
+
pub fn initialize(&self, component_path: String, env_vars: HashMap<String, String>) -> Result<(), Error> {
|
25
31
|
let mut this = self.0.borrow_mut();
|
26
32
|
let engine = build_engine();
|
27
|
-
let linker = build_linker(&engine).
|
28
|
-
|
33
|
+
let linker = build_linker(&engine).map_err(|e| {
|
34
|
+
Error::new(
|
35
|
+
magnus::exception::runtime_error(),
|
36
|
+
format!("Failed to build linker: {}", e)
|
37
|
+
)
|
38
|
+
})?;
|
39
|
+
let mut store = if env_vars.is_empty() {
|
40
|
+
build_store(&engine, None)
|
41
|
+
} else {
|
42
|
+
build_store(&engine, Some(env_vars))
|
43
|
+
};
|
29
44
|
|
30
|
-
let app = app(component_path.clone(), engine, &mut store, linker).
|
45
|
+
let app = app(component_path.clone(), engine, &mut store, linker).map_err(|e| {
|
46
|
+
if e.to_string().contains("Incompatible WASM file version") {
|
47
|
+
Error::new(
|
48
|
+
magnus::exception::runtime_error(),
|
49
|
+
e.to_string()
|
50
|
+
)
|
51
|
+
} else {
|
52
|
+
Error::new(
|
53
|
+
magnus::exception::runtime_error(),
|
54
|
+
format!("Failed to initialize app: {}", e)
|
55
|
+
)
|
56
|
+
}
|
57
|
+
})?;
|
31
58
|
|
32
59
|
this.component_path = component_path.to_string();
|
33
60
|
*this.instance.borrow_mut() = Some(app);
|
34
61
|
*this.store.borrow_mut() = Some(store);
|
62
|
+
|
63
|
+
Ok(())
|
35
64
|
}
|
36
65
|
|
37
66
|
pub fn trigger_ids(&self) -> Result<Vec<String>, Error> {
|
@@ -52,17 +81,85 @@ impl MutRApp {
|
|
52
81
|
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
53
82
|
Err(wit_err.clone().into())
|
54
83
|
} else {
|
55
|
-
Err(
|
56
|
-
|
57
|
-
|
58
|
-
|
84
|
+
Err(Error::new(
|
85
|
+
magnus::exception::runtime_error(),
|
86
|
+
format!("Unexpected error: {:?}", err)
|
87
|
+
))
|
88
|
+
}
|
89
|
+
},
|
90
|
+
}
|
91
|
+
} else {
|
92
|
+
Err(AppError {
|
93
|
+
code: ErrorCode::InternalError,
|
94
|
+
message: "App instance couldn't be initialized".to_string(),
|
95
|
+
}.into())
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
pub fn trigger_input_schema(&self, context: RTriggerContext) -> Result<String, Error> {
|
100
|
+
let binding = self.0.borrow();
|
101
|
+
|
102
|
+
let mut instance = binding.instance.borrow_mut();
|
103
|
+
let mut store = binding.store.borrow_mut();
|
104
|
+
|
105
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
106
|
+
let context_ctx = context.into();
|
107
|
+
match instance.standout_app_triggers().call_input_schema(store, &context_ctx) {
|
108
|
+
Ok(result) => {
|
109
|
+
match result {
|
110
|
+
Ok(schema) => Ok(schema),
|
111
|
+
Err(err) => Err(err.into())
|
112
|
+
}
|
113
|
+
},
|
114
|
+
Err(err) => {
|
115
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
116
|
+
Err(wit_err.clone().into())
|
117
|
+
} else {
|
118
|
+
Err(Error::new(
|
119
|
+
magnus::exception::runtime_error(),
|
120
|
+
format!("Unexpected error: {:?}", err)
|
121
|
+
))
|
122
|
+
}
|
123
|
+
},
|
124
|
+
}
|
125
|
+
} else {
|
126
|
+
Err(AppError {
|
127
|
+
code: ErrorCode::InternalError,
|
128
|
+
message: "App instance couldn't be initialized".to_string(),
|
129
|
+
}.into())
|
130
|
+
}
|
131
|
+
}
|
132
|
+
|
133
|
+
pub fn trigger_output_schema(&self, context: RTriggerContext) -> Result<String, Error> {
|
134
|
+
let binding = self.0.borrow();
|
135
|
+
|
136
|
+
let mut instance = binding.instance.borrow_mut();
|
137
|
+
let mut store = binding.store.borrow_mut();
|
138
|
+
|
139
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
140
|
+
let context_ctx = context.into();
|
141
|
+
match instance.standout_app_triggers().call_output_schema(store, &context_ctx) {
|
142
|
+
Ok(result) => {
|
143
|
+
match result {
|
144
|
+
Ok(schema) => Ok(schema),
|
145
|
+
Err(err) => Err(err.into())
|
146
|
+
}
|
147
|
+
},
|
148
|
+
Err(err) => {
|
149
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
150
|
+
Err(wit_err.clone().into())
|
151
|
+
} else {
|
152
|
+
Err(Error::new(
|
153
|
+
magnus::exception::runtime_error(),
|
154
|
+
format!("Unexpected error: {:?}", err)
|
155
|
+
))
|
59
156
|
}
|
60
157
|
},
|
61
158
|
}
|
62
159
|
} else {
|
63
160
|
Err(AppError {
|
64
161
|
code: ErrorCode::InternalError,
|
65
|
-
message: "App instance
|
162
|
+
message: "App instance couldn't be initialized".to_string(),
|
66
163
|
}.into())
|
67
164
|
}
|
68
165
|
}
|
@@ -107,7 +204,153 @@ impl MutRApp {
|
|
107
204
|
} else {
|
108
205
|
Err(AppError {
|
109
206
|
code: ErrorCode::InternalError,
|
110
|
-
message: "App instance
|
207
|
+
message: "App instance couldn't be initialized".to_string(),
|
208
|
+
})
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
pub fn action_ids(&self) -> Result<Vec<String>, Error> {
|
213
|
+
let binding = self.0.borrow();
|
214
|
+
|
215
|
+
let mut instance = binding.instance.borrow_mut();
|
216
|
+
let mut store = binding.store.borrow_mut();
|
217
|
+
|
218
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
219
|
+
match instance.standout_app_actions().call_action_ids(store) {
|
220
|
+
Ok(result) => {
|
221
|
+
match result {
|
222
|
+
Ok(ids) => Ok(ids),
|
223
|
+
Err(err) => Err(err.into())
|
224
|
+
}
|
225
|
+
},
|
226
|
+
Err(err) => {
|
227
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
228
|
+
Err(wit_err.clone().into())
|
229
|
+
} else {
|
230
|
+
Err(Error::new(
|
231
|
+
magnus::exception::runtime_error(),
|
232
|
+
format!("Unexpected error: {:?}", err)
|
233
|
+
))
|
234
|
+
}
|
235
|
+
},
|
236
|
+
}
|
237
|
+
} else {
|
238
|
+
Err(AppError {
|
239
|
+
code: ErrorCode::InternalError,
|
240
|
+
message: "App instance couldn't be initialized".to_string(),
|
241
|
+
}.into())
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
pub fn action_input_schema(&self, context: RActionContext) -> Result<String, Error> {
|
246
|
+
let binding = self.0.borrow();
|
247
|
+
|
248
|
+
let mut instance = binding.instance.borrow_mut();
|
249
|
+
let mut store = binding.store.borrow_mut();
|
250
|
+
|
251
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
252
|
+
let context_ctx = context.into();
|
253
|
+
match instance.standout_app_actions().call_input_schema(store, &context_ctx) {
|
254
|
+
Ok(result) => {
|
255
|
+
match result {
|
256
|
+
Ok(schema) => Ok(schema),
|
257
|
+
Err(err) => Err(err.into())
|
258
|
+
}
|
259
|
+
},
|
260
|
+
Err(err) => {
|
261
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
262
|
+
Err(wit_err.clone().into())
|
263
|
+
} else {
|
264
|
+
Err(Error::new(
|
265
|
+
magnus::exception::runtime_error(),
|
266
|
+
format!("Unexpected error: {:?}", err)
|
267
|
+
))
|
268
|
+
}
|
269
|
+
},
|
270
|
+
}
|
271
|
+
} else {
|
272
|
+
Err(AppError {
|
273
|
+
code: ErrorCode::InternalError,
|
274
|
+
message: "App instance couldn't be initialized".to_string(),
|
275
|
+
}.into())
|
276
|
+
}
|
277
|
+
}
|
278
|
+
|
279
|
+
pub fn action_output_schema(&self, context: RActionContext) -> Result<String, Error> {
|
280
|
+
let binding = self.0.borrow();
|
281
|
+
|
282
|
+
let mut instance = binding.instance.borrow_mut();
|
283
|
+
let mut store = binding.store.borrow_mut();
|
284
|
+
|
285
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
286
|
+
let context_ctx = context.into();
|
287
|
+
match instance.standout_app_actions().call_output_schema(store, &context_ctx) {
|
288
|
+
Ok(result) => {
|
289
|
+
match result {
|
290
|
+
Ok(schema) => Ok(schema),
|
291
|
+
Err(err) => Err(err.into())
|
292
|
+
}
|
293
|
+
},
|
294
|
+
Err(err) => {
|
295
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
296
|
+
Err(wit_err.clone().into())
|
297
|
+
} else {
|
298
|
+
Err(Error::new(
|
299
|
+
magnus::exception::runtime_error(),
|
300
|
+
format!("Unexpected error: {:?}", err)
|
301
|
+
))
|
302
|
+
}
|
303
|
+
},
|
304
|
+
}
|
305
|
+
} else {
|
306
|
+
Err(AppError {
|
307
|
+
code: ErrorCode::InternalError,
|
308
|
+
message: "App instance couldn't be initialized".to_string(),
|
309
|
+
}.into())
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
pub fn rb_execute_action(&self, context: Value) -> Result<RActionResponse, magnus::Error> {
|
314
|
+
let context: RActionContext = TryConvert::try_convert(context).unwrap();
|
315
|
+
let response = self.execute_action(context.into());
|
316
|
+
|
317
|
+
match response {
|
318
|
+
Ok(response) => Ok(response.into()),
|
319
|
+
Err(err) => Err(err.into())
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
fn execute_action(&self, context: ActionContext) -> Result<ActionResponse, AppError> {
|
324
|
+
let binding = self.0.borrow();
|
325
|
+
|
326
|
+
let mut instance = binding.instance.borrow_mut();
|
327
|
+
let mut store = binding.store.borrow_mut();
|
328
|
+
|
329
|
+
if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
|
330
|
+
match instance
|
331
|
+
.standout_app_actions()
|
332
|
+
.call_execute(store, &context) {
|
333
|
+
Ok(response) => {
|
334
|
+
match response {
|
335
|
+
Ok(res) => Ok(res),
|
336
|
+
Err(err) => Err(err),
|
337
|
+
}
|
338
|
+
},
|
339
|
+
Err(err) => {
|
340
|
+
if let Some(wit_err) = err.downcast_ref::<AppError>() {
|
341
|
+
Err(AppError::from(wit_err.clone()))
|
342
|
+
} else {
|
343
|
+
Err(AppError {
|
344
|
+
code: ErrorCode::InternalError,
|
345
|
+
message: format!("Unexpected error: {:?}", err),
|
346
|
+
})
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
} else {
|
351
|
+
Err(AppError {
|
352
|
+
code: ErrorCode::InternalError,
|
353
|
+
message: "App instance couldn't be initialized".to_string(),
|
111
354
|
})
|
112
355
|
}
|
113
356
|
}
|
@@ -1,14 +1,14 @@
|
|
1
1
|
use magnus::{prelude::*, Error, TryConvert, Value};
|
2
|
-
use crate::component::standout::app::types::
|
2
|
+
use crate::component::standout::app::types::Connection;
|
3
3
|
|
4
|
-
#[magnus::wrap(class = "AppBridge::
|
5
|
-
pub struct
|
6
|
-
pub inner:
|
4
|
+
#[magnus::wrap(class = "AppBridge::Connection")]
|
5
|
+
pub struct RConnection {
|
6
|
+
pub inner: Connection,
|
7
7
|
}
|
8
8
|
|
9
|
-
impl
|
9
|
+
impl RConnection {
|
10
10
|
pub fn new(id: String, name: String, serialized_data: String) -> Self {
|
11
|
-
let inner =
|
11
|
+
let inner = Connection {
|
12
12
|
id: id,
|
13
13
|
name: name,
|
14
14
|
serialized_data: serialized_data,
|
@@ -29,7 +29,7 @@ impl RAccount {
|
|
29
29
|
}
|
30
30
|
}
|
31
31
|
|
32
|
-
impl Clone for
|
32
|
+
impl Clone for RConnection {
|
33
33
|
fn clone(&self) -> Self {
|
34
34
|
Self {
|
35
35
|
inner: self.inner.clone(),
|
@@ -37,13 +37,13 @@ impl Clone for RAccount {
|
|
37
37
|
}
|
38
38
|
}
|
39
39
|
|
40
|
-
impl TryConvert for
|
40
|
+
impl TryConvert for RConnection {
|
41
41
|
fn try_convert(val: Value) -> Result<Self, Error> {
|
42
42
|
let id: String = val.funcall("id", ())?;
|
43
43
|
let name: String = val.funcall("name", ())?;
|
44
44
|
let serialized_data: String = val.funcall("serialized_data", ())?;
|
45
45
|
|
46
|
-
let inner =
|
46
|
+
let inner = Connection {
|
47
47
|
id,
|
48
48
|
name,
|
49
49
|
serialized_data,
|
@@ -53,8 +53,8 @@ impl TryConvert for RAccount {
|
|
53
53
|
}
|
54
54
|
}
|
55
55
|
|
56
|
-
impl From<
|
57
|
-
fn from(
|
58
|
-
|
56
|
+
impl From<RConnection> for Connection {
|
57
|
+
fn from(rconnection: RConnection) -> Self {
|
58
|
+
rconnection.inner
|
59
59
|
}
|
60
60
|
}
|
@@ -1,58 +1,79 @@
|
|
1
1
|
use magnus::{prelude::*, Error, TryConvert, Value};
|
2
2
|
use crate::component::standout::app::types::TriggerContext;
|
3
|
-
use super::
|
3
|
+
use super::connection::RConnection;
|
4
4
|
|
5
5
|
#[magnus::wrap(class = "AppBridge::TriggerContext")]
|
6
6
|
pub struct RTriggerContext {
|
7
7
|
inner: TriggerContext,
|
8
|
-
|
8
|
+
wrapped_connection: Option<RConnection>,
|
9
9
|
}
|
10
10
|
|
11
11
|
impl RTriggerContext {
|
12
|
-
pub fn new(trigger_id: String,
|
13
|
-
|
12
|
+
pub fn new(trigger_id: String, connection: Value, store: String, serialized_input: String) -> Result<Self, Error> {
|
13
|
+
if connection.is_nil() {
|
14
|
+
return Err(Error::new(magnus::exception::runtime_error(), "Connection is required"));
|
15
|
+
}
|
16
|
+
|
17
|
+
let wrapped_connection: RConnection = match TryConvert::try_convert(connection) {
|
18
|
+
Ok(conn) => conn,
|
19
|
+
Err(_) => return Err(Error::new(magnus::exception::runtime_error(), "Connection is required")),
|
20
|
+
};
|
14
21
|
|
15
22
|
let inner = TriggerContext {
|
16
23
|
trigger_id: trigger_id,
|
17
|
-
|
24
|
+
connection: wrapped_connection.clone().into(),
|
18
25
|
store: store,
|
26
|
+
serialized_input: serialized_input,
|
19
27
|
};
|
20
|
-
Self {
|
28
|
+
Ok(Self {
|
21
29
|
inner,
|
22
|
-
|
23
|
-
}
|
30
|
+
wrapped_connection: Some(wrapped_connection),
|
31
|
+
})
|
24
32
|
}
|
25
33
|
|
26
34
|
pub fn trigger_id(&self) -> String {
|
27
35
|
self.inner.trigger_id.clone()
|
28
36
|
}
|
29
37
|
|
30
|
-
pub fn
|
31
|
-
self.
|
38
|
+
pub fn connection(&self) -> RConnection {
|
39
|
+
self.wrapped_connection.clone().unwrap()
|
32
40
|
}
|
33
41
|
|
34
42
|
pub fn store(&self) -> String {
|
35
43
|
self.inner.store.clone()
|
36
44
|
}
|
45
|
+
|
46
|
+
pub fn serialized_input(&self) -> String {
|
47
|
+
self.inner.serialized_input.clone()
|
48
|
+
}
|
37
49
|
}
|
38
50
|
|
39
51
|
impl TryConvert for RTriggerContext {
|
40
52
|
fn try_convert(val: Value) -> Result<Self, Error> {
|
41
|
-
let
|
53
|
+
let connection_val: Value = val.funcall("connection", ())?;
|
42
54
|
let store: String = val.funcall("store", ())?;
|
43
55
|
let trigger_id: String = val.funcall("trigger_id", ())?;
|
56
|
+
let serialized_input: String = val.funcall("serialized_input", ())?;
|
44
57
|
|
45
|
-
|
58
|
+
if connection_val.is_nil() {
|
59
|
+
return Err(Error::new(magnus::exception::runtime_error(), "Connection is required"));
|
60
|
+
}
|
61
|
+
|
62
|
+
let wrapped_connection: RConnection = match TryConvert::try_convert(connection_val) {
|
63
|
+
Ok(conn) => conn,
|
64
|
+
Err(_) => return Err(Error::new(magnus::exception::runtime_error(), "Connection is required")),
|
65
|
+
};
|
46
66
|
|
47
67
|
let inner = TriggerContext {
|
48
68
|
trigger_id: trigger_id,
|
49
|
-
|
69
|
+
connection: wrapped_connection.clone().inner,
|
50
70
|
store: store,
|
71
|
+
serialized_input: serialized_input,
|
51
72
|
};
|
52
73
|
|
53
74
|
Ok(Self {
|
54
75
|
inner,
|
55
|
-
|
76
|
+
wrapped_connection: Some(wrapped_connection),
|
56
77
|
})
|
57
78
|
}
|
58
79
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
package standout:app@2.
|
1
|
+
package standout:app@2.1.1;
|
2
2
|
|
3
3
|
interface types {
|
4
4
|
// The trigger-store is a string that is used to store data between trigger
|
@@ -10,10 +10,10 @@ interface types {
|
|
10
10
|
// need to add more data to the store.
|
11
11
|
type trigger-store = string;
|
12
12
|
|
13
|
-
record
|
13
|
+
record connection {
|
14
14
|
id: string,
|
15
15
|
name: string,
|
16
|
-
// The
|
16
|
+
// The connection data is a JSON object serialized into a string. The JSON root
|
17
17
|
// will always be an object.
|
18
18
|
serialized-data: string,
|
19
19
|
}
|
@@ -23,12 +23,31 @@ interface types {
|
|
23
23
|
// invoked.
|
24
24
|
trigger-id: string,
|
25
25
|
|
26
|
-
// The
|
27
|
-
|
26
|
+
// The connection that the trigger is invoked for.
|
27
|
+
// Connection is required for all trigger operations.
|
28
|
+
connection: connection,
|
28
29
|
|
29
30
|
// The store will contain the data that was stored in the trigger store the
|
30
31
|
// last time the trigger was invoked.
|
31
32
|
store: trigger-store,
|
33
|
+
|
34
|
+
// The input data for the trigger, serialized as a JSON object string.
|
35
|
+
// This contains the input data from the trigger configuration form.
|
36
|
+
serialized-input: string,
|
37
|
+
}
|
38
|
+
|
39
|
+
record action-context {
|
40
|
+
// Action ID is a unique identifier for the action that is requested to be
|
41
|
+
// invoked.
|
42
|
+
action-id: string,
|
43
|
+
|
44
|
+
// The connection that the action is invoked for.
|
45
|
+
// Connection is required for all action operations.
|
46
|
+
connection: connection,
|
47
|
+
|
48
|
+
// The input data for the action, serialized as a JSON object string.
|
49
|
+
// This contains the data passed from the previous step in the workflow.
|
50
|
+
serialized-input: string,
|
32
51
|
}
|
33
52
|
|
34
53
|
record trigger-response {
|
@@ -41,10 +60,17 @@ interface types {
|
|
41
60
|
store: trigger-store,
|
42
61
|
}
|
43
62
|
|
63
|
+
record action-response {
|
64
|
+
// The output data from the action, serialized as a JSON object string.
|
65
|
+
// This contains the data that will be passed to the next step in the workflow.
|
66
|
+
// The data must be a valid JSON object (not an array or primitive).
|
67
|
+
serialized-output: string
|
68
|
+
}
|
69
|
+
|
44
70
|
record trigger-event {
|
45
71
|
// The ID of the trigger event
|
46
72
|
//
|
47
|
-
// If the
|
73
|
+
// If the connection used for the given instance of the trigger is the same,
|
48
74
|
// as seen before. Then the event will be ignored.
|
49
75
|
//
|
50
76
|
// A scheduler could therefore use an timestamp as the ID, to ensure that
|
@@ -81,7 +107,7 @@ interface types {
|
|
81
107
|
/// Authentication failed. Typically due to an invalid or expired API key or token.
|
82
108
|
unauthenticated,
|
83
109
|
|
84
|
-
/// Authorization failed. The
|
110
|
+
/// Authorization failed. The connection is valid but does not have the necessary permissions.
|
85
111
|
forbidden,
|
86
112
|
|
87
113
|
/// The trigger is misconfigured. For example, a required setting is missing or invalid.
|
@@ -116,6 +142,18 @@ interface triggers {
|
|
116
142
|
|
117
143
|
trigger-ids: func() -> result<list<string>, app-error>;
|
118
144
|
|
145
|
+
// Get the input schema for a specific trigger
|
146
|
+
// Returns a JSON Schema Draft 2020-12 schema as a string
|
147
|
+
// The schema may vary based on the connection in the context
|
148
|
+
// The trigger-id is extracted from the context
|
149
|
+
input-schema: func(context: trigger-context) -> result<string, app-error>;
|
150
|
+
|
151
|
+
// Get the output schema for a specific trigger
|
152
|
+
// Returns a JSON Schema Draft 2020-12 schema as a string
|
153
|
+
// The schema may vary based on the connection in the context
|
154
|
+
// The trigger-id is extracted from the context
|
155
|
+
output-schema: func(context: trigger-context) -> result<string, app-error>;
|
156
|
+
|
119
157
|
// Fetch events
|
120
158
|
//
|
121
159
|
// There are some limitations to the function:
|
@@ -140,6 +178,47 @@ interface triggers {
|
|
140
178
|
fetch-events: func(context: trigger-context) -> result<trigger-response, app-error>;
|
141
179
|
}
|
142
180
|
|
181
|
+
interface actions {
|
182
|
+
use types.{action-context, action-response, app-error};
|
183
|
+
|
184
|
+
action-ids: func() -> result<list<string>, app-error>;
|
185
|
+
|
186
|
+
// Get the input schema for a specific action
|
187
|
+
// Returns a JSON Schema Draft 2020-12 schema as a string
|
188
|
+
// The schema may vary based on the connection in the context
|
189
|
+
// The action-id is extracted from the context
|
190
|
+
input-schema: func(context: action-context) -> result<string, app-error>;
|
191
|
+
|
192
|
+
// Get the output schema for a specific action
|
193
|
+
// Returns a JSON Schema Draft 2020-12 schema as a string
|
194
|
+
// The schema may vary based on the connection in the context
|
195
|
+
// The action-id is extracted from the context
|
196
|
+
output-schema: func(context: action-context) -> result<string, app-error>;
|
197
|
+
|
198
|
+
// Execute an action
|
199
|
+
//
|
200
|
+
// There are some limitations to the function:
|
201
|
+
// - It must return an `action-response` within 30 seconds
|
202
|
+
// - The serialized-output must be a valid JSON object serialized as a string
|
203
|
+
//
|
204
|
+
// Actions can perform various operations such as:
|
205
|
+
// - Making HTTP requests to external APIs
|
206
|
+
// - Processing and transforming data
|
207
|
+
// - Storing data for future use
|
208
|
+
// - Triggering other systems or workflows
|
209
|
+
//
|
210
|
+
// The action receives input data from the previous step and can return
|
211
|
+
// serialized output data to be passed to the next step in the workflow.
|
212
|
+
execute: func(context: action-context) -> result<action-response, app-error>;
|
213
|
+
}
|
214
|
+
|
215
|
+
interface environment {
|
216
|
+
// Get all environment variables
|
217
|
+
env-vars: func() -> list<tuple<string, string>>;
|
218
|
+
// Get a specific environment variable by name
|
219
|
+
env-var: func(name: string) -> option<string>;
|
220
|
+
}
|
221
|
+
|
143
222
|
interface http {
|
144
223
|
record response {
|
145
224
|
status: u16,
|
@@ -192,5 +271,7 @@ interface http {
|
|
192
271
|
|
193
272
|
world bridge {
|
194
273
|
import http;
|
274
|
+
import environment;
|
195
275
|
export triggers;
|
276
|
+
export actions;
|
196
277
|
}
|
data/lib/app_bridge/app.rb
CHANGED
@@ -3,8 +3,16 @@
|
|
3
3
|
require "timeout"
|
4
4
|
|
5
5
|
module AppBridge
|
6
|
-
# An app that can be used to fetch events.
|
6
|
+
# An app that can be used to fetch events and execute actions.
|
7
7
|
class App
|
8
|
+
def initialize(component_path, environment_variables: {})
|
9
|
+
@component_path = component_path
|
10
|
+
@environment_variables = environment_variables
|
11
|
+
_rust_initialize(component_path, environment_variables)
|
12
|
+
rescue StandardError
|
13
|
+
raise InternalError, "Incompatible WASM file version"
|
14
|
+
end
|
15
|
+
|
8
16
|
def fetch_events(context)
|
9
17
|
response = request_events_with_timeout(context)
|
10
18
|
|
@@ -14,7 +22,15 @@ module AppBridge
|
|
14
22
|
response
|
15
23
|
end
|
16
24
|
|
17
|
-
def
|
25
|
+
def execute_action(context)
|
26
|
+
response = request_action_with_timeout(context)
|
27
|
+
|
28
|
+
validate_action_response_size!(response.serialized_output)
|
29
|
+
|
30
|
+
response
|
31
|
+
end
|
32
|
+
|
33
|
+
def timeout_seconds
|
18
34
|
30 # seconds
|
19
35
|
end
|
20
36
|
|
@@ -32,8 +48,20 @@ module AppBridge
|
|
32
48
|
raise StoreTooLargeError, "Store size exceeds 64 kB limit"
|
33
49
|
end
|
34
50
|
|
51
|
+
def validate_action_response_size!(serialized_output)
|
52
|
+
return if serialized_output.size <= 64 * 1024
|
53
|
+
|
54
|
+
raise ActionResponseTooLargeError, "Action response size exceeds 64 kB limit"
|
55
|
+
end
|
56
|
+
|
57
|
+
def request_action_with_timeout(context)
|
58
|
+
Timeout.timeout(timeout_seconds, TimeoutError, "Action exceeded #{timeout_seconds} seconds") do
|
59
|
+
_rust_execute_action(context)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
35
63
|
def request_events_with_timeout(context)
|
36
|
-
Timeout.timeout(
|
64
|
+
Timeout.timeout(timeout_seconds, TimeoutError, "Polling exceeded #{timeout_seconds} seconds") do
|
37
65
|
_rust_fetch_events(context)
|
38
66
|
end
|
39
67
|
end
|