app_bridge 0.8.4 → 2.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 303eecc7d766a080ffccb1d9b199d0728d0416600f54904f3d257e3fc2bf5c17
4
- data.tar.gz: 52e4e42cfc9b83b491555126af7b0984579e738ba40ba280ac20f255054859ca
3
+ metadata.gz: b349dc494a4c86924b7afc65d116b4d9bc43ba48ade3e7baa053440c7ce01d58
4
+ data.tar.gz: 8287685338c6d02170acf83a88148ad42503566163d09b1715656685512b9551
5
5
  SHA512:
6
- metadata.gz: 94a7305b7e768b706d5cb6bcf09d650a3b529e5a2719d60aa2d1da9a193b083309136a6f0dfe405a6ada3b8c9f910d87a1853dbd01f369acba096284083b946e
7
- data.tar.gz: 4c4f7e0a0586ec45d6d1bf26cf5583b04dddc386418da4a15f862f013fb599a6464617741345c49485ee72af628887a27c6079f03dd867c0e9d323d085fb9db7
6
+ metadata.gz: 2170946cd76e4752b4f1318554c722e99f36a890d1478d054c76190e9d3dd1ae00e5bc443e8e9733aa41d666e57aaf49e5349a9a6e9d0d912c756a853226ffe2
7
+ data.tar.gz: 4fdac8243830792bf317c8cea2c24234236189d042bf8fa9f76e278bf7977067d1ce59be8369e23f99353a41305d728a1e7723437099ddf6b6ed70fe093ee457
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.4.2
data/Cargo.lock CHANGED
@@ -67,7 +67,7 @@ checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
67
67
 
68
68
  [[package]]
69
69
  name = "app_bridge"
70
- version = "0.8.4"
70
+ version = "1.0.0"
71
71
  dependencies = [
72
72
  "magnus",
73
73
  "reqwest",
@@ -1752,9 +1752,9 @@ dependencies = [
1752
1752
 
1753
1753
  [[package]]
1754
1754
  name = "ring"
1755
- version = "0.17.11"
1755
+ version = "0.17.14"
1756
1756
  source = "registry+https://github.com/rust-lang/crates.io-index"
1757
- checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
1757
+ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
1758
1758
  dependencies = [
1759
1759
  "cc",
1760
1760
  "cfg-if",
data/Cargo.toml CHANGED
@@ -5,4 +5,4 @@
5
5
  [workspace]
6
6
  members = ["./ext/app_bridge"]
7
7
  resolver = "2"
8
- exclude = ["./spec/fixtures/components/example"]
8
+ exclude = ["./spec/fixtures/components/rust_app"]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "app_bridge"
3
- version = "0.8.4"
3
+ version = "1.0.0"
4
4
  edition = "2021"
5
5
  authors = ["Alexander Ross <ross@standout.se>"]
6
6
  publish = false
@@ -0,0 +1,30 @@
1
+ use crate::component::standout::app::types::{AppError, ErrorCode};
2
+ use magnus::{self, Error, Ruby, ExceptionClass};
3
+
4
+ impl From<ErrorCode> for ExceptionClass {
5
+ fn from(value: ErrorCode) -> Self {
6
+ fn get_class(name: &str) -> ExceptionClass {
7
+ let ruby = Ruby::get().unwrap();
8
+ ruby.eval::<ExceptionClass>(name).unwrap()
9
+ }
10
+
11
+ match value {
12
+ ErrorCode::Unauthenticated => get_class("AppBridge::UnauthenticatedError"),
13
+ ErrorCode::Forbidden => get_class("AppBridge::ForbiddenError"),
14
+ ErrorCode::Misconfigured => get_class("AppBridge::MisconfiguredError"),
15
+ ErrorCode::Unsupported => get_class("AppBridge::UnsupportedError"),
16
+ ErrorCode::RateLimit => get_class("AppBridge::RateLimitError"),
17
+ ErrorCode::Timeout => get_class("AppBridge::TimeoutError"),
18
+ ErrorCode::Unavailable => get_class("AppBridge::UnavailableError"),
19
+ ErrorCode::InternalError => get_class("AppBridge::InternalError"),
20
+ ErrorCode::MalformedResponse => get_class("AppBridge::MalformedResponseError"),
21
+ ErrorCode::Other => get_class("AppBridge::OtherError"),
22
+ }
23
+ }
24
+ }
25
+
26
+ impl From<AppError> for Error {
27
+ fn from(value: AppError) -> Self {
28
+ Error::new(value.code.into(), value.message)
29
+ }
30
+ }
@@ -1,274 +1,32 @@
1
- use magnus::{function, method, prelude::*, Error, RArray, Ruby, TryConvert, Value};
2
- use std::cell::RefCell;
3
- use wasmtime::Store;
1
+ use magnus::{function, method, prelude::*, Error, Ruby};
4
2
  mod app_state;
5
3
  mod component;
6
4
  mod request_builder;
5
+ mod error_mapping;
7
6
 
8
- use app_state::AppState;
9
- use component::standout::app::types::{Account, TriggerContext, TriggerEvent, TriggerResponse};
10
- use component::{app, build_engine, build_linker, build_store, Bridge};
11
-
12
- #[derive(Default)]
13
- pub struct RApp {
14
- component_path: String,
15
- instance: RefCell<Option<Bridge>>,
16
- store: RefCell<Option<Store<AppState>>>,
17
- }
18
-
19
- #[derive(Default)]
20
- #[magnus::wrap(class = "AppBridge::App")]
21
- struct MutRApp(RefCell<RApp>);
22
-
23
- impl MutRApp {
24
- fn initialize(&self, component_path: String) {
25
- let mut this = self.0.borrow_mut();
26
- let engine = build_engine();
27
- let linker = build_linker(&engine).unwrap();
28
- let mut store = build_store(&engine);
29
-
30
- let app = app(component_path.clone(), engine, &mut store, linker).unwrap();
31
-
32
- this.component_path = component_path.to_string();
33
- *this.instance.borrow_mut() = Some(app);
34
- *this.store.borrow_mut() = Some(store);
35
- }
36
-
37
- fn triggers(&self) -> Vec<String> {
38
- let binding = self.0.borrow();
39
-
40
- let mut instance = binding.instance.borrow_mut();
41
- let mut store = binding.store.borrow_mut();
42
-
43
- if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
44
- instance
45
- .standout_app_triggers()
46
- .call_get_triggers(store)
47
- .unwrap()
48
- } else {
49
- vec![]
50
- }
51
- }
52
-
53
- fn rb_fetch_events(&self, context: Value) -> RTriggerResponse {
54
- let context: RTriggerContext = TryConvert::try_convert(context).unwrap();
55
- let response = self.fetch_events(context.inner);
56
-
57
- RTriggerResponse::from_trigger_response(response)
58
- }
59
-
60
- fn fetch_events(&self, context: TriggerContext) -> TriggerResponse {
61
- let binding = self.0.borrow();
62
-
63
- let mut instance = binding.instance.borrow_mut();
64
- let mut store = binding.store.borrow_mut();
65
-
66
- if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
67
- instance
68
- .standout_app_triggers()
69
- .call_fetch_events(store, &context)
70
- .unwrap()
71
- } else {
72
- TriggerResponse {
73
- store: context.store,
74
- events: vec![],
75
- }
76
- }
77
- }
78
- }
79
-
80
- #[magnus::wrap(class = "AppBridge::Account")]
81
- struct RAccount {
82
- inner: Account,
83
- }
84
-
85
- impl RAccount {
86
- fn new(id: String, name: String, serialized_data: String) -> Self {
87
- let inner = Account {
88
- id: id,
89
- name: name,
90
- serialized_data: serialized_data,
91
- };
92
- Self { inner }
93
- }
94
-
95
- fn id(&self) -> String {
96
- self.inner.id.clone()
97
- }
98
-
99
- fn name(&self) -> String {
100
- self.inner.name.clone()
101
- }
102
-
103
- fn serialized_data(&self) -> String {
104
- self.inner.serialized_data.clone()
105
- }
106
- }
107
-
108
- impl Clone for RAccount {
109
- fn clone(&self) -> Self {
110
- Self {
111
- inner: self.inner.clone(),
112
- }
113
- }
114
- }
115
-
116
- impl TryConvert for RAccount {
117
- fn try_convert(val: Value) -> Result<Self, Error> {
118
- let id: String = val.funcall("id", ())?;
119
- let name: String = val.funcall("name", ())?;
120
- let serialized_data: String = val.funcall("serialized_data", ())?;
121
-
122
- let inner = Account {
123
- id,
124
- name,
125
- serialized_data,
126
- };
127
-
128
- Ok(Self { inner })
129
- }
130
- }
131
-
132
- #[magnus::wrap(class = "AppBridge::TriggerContext")]
133
- struct RTriggerContext {
134
- inner: TriggerContext,
135
- wrapped_account: RAccount,
136
- }
137
-
138
- impl RTriggerContext {
139
- fn new(trigger_id: String, account: Value, store: String) -> Self {
140
- let account: RAccount = TryConvert::try_convert(account).unwrap();
141
-
142
- let inner = TriggerContext {
143
- trigger_id: trigger_id,
144
- account: account.clone().inner,
145
- store: store,
146
- };
147
- Self {
148
- inner,
149
- wrapped_account: account.clone(),
150
- }
151
- }
152
-
153
- fn trigger_id(&self) -> String {
154
- self.inner.trigger_id.clone()
155
- }
156
-
157
- fn account(&self) -> RAccount {
158
- self.wrapped_account.clone()
159
- }
160
-
161
- fn store(&self) -> String {
162
- self.inner.store.clone()
163
- }
164
- }
165
-
166
- impl TryConvert for RTriggerContext {
167
- fn try_convert(val: Value) -> Result<Self, Error> {
168
- let account_val: Value = val.funcall("account", ())?;
169
- let store: String = val.funcall("store", ())?;
170
- let trigger_id: String = val.funcall("trigger_id", ())?;
171
-
172
- let account: RAccount = TryConvert::try_convert(account_val).unwrap();
173
-
174
- let inner = TriggerContext {
175
- trigger_id: trigger_id,
176
- account: account.clone().inner,
177
- store: store,
178
- };
179
-
180
- Ok(Self {
181
- inner,
182
- wrapped_account: account.clone(),
183
- })
184
- }
185
- }
186
-
187
- #[magnus::wrap(class = "AppBridge::TriggerResponse")]
188
- struct RTriggerResponse {
189
- inner: TriggerResponse,
190
- }
191
-
192
- impl RTriggerResponse {
193
- fn new(store: String, events: RArray) -> Self {
194
- let iter = events.into_iter();
195
- let res: Vec<RTriggerEvent> = iter
196
- .map(&TryConvert::try_convert)
197
- .collect::<Result<Vec<RTriggerEvent>, Error>>()
198
- .unwrap();
199
-
200
- let inner = TriggerResponse {
201
- store: store,
202
- events: res.iter().map(|e| e.inner.clone()).collect(),
203
- };
204
- Self { inner }
205
- }
206
-
207
- fn from_trigger_response(inner: TriggerResponse) -> Self {
208
- Self { inner }
209
- }
210
-
211
- fn store(&self) -> String {
212
- self.inner.store.clone()
213
- }
214
-
215
- fn events(&self) -> RArray {
216
- self.inner
217
- .events
218
- .iter()
219
- .map(|e| RTriggerEvent::new(e.id.clone(), e.timestamp, e.serialized_data.clone()))
220
- .collect()
221
- }
222
- }
223
-
224
- #[magnus::wrap(class = "AppBridge::TriggerEvent")]
225
- struct RTriggerEvent {
226
- inner: TriggerEvent,
227
- }
228
-
229
- impl RTriggerEvent {
230
- fn new(id: String, timestamp: u64, serialized_data: String) -> Self {
231
- let inner = TriggerEvent {
232
- id: id,
233
- timestamp: timestamp,
234
- serialized_data: serialized_data,
235
- };
236
- Self { inner }
237
- }
238
-
239
- fn id(&self) -> String {
240
- self.inner.id.clone()
241
- }
242
-
243
- fn timestamp(&self) -> u64 {
244
- self.inner.timestamp
245
- }
246
-
247
- fn serialized_data(&self) -> String {
248
- self.inner.serialized_data.clone()
249
- }
250
- }
251
-
252
- impl TryConvert for RTriggerEvent {
253
- fn try_convert(val: Value) -> Result<Self, Error> {
254
- let id: String = val.funcall("id", ())?;
255
- let timestamp: u64 = val.funcall("timestamp", ())?;
256
- let serialized_data: String = val.funcall("serialized_data", ())?;
257
-
258
- let inner = TriggerEvent {
259
- id,
260
- timestamp,
261
- serialized_data,
262
- };
263
-
264
- Ok(Self { inner })
265
- }
266
- }
7
+ mod wrappers;
8
+ use wrappers::account::RAccount;
9
+ use wrappers::trigger_context::RTriggerContext;
10
+ use wrappers::trigger_event::RTriggerEvent;
11
+ use wrappers::trigger_response::RTriggerResponse;
12
+ use wrappers::app::MutRApp;
267
13
 
268
14
  #[magnus::init]
269
15
  fn init(ruby: &Ruby) -> Result<(), Error> {
270
16
  let module = ruby.define_module("AppBridge")?;
271
17
 
18
+ let error = module.define_error("Error", ruby.exception_standard_error())?;
19
+ module.define_error("UnauthenticatedError", error)?;
20
+ module.define_error("ForbiddenError", error)?;
21
+ module.define_error("MisconfiguredError", error)?;
22
+ module.define_error("UnsupportedError", error)?;
23
+ module.define_error("RateLimitError", error)?;
24
+ module.define_error("TimeoutError", error)?;
25
+ module.define_error("UnavailableError", error)?;
26
+ module.define_error("InternalError", error)?;
27
+ module.define_error("MalformedResponseError", error)?;
28
+ module.define_error("OtherError", error)?;
29
+
272
30
  // Define the Accout class
273
31
  let account_class = module.define_class("Account", ruby.class_object())?;
274
32
  account_class.define_singleton_method("new", function!(RAccount::new, 3))?;
@@ -277,9 +35,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
277
35
  account_class.define_method("serialized_data", method!(RAccount::serialized_data, 0))?;
278
36
 
279
37
  let trigger_event_class = module.define_class("TriggerEvent", ruby.class_object())?;
280
- trigger_event_class.define_singleton_method("new", function!(RTriggerEvent::new, 3))?;
38
+ trigger_event_class.define_singleton_method("new", function!(RTriggerEvent::new, 2))?;
281
39
  trigger_event_class.define_method("id", method!(RTriggerEvent::id, 0))?;
282
- trigger_event_class.define_method("timestamp", method!(RTriggerEvent::timestamp, 0))?;
283
40
  trigger_event_class.define_method(
284
41
  "serialized_data",
285
42
  method!(RTriggerEvent::serialized_data, 0),
@@ -300,8 +57,8 @@ fn init(ruby: &Ruby) -> Result<(), Error> {
300
57
  let app_class = module.define_class("App", ruby.class_object())?;
301
58
  app_class.define_alloc_func::<MutRApp>();
302
59
  app_class.define_method("initialize", method!(MutRApp::initialize, 1))?;
303
- app_class.define_method("triggers", method!(MutRApp::triggers, 0))?;
304
- app_class.define_method("fetch_events", method!(MutRApp::rb_fetch_events, 1))?;
60
+ app_class.define_method("trigger_ids", method!(MutRApp::trigger_ids, 0))?;
61
+ app_class.define_private_method("_rust_fetch_events", method!(MutRApp::rb_fetch_events, 1))?;
305
62
 
306
63
  Ok(())
307
64
  }
@@ -0,0 +1,60 @@
1
+ use magnus::{prelude::*, Error, TryConvert, Value};
2
+ use crate::component::standout::app::types::Account;
3
+
4
+ #[magnus::wrap(class = "AppBridge::Account")]
5
+ pub struct RAccount {
6
+ pub inner: Account,
7
+ }
8
+
9
+ impl RAccount {
10
+ pub fn new(id: String, name: String, serialized_data: String) -> Self {
11
+ let inner = Account {
12
+ id: id,
13
+ name: name,
14
+ serialized_data: serialized_data,
15
+ };
16
+ Self { inner }
17
+ }
18
+
19
+ pub fn id(&self) -> String {
20
+ self.inner.id.clone()
21
+ }
22
+
23
+ pub fn name(&self) -> String {
24
+ self.inner.name.clone()
25
+ }
26
+
27
+ pub fn serialized_data(&self) -> String {
28
+ self.inner.serialized_data.clone()
29
+ }
30
+ }
31
+
32
+ impl Clone for RAccount {
33
+ fn clone(&self) -> Self {
34
+ Self {
35
+ inner: self.inner.clone(),
36
+ }
37
+ }
38
+ }
39
+
40
+ impl TryConvert for RAccount {
41
+ fn try_convert(val: Value) -> Result<Self, Error> {
42
+ let id: String = val.funcall("id", ())?;
43
+ let name: String = val.funcall("name", ())?;
44
+ let serialized_data: String = val.funcall("serialized_data", ())?;
45
+
46
+ let inner = Account {
47
+ id,
48
+ name,
49
+ serialized_data,
50
+ };
51
+
52
+ Ok(Self { inner })
53
+ }
54
+ }
55
+
56
+ impl From<RAccount> for Account {
57
+ fn from(raccount: RAccount) -> Self {
58
+ raccount.inner
59
+ }
60
+ }
@@ -0,0 +1,114 @@
1
+ use magnus::{Error, TryConvert, Value};
2
+ use std::cell::RefCell;
3
+ use wasmtime::Store;
4
+
5
+ use crate::app_state::AppState;
6
+ use crate::component::standout::app::types::{TriggerContext, TriggerResponse, AppError, ErrorCode};
7
+ use crate::component::{app, build_engine, build_linker, build_store, Bridge};
8
+ use super::trigger_context::RTriggerContext;
9
+
10
+ use super::trigger_response::RTriggerResponse;
11
+
12
+ #[derive(Default)]
13
+ pub struct RApp {
14
+ component_path: String,
15
+ instance: RefCell<Option<Bridge>>,
16
+ store: RefCell<Option<Store<AppState>>>,
17
+ }
18
+
19
+ #[derive(Default)]
20
+ #[magnus::wrap(class = "AppBridge::App")]
21
+ pub struct MutRApp(RefCell<RApp>);
22
+
23
+ impl MutRApp {
24
+ pub fn initialize(&self, component_path: String) {
25
+ let mut this = self.0.borrow_mut();
26
+ let engine = build_engine();
27
+ let linker = build_linker(&engine).unwrap();
28
+ let mut store = build_store(&engine);
29
+
30
+ let app = app(component_path.clone(), engine, &mut store, linker).unwrap();
31
+
32
+ this.component_path = component_path.to_string();
33
+ *this.instance.borrow_mut() = Some(app);
34
+ *this.store.borrow_mut() = Some(store);
35
+ }
36
+
37
+ pub fn trigger_ids(&self) -> Result<Vec<String>, Error> {
38
+ let binding = self.0.borrow();
39
+
40
+ let mut instance = binding.instance.borrow_mut();
41
+ let mut store = binding.store.borrow_mut();
42
+
43
+ if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
44
+ match instance.standout_app_triggers().call_trigger_ids(store) {
45
+ Ok(result) => {
46
+ match result {
47
+ Ok(ids) => Ok(ids),
48
+ Err(err) => Err(err.into())
49
+ }
50
+ },
51
+ Err(err) => {
52
+ if let Some(wit_err) = err.downcast_ref::<AppError>() {
53
+ Err(wit_err.clone().into())
54
+ } else {
55
+ Err(AppError {
56
+ code: ErrorCode::InternalError,
57
+ message: format!("Unexpected error: {:?}", err),
58
+ }.into())
59
+ }
60
+ },
61
+ }
62
+ } else {
63
+ Err(AppError {
64
+ code: ErrorCode::InternalError,
65
+ message: "App instance couln't be initialized".to_string(),
66
+ }.into())
67
+ }
68
+ }
69
+
70
+ pub fn rb_fetch_events(&self, context: Value) -> Result<RTriggerResponse, magnus::Error> {
71
+ let context: RTriggerContext = TryConvert::try_convert(context).unwrap();
72
+ let response = self.fetch_events(context.into());
73
+
74
+ match response {
75
+ Ok(response) => Ok(response.into()),
76
+ Err(err) => Err(err.into())
77
+ }
78
+ }
79
+
80
+ fn fetch_events(&self, context: TriggerContext) -> Result<TriggerResponse, AppError> {
81
+ let binding = self.0.borrow();
82
+
83
+ let mut instance = binding.instance.borrow_mut();
84
+ let mut store = binding.store.borrow_mut();
85
+
86
+ if let (Some(instance), Some(store)) = (&mut *instance, &mut *store) {
87
+ match instance
88
+ .standout_app_triggers()
89
+ .call_fetch_events(store, &context) {
90
+ Ok(response) => {
91
+ match response {
92
+ Ok(res) => Ok(res),
93
+ Err(err) => Err(err),
94
+ }
95
+ },
96
+ Err(err) => {
97
+ if let Some(wit_err) = err.downcast_ref::<AppError>() {
98
+ Err(AppError::from(wit_err.clone()))
99
+ } else {
100
+ Err(AppError {
101
+ code: ErrorCode::InternalError,
102
+ message: format!("Unexpected error: {:?}", err),
103
+ })
104
+ }
105
+ }
106
+ }
107
+ } else {
108
+ Err(AppError {
109
+ code: ErrorCode::InternalError,
110
+ message: "App instance couln't be initialized".to_string(),
111
+ })
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,5 @@
1
+ pub mod account;
2
+ pub mod trigger_context;
3
+ pub mod trigger_response;
4
+ pub mod trigger_event;
5
+ pub mod app;
@@ -0,0 +1,64 @@
1
+ use magnus::{prelude::*, Error, TryConvert, Value};
2
+ use crate::component::standout::app::types::TriggerContext;
3
+ use super::account::RAccount;
4
+
5
+ #[magnus::wrap(class = "AppBridge::TriggerContext")]
6
+ pub struct RTriggerContext {
7
+ inner: TriggerContext,
8
+ wrapped_account: RAccount,
9
+ }
10
+
11
+ impl RTriggerContext {
12
+ pub fn new(trigger_id: String, account: Value, store: String) -> Self {
13
+ let account: RAccount = TryConvert::try_convert(account).unwrap();
14
+
15
+ let inner = TriggerContext {
16
+ trigger_id: trigger_id,
17
+ account: account.clone().into(),
18
+ store: store,
19
+ };
20
+ Self {
21
+ inner,
22
+ wrapped_account: account.clone(),
23
+ }
24
+ }
25
+
26
+ pub fn trigger_id(&self) -> String {
27
+ self.inner.trigger_id.clone()
28
+ }
29
+
30
+ pub fn account(&self) -> RAccount {
31
+ self.wrapped_account.clone()
32
+ }
33
+
34
+ pub fn store(&self) -> String {
35
+ self.inner.store.clone()
36
+ }
37
+ }
38
+
39
+ impl TryConvert for RTriggerContext {
40
+ fn try_convert(val: Value) -> Result<Self, Error> {
41
+ let account_val: Value = val.funcall("account", ())?;
42
+ let store: String = val.funcall("store", ())?;
43
+ let trigger_id: String = val.funcall("trigger_id", ())?;
44
+
45
+ let account: RAccount = TryConvert::try_convert(account_val).unwrap();
46
+
47
+ let inner = TriggerContext {
48
+ trigger_id: trigger_id,
49
+ account: account.clone().inner,
50
+ store: store,
51
+ };
52
+
53
+ Ok(Self {
54
+ inner,
55
+ wrapped_account: account.clone(),
56
+ })
57
+ }
58
+ }
59
+
60
+ impl From<RTriggerContext> for TriggerContext {
61
+ fn from(rtrigger_context: RTriggerContext) -> Self {
62
+ rtrigger_context.inner
63
+ }
64
+ }
@@ -0,0 +1,63 @@
1
+ use magnus::{prelude::*, Error, TryConvert, Value};
2
+ use crate::component::standout::app::types::TriggerEvent;
3
+
4
+ #[magnus::wrap(class = "AppBridge::TriggerEvent")]
5
+ pub struct RTriggerEvent {
6
+ inner: TriggerEvent,
7
+ }
8
+
9
+ impl RTriggerEvent {
10
+ pub fn new(id: String, serialized_data: String) -> Self {
11
+ let inner: TriggerEvent = TriggerEvent {
12
+ id: id,
13
+ serialized_data: serialized_data,
14
+ };
15
+ Self { inner }
16
+ }
17
+
18
+ pub fn id(&self) -> String {
19
+ self.inner.id.clone()
20
+ }
21
+
22
+ pub fn serialized_data(&self) -> String {
23
+ self.inner.serialized_data.clone()
24
+ }
25
+ }
26
+
27
+ impl TryConvert for RTriggerEvent {
28
+ fn try_convert(val: Value) -> Result<Self, Error> {
29
+ let id: String = val.funcall("id", ())?;
30
+ let serialized_data: String = val.funcall("serialized_data", ())?;
31
+
32
+ let inner = TriggerEvent {
33
+ id,
34
+ serialized_data,
35
+ };
36
+
37
+ Ok(Self { inner })
38
+ }
39
+ }
40
+
41
+ impl From<RTriggerEvent> for TriggerEvent {
42
+ fn from(rtrigger_event: RTriggerEvent) -> Self {
43
+ rtrigger_event.inner
44
+ }
45
+ }
46
+
47
+ impl From<TriggerEvent> for RTriggerEvent {
48
+ fn from(trigger_event: TriggerEvent) -> Self {
49
+ Self { inner: trigger_event }
50
+ }
51
+ }
52
+
53
+ impl From<&RTriggerEvent> for TriggerEvent {
54
+ fn from(rtrigger_event: &RTriggerEvent) -> Self {
55
+ rtrigger_event.inner.clone()
56
+ }
57
+ }
58
+
59
+ impl From<&TriggerEvent> for RTriggerEvent {
60
+ fn from(trigger_event: &TriggerEvent) -> Self {
61
+ Self { inner: trigger_event.clone() }
62
+ }
63
+ }
@@ -0,0 +1,42 @@
1
+ use magnus::{Error, RArray, TryConvert};
2
+ use crate::component::standout::app::types::TriggerResponse;
3
+ use super::trigger_event::RTriggerEvent;
4
+
5
+ #[magnus::wrap(class = "AppBridge::TriggerResponse")]
6
+ pub struct RTriggerResponse {
7
+ inner: TriggerResponse,
8
+ }
9
+
10
+ impl RTriggerResponse {
11
+ pub fn new(store: String, events: RArray) -> Self {
12
+ let iter = events.into_iter();
13
+ let res: Vec<RTriggerEvent> = iter
14
+ .map(&TryConvert::try_convert)
15
+ .collect::<Result<Vec<RTriggerEvent>, Error>>()
16
+ .unwrap();
17
+
18
+ let inner = TriggerResponse {
19
+ store: store,
20
+ events: res.iter().map(|e| e.into()).collect(),
21
+ };
22
+ Self { inner }
23
+ }
24
+
25
+ pub fn store(&self) -> String {
26
+ self.inner.store.clone()
27
+ }
28
+
29
+ pub fn events(&self) -> RArray {
30
+ self.inner
31
+ .events
32
+ .iter()
33
+ .map(|e| RTriggerEvent::from(e))
34
+ .collect()
35
+ }
36
+ }
37
+
38
+ impl From<TriggerResponse> for RTriggerResponse {
39
+ fn from(value: TriggerResponse) -> Self {
40
+ Self { inner: value }
41
+ }
42
+ }
@@ -1,4 +1,4 @@
1
- package standout:app@0.3.0;
1
+ package standout:app@2.0.0;
2
2
 
3
3
  interface types {
4
4
  // The trigger-store is a string that is used to store data between trigger
@@ -59,30 +59,69 @@ interface types {
59
59
  // ensure that the event is only triggered once per order update.
60
60
  id: string,
61
61
 
62
- // The timestamp of the event.
63
- // Must be a unix timestamp in milliseconds since epoch (UTC).
64
- // In JavaScript `Date.now()` can be used to get the current timestamp in
65
- // milliseconds.
66
- timestamp: u64,
67
-
68
62
  // Serialized data must be a JSON object serialized into a string
69
63
  // Note that it is important that the root is a object, not an array,
70
64
  // or another primitive type.
71
65
  serialized-data: string,
72
66
  }
67
+
68
+ /// A structured error that can be returned by for example a call to a trigger or action.
69
+ /// Contains a machine-readable code and a human-readable message.
70
+ record app-error {
71
+ /// The error code identifying the type of failure.
72
+ code: error-code,
73
+
74
+ /// A human-readable message describing the error in more detail.
75
+ message: string,
76
+ }
77
+
78
+ /// An enumeration of error codes that can be returned by a trigger implementation.
79
+ /// These codes help the platform and plugin developers distinguish between different types of failures.
80
+ variant error-code {
81
+ /// Authentication failed. Typically due to an invalid or expired API key or token.
82
+ unauthenticated,
83
+
84
+ /// Authorization failed. The account is valid but does not have the necessary permissions.
85
+ forbidden,
86
+
87
+ /// The trigger is misconfigured. For example, a required setting is missing or invalid.
88
+ misconfigured,
89
+
90
+ /// The target system does not support a required feature or endpoint.
91
+ unsupported,
92
+
93
+ /// The target system is rate-limiting requests. Try again later.
94
+ rate-limit,
95
+
96
+ /// The request timed out. The target system did not respond in time.
97
+ timeout,
98
+
99
+ /// The target system is currently unavailable or unreachable.
100
+ unavailable,
101
+
102
+ /// An unexpected internal error occurred in the plugin.
103
+ internal-error,
104
+
105
+ /// The response from the external system could not be parsed or was in an invalid format.
106
+ malformed-response,
107
+
108
+ /// A catch-all for all other types of errors. Should include a descriptive message.
109
+ other,
110
+ }
73
111
  }
74
112
 
75
113
 
76
114
  interface triggers {
77
- use types.{trigger-context, trigger-event, trigger-response};
115
+ use types.{trigger-context, trigger-event, trigger-response, app-error};
78
116
 
79
- get-triggers: func() -> list<string>;
117
+ trigger-ids: func() -> result<list<string>, app-error>;
80
118
 
81
119
  // Fetch events
82
120
  //
83
121
  // There are some limitations to the function:
84
- // - It must return within 30 seconds
85
- // - It must return less than or equal to 100 events
122
+ // - It must a `trigger-response` within 30 seconds
123
+ // - It must return less than or equal to 100 `trigger-response.events`
124
+ // - It must not return more than 64 kB of data in the `trigger-response.store`
86
125
  //
87
126
  // If you need to fetch more events, you can return up to 100 events and then
88
127
  // store the data needed for you to remember where you left off in the store.
@@ -98,7 +137,7 @@ interface triggers {
98
137
  // the same events. That will ensure that the user that is building an
99
138
  // integration with your trigger will not miss any events if your system is
100
139
  // down for a short period of time.
101
- fetch-events: func(context: trigger-context) -> trigger-response;
140
+ fetch-events: func(context: trigger-context) -> result<trigger-response, app-error>;
102
141
  }
103
142
 
104
143
  interface http {
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "timeout"
4
+
5
+ module AppBridge
6
+ # An app that can be used to fetch events.
7
+ class App
8
+ def fetch_events(context)
9
+ response = request_events_with_timeout(context)
10
+
11
+ validate_number_of_events!(response.events)
12
+ validate_store_size!(response.store)
13
+
14
+ response
15
+ end
16
+
17
+ def polling_timeout
18
+ 30 # seconds
19
+ end
20
+
21
+ private
22
+
23
+ def validate_number_of_events!(events)
24
+ return if events.size <= 100
25
+
26
+ raise TooManyEventsError, "Maximum 100 events allowed"
27
+ end
28
+
29
+ def validate_store_size!(store)
30
+ return if store.size <= 64 * 1024
31
+
32
+ raise StoreTooLargeError, "Store size exceeds 64 kB limit"
33
+ end
34
+
35
+ def request_events_with_timeout(context)
36
+ Timeout.timeout(polling_timeout, TimeoutError, "Polling exceeded #{polling_timeout} seconds") do
37
+ _rust_fetch_events(context)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppBridge
4
- VERSION = "0.8.4"
4
+ VERSION = "2.0.0"
5
5
  end
data/lib/app_bridge.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "app_bridge/version"
4
+ require_relative "app_bridge/app"
5
+
4
6
  begin
5
7
  require "app_bridge/#{RUBY_VERSION.split(".").first(2).join(".")}/app_bridge"
6
8
  rescue LoadError
@@ -9,6 +11,9 @@ end
9
11
 
10
12
  module AppBridge
11
13
  class Error < StandardError; end
14
+ class TimeoutError < Error; end
15
+ class TooManyEventsError < Error; end
16
+ class StoreTooLargeError < Error; end
12
17
 
13
18
  # Represents a trigger event that is recieved from the app.
14
19
  class TriggerEvent
data/tasks/fixtures.rake CHANGED
@@ -2,36 +2,43 @@
2
2
 
3
3
  require "English"
4
4
 
5
- namespace :fixtures do
5
+ namespace :fixtures do # rubocop:disable Metrics/BlockLength
6
6
  namespace :apps do
7
7
  desc "Clean up build artifacts"
8
8
  task :clean do
9
- # In context of the path spec/fixtures/components/example.
9
+ # In context of the path spec/fixtures/components/rust_app.
10
10
  # Execute cargo clean.
11
11
  #
12
- pwd = "spec/fixtures/components/example"
12
+ pwd = "spec/fixtures/components/rust_app"
13
13
  pid = Process.spawn("cargo clean", chdir: pwd)
14
14
  Process.wait(pid)
15
15
  raise "Failed to clean build artifacts" unless $CHILD_STATUS.success?
16
16
 
17
17
  # Remove the built wasm artifact.
18
- pid = Process.spawn("rm example.wasm", chdir: "spec/fixtures/components")
18
+ pid = Process.spawn("rm rust_app.wasm", chdir: "spec/fixtures/components")
19
19
  Process.wait(pid)
20
20
  end
21
21
 
22
22
  desc "Compile the fixture apps"
23
- task :compile do
24
- pwd = "spec/fixtures/components/example"
23
+ task :compile_rust do
24
+ pwd = "spec/fixtures/components/rust_app"
25
25
  compile_pid = Process.spawn("cargo clean && cargo build --release --target wasm32-wasip2",
26
26
  chdir: pwd)
27
27
  Process.wait(compile_pid)
28
28
  raise "Failed to build artifacts" unless $CHILD_STATUS.success?
29
29
 
30
- move_pid = Process.spawn("mv #{pwd}/target/wasm32-wasip2/release/example.wasm #{pwd}/../example.wasm")
30
+ move_pid = Process.spawn("mv #{pwd}/target/wasm32-wasip2/release/rust_app.wasm #{pwd}/../rust_app.wasm")
31
31
  Process.wait(move_pid)
32
32
  end
33
+
34
+ task :compile_js do
35
+ pwd = "spec/fixtures/components/js_app"
36
+ pid = Process.spawn("npm run build", chdir: pwd)
37
+ Process.wait(pid)
38
+ raise "Failed to build artifacts" unless $CHILD_STATUS.success?
39
+ end
33
40
  end
34
41
  end
35
42
 
36
43
  desc "Build all fixtures"
37
- task fixtures: %i[fixtures:apps:clean fixtures:apps:compile]
44
+ task fixtures: %i[fixtures:apps:clean fixtures:apps:compile_rust fixtures:apps:compile_js]
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: app_bridge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Ross
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-02-28 00:00:00.000000000 Z
10
+ date: 2025-04-14 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rb_sys
@@ -36,6 +35,7 @@ extra_rdoc_files: []
36
35
  files:
37
36
  - ".rspec"
38
37
  - ".rubocop.yml"
38
+ - ".tool-versions"
39
39
  - CHANGELOG.md
40
40
  - Cargo.lock
41
41
  - Cargo.toml
@@ -45,10 +45,18 @@ files:
45
45
  - ext/app_bridge/extconf.rb
46
46
  - ext/app_bridge/src/app_state.rs
47
47
  - ext/app_bridge/src/component.rs
48
+ - ext/app_bridge/src/error_mapping.rs
48
49
  - ext/app_bridge/src/lib.rs
49
50
  - ext/app_bridge/src/request_builder.rs
51
+ - ext/app_bridge/src/wrappers/account.rs
52
+ - ext/app_bridge/src/wrappers/app.rs
53
+ - ext/app_bridge/src/wrappers/mod.rs
54
+ - ext/app_bridge/src/wrappers/trigger_context.rs
55
+ - ext/app_bridge/src/wrappers/trigger_event.rs
56
+ - ext/app_bridge/src/wrappers/trigger_response.rs
50
57
  - ext/app_bridge/wit/world.wit
51
58
  - lib/app_bridge.rb
59
+ - lib/app_bridge/app.rb
52
60
  - lib/app_bridge/version.rb
53
61
  - sig/app_bridge.rbs
54
62
  - tasks/fixtures.rake
@@ -60,7 +68,6 @@ metadata:
60
68
  source_code_uri: https://github.com/standout/app_bridge
61
69
  changelog_uri: https://github.com/standout/app_bridge/blob/main/CHANGELOG.md
62
70
  rubygems_mfa_required: 'true'
63
- post_install_message:
64
71
  rdoc_options: []
65
72
  require_paths:
66
73
  - lib
@@ -76,8 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
83
  - !ruby/object:Gem::Version
77
84
  version: 3.3.11
78
85
  requirements: []
79
- rubygems_version: 3.5.22
80
- signing_key:
86
+ rubygems_version: 3.6.2
81
87
  specification_version: 4
82
88
  summary: Communication layer for Standout integration apps
83
89
  test_files: []