app_bridge 3.0.0 → 4.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.
@@ -1,178 +1,291 @@
1
1
  use crate::app_state::AppState;
2
- use crate::component::standout::app::http::{
3
- HostRequestBuilder, Method, Request, RequestBuilder, RequestError, Response,
4
- };
2
+ use crate::component::{v3, v4};
3
+ use crate::component::v4::standout::app::http::{Method, Request, RequestError, Response};
5
4
  use reqwest::Method as ReqwestMethod;
6
5
  use std::result::Result::Ok;
7
6
  use wasmtime::component::Resource;
8
7
 
9
- impl HostRequestBuilder for AppState {
10
- fn new(&mut self) -> Resource<RequestBuilder> {
11
- let id = self.next_request_id;
12
- self.next_request_id += 1;
13
- self.request_list.insert(id, Request::default());
14
- Resource::new_own(id)
15
- }
8
+ // ============================================================================
9
+ // Macro to implement HostRequestBuilder for any version
10
+ //
11
+ // When adding a new version, just add:
12
+ // impl_host_request_builder!(v5);
13
+ // impl_http_type_conversions!(v5);
14
+ // ============================================================================
16
15
 
17
- fn method(
18
- &mut self,
19
- self_: Resource<RequestBuilder>,
20
- method: Method,
21
- ) -> Resource<RequestBuilder> {
22
- let id = self_.rep();
23
- let mut request = self.request_list.get(&id).cloned().unwrap();
24
- request.method = method;
25
- let new_id = self.next_request_id;
26
- self.next_request_id += 1;
27
- self.request_list.insert(new_id, request);
28
- Resource::new_own(new_id)
29
- }
16
+ macro_rules! impl_host_request_builder {
17
+ ($v:ident) => {
18
+ impl $v::standout::app::http::HostRequestBuilder for AppState {
19
+ fn new(&mut self) -> Resource<$v::standout::app::http::RequestBuilder> {
20
+ let id = self.next_request_id;
21
+ self.next_request_id += 1;
22
+ self.request_list.insert(id, Request::default());
23
+ Resource::new_own(id)
24
+ }
30
25
 
31
- fn url(
32
- &mut self,
33
- self_: Resource<RequestBuilder>,
34
- url: wasmtime::component::__internal::String,
35
- ) -> Resource<RequestBuilder> {
36
- let id = self_.rep();
37
- let mut request = self.request_list.get(&id).cloned().unwrap();
38
- request.url = url;
39
- let new_id = self.next_request_id;
40
- self.next_request_id += 1;
41
- self.request_list.insert(new_id, request);
42
- Resource::new_own(new_id)
43
- }
26
+ fn method(
27
+ &mut self,
28
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
29
+ method: $v::standout::app::http::Method,
30
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
31
+ let id = self_.rep();
32
+ if let Some(mut request) = self.request_list.get(&id).cloned() {
33
+ request.method = method.into();
34
+ let new_id = self.next_request_id;
35
+ self.next_request_id += 1;
36
+ self.request_list.insert(new_id, request);
37
+ Resource::new_own(new_id)
38
+ } else {
39
+ Resource::new_own(id)
40
+ }
41
+ }
44
42
 
45
- #[doc = " Add a header to the request"]
46
- fn header(
47
- &mut self,
48
- self_: Resource<RequestBuilder>,
49
- key: wasmtime::component::__internal::String,
50
- value: wasmtime::component::__internal::String,
51
- ) -> Resource<RequestBuilder> {
52
- let id = self_.rep();
53
- let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
54
- request.headers.push((key, value));
55
- let new_id = self.next_request_id;
56
- self.next_request_id += 1;
57
- self.request_list.insert(new_id, request);
58
- Resource::new_own(new_id)
59
- }
43
+ fn url(
44
+ &mut self,
45
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
46
+ url: String,
47
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
48
+ let id = self_.rep();
49
+ if let Some(mut request) = self.request_list.get(&id).cloned() {
50
+ request.url = url;
51
+ let new_id = self.next_request_id;
52
+ self.next_request_id += 1;
53
+ self.request_list.insert(new_id, request);
54
+ Resource::new_own(new_id)
55
+ } else {
56
+ Resource::new_own(id)
57
+ }
58
+ }
59
+
60
+ fn header(
61
+ &mut self,
62
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
63
+ key: String,
64
+ value: String,
65
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
66
+ let id = self_.rep();
67
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
68
+ request.headers.push((key, value));
69
+ let new_id = self.next_request_id;
70
+ self.next_request_id += 1;
71
+ self.request_list.insert(new_id, request);
72
+ Resource::new_own(new_id)
73
+ }
60
74
 
61
- fn headers(
62
- &mut self,
63
- self_: Resource<RequestBuilder>,
64
- headers: wasmtime::component::__internal::Vec<(
65
- wasmtime::component::__internal::String,
66
- wasmtime::component::__internal::String,
67
- )>,
68
- ) -> Resource<RequestBuilder> {
69
- let id = self_.rep();
70
- let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
71
- for (key, value) in headers {
72
- request.headers.push((key, value));
75
+ fn headers(
76
+ &mut self,
77
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
78
+ headers: Vec<(String, String)>,
79
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
80
+ let id = self_.rep();
81
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
82
+ request.headers.extend(headers);
83
+ let new_id = self.next_request_id;
84
+ self.next_request_id += 1;
85
+ self.request_list.insert(new_id, request);
86
+ Resource::new_own(new_id)
87
+ }
88
+
89
+ fn body(
90
+ &mut self,
91
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
92
+ body: String,
93
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
94
+ let id = self_.rep();
95
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
96
+ request.body = body;
97
+ let new_id = self.next_request_id;
98
+ self.next_request_id += 1;
99
+ self.request_list.insert(new_id, request);
100
+ Resource::new_own(new_id)
101
+ }
102
+
103
+ fn send(
104
+ &mut self,
105
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
106
+ ) -> Result<$v::standout::app::http::Response, $v::standout::app::http::RequestError> {
107
+ let id = self_.rep();
108
+ match self.request_list.get(&id).cloned() {
109
+ Some(request) => send_request(&self.client, &request)
110
+ .map(Into::into)
111
+ .map_err(Into::into),
112
+ None => Err($v::standout::app::http::RequestError::Other(
113
+ "Request not found".to_string(),
114
+ )),
115
+ }
116
+ }
117
+
118
+ fn drop(
119
+ &mut self,
120
+ rep: Resource<$v::standout::app::http::RequestBuilder>,
121
+ ) -> wasmtime::Result<()> {
122
+ self.request_list.remove(&rep.rep());
123
+ Ok(())
124
+ }
125
+
126
+ fn object(
127
+ &mut self,
128
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
129
+ ) -> $v::standout::app::http::Request {
130
+ self.request_list
131
+ .get(&self_.rep())
132
+ .cloned()
133
+ .unwrap_or_default()
134
+ .into()
135
+ }
73
136
  }
74
- let new_id = self.next_request_id;
75
- self.next_request_id += 1;
76
- self.request_list.insert(new_id, request);
77
- Resource::new_own(new_id)
78
- }
137
+ };
138
+ }
79
139
 
80
- #[doc = " Add a body to the request"]
81
- fn body(
82
- &mut self,
83
- self_: Resource<RequestBuilder>,
84
- body: wasmtime::component::__internal::String,
85
- ) -> Resource<RequestBuilder> {
86
- let id = self_.rep();
87
- let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
88
- request.body = body;
89
- let new_id = self.next_request_id;
90
- self.next_request_id += 1;
91
- self.request_list.insert(new_id, request);
92
- Resource::new_own(new_id)
93
- }
140
+ // ============================================================================
141
+ // Macro to implement HTTP type conversions for a version
142
+ // ============================================================================
94
143
 
95
- #[doc = " Send the request"]
96
- fn send(&mut self, self_: Resource<RequestBuilder>) -> Result<Response, RequestError> {
97
- let id = self_.rep();
98
- let request = match self.request_list.get(&id).cloned() {
99
- Some(request) => request,
100
- None => return Err(RequestError::Other("Request not found".to_string())),
101
- };
102
- let client = self.client.lock().unwrap();
103
- let mut request_builder = client.request(request.method.into(), &request.url);
104
- for (key, value) in request.headers {
105
- request_builder = request_builder.header(key, value);
144
+ macro_rules! impl_http_type_conversions {
145
+ ($v:ident) => {
146
+ impl From<$v::standout::app::http::Method> for Method {
147
+ fn from(m: $v::standout::app::http::Method) -> Self {
148
+ use $v::standout::app::http::Method as V;
149
+ match m {
150
+ V::Get => Self::Get,
151
+ V::Post => Self::Post,
152
+ V::Put => Self::Put,
153
+ V::Delete => Self::Delete,
154
+ V::Patch => Self::Patch,
155
+ V::Options => Self::Options,
156
+ V::Head => Self::Head,
157
+ }
158
+ }
106
159
  }
107
- request_builder = request_builder.body(request.body);
108
-
109
- let response = request_builder.send();
110
-
111
- match response {
112
- Ok(resp) => {
113
- let mut response = Response::default();
114
- response.status = resp.status().as_u16();
115
- for (key, value) in resp.headers() {
116
- response.headers.push((
117
- key.as_str().to_string(),
118
- value.to_str().unwrap_or_default().to_string(),
119
- ));
160
+
161
+ impl From<Method> for $v::standout::app::http::Method {
162
+ fn from(m: Method) -> Self {
163
+ match m {
164
+ Method::Get => Self::Get,
165
+ Method::Post => Self::Post,
166
+ Method::Put => Self::Put,
167
+ Method::Delete => Self::Delete,
168
+ Method::Patch => Self::Patch,
169
+ Method::Options => Self::Options,
170
+ Method::Head => Self::Head,
120
171
  }
121
- response.body = resp.text().unwrap_or_default();
122
- Ok(response)
123
172
  }
124
- Err(error) => {
125
- let error_message = format!(
126
- "Request failed to {} {}: {}",
127
- request.method,
128
- request.url,
129
- error
130
- );
131
- let error = RequestError::Other(error_message);
132
- Err(error)
173
+ }
174
+
175
+ impl From<Response> for $v::standout::app::http::Response {
176
+ fn from(r: Response) -> Self {
177
+ Self {
178
+ status: r.status,
179
+ headers: r.headers,
180
+ body: r.body,
181
+ }
133
182
  }
134
183
  }
135
- }
136
184
 
137
- fn drop(&mut self, rep: Resource<RequestBuilder>) -> wasmtime::Result<()> {
138
- let id = rep.rep();
139
- self.request_list.remove(&id);
140
- Ok(())
185
+ impl From<Request> for $v::standout::app::http::Request {
186
+ fn from(r: Request) -> Self {
187
+ Self {
188
+ method: r.method.into(),
189
+ url: r.url,
190
+ headers: r.headers,
191
+ body: r.body,
192
+ }
193
+ }
194
+ }
195
+
196
+ impl From<RequestError> for $v::standout::app::http::RequestError {
197
+ fn from(e: RequestError) -> Self {
198
+ match e {
199
+ RequestError::Other(msg) => Self::Other(msg),
200
+ }
201
+ }
202
+ }
203
+ };
204
+ }
205
+
206
+ // ============================================================================
207
+ // Generate implementations for all supported versions
208
+ // When adding v5, just add:
209
+ // impl_host_request_builder!(v5);
210
+ // impl_http_type_conversions!(v5);
211
+ // ============================================================================
212
+
213
+ impl_host_request_builder!(v3);
214
+ impl_host_request_builder!(v4);
215
+
216
+ impl_http_type_conversions!(v3);
217
+ // Note: v4 doesn't need conversions since we use v4 types as the canonical internal types
218
+
219
+ // ============================================================================
220
+ // Shared request sending logic
221
+ // ============================================================================
222
+
223
+ fn send_request(
224
+ client: &std::sync::Arc<std::sync::Mutex<reqwest::blocking::Client>>,
225
+ request: &Request,
226
+ ) -> Result<Response, RequestError> {
227
+ let client = client.lock().unwrap();
228
+ let mut builder = client.request(request.method.clone().into(), &request.url);
229
+
230
+ for (key, value) in &request.headers {
231
+ builder = builder.header(key, value);
141
232
  }
233
+ builder = builder.body(request.body.clone());
234
+
235
+ match builder.send() {
236
+ Ok(resp) => {
237
+ let headers = resp
238
+ .headers()
239
+ .iter()
240
+ .map(|(k, v)| {
241
+ (
242
+ k.as_str().to_string(),
243
+ v.to_str().unwrap_or_default().to_string(),
244
+ )
245
+ })
246
+ .collect();
142
247
 
143
- fn object(&mut self, self_: Resource<RequestBuilder>) -> Request {
144
- let id = self_.rep();
145
- self.request_list.get(&id).cloned().unwrap_or_default()
248
+ Ok(Response {
249
+ status: resp.status().as_u16(),
250
+ headers,
251
+ body: resp.text().unwrap_or_default(),
252
+ })
253
+ }
254
+ Err(error) => Err(RequestError::Other(format!(
255
+ "Request failed to {} {}: {}",
256
+ request.method, request.url, error
257
+ ))),
146
258
  }
147
259
  }
148
260
 
261
+ // ============================================================================
262
+ // Standard type implementations (used by all versions)
263
+ // ============================================================================
264
+
149
265
  impl From<Method> for ReqwestMethod {
150
266
  fn from(method: Method) -> Self {
151
267
  match method {
152
- Method::Get => ReqwestMethod::GET,
153
- Method::Post => ReqwestMethod::POST,
154
- Method::Put => ReqwestMethod::PUT,
155
- Method::Delete => ReqwestMethod::DELETE,
156
- Method::Patch => ReqwestMethod::PATCH,
157
- Method::Head => ReqwestMethod::HEAD,
158
- Method::Options => ReqwestMethod::OPTIONS,
268
+ Method::Get => Self::GET,
269
+ Method::Post => Self::POST,
270
+ Method::Put => Self::PUT,
271
+ Method::Delete => Self::DELETE,
272
+ Method::Patch => Self::PATCH,
273
+ Method::Head => Self::HEAD,
274
+ Method::Options => Self::OPTIONS,
159
275
  }
160
276
  }
161
277
  }
162
278
 
163
279
  impl Default for Request {
164
280
  fn default() -> Self {
165
- let version = env!("CARGO_PKG_VERSION");
166
- let user_agent = format!("Standout-AppBridge/{version}");
167
- let headers = vec![
168
- ("User-Agent".to_string(), user_agent.into()),
169
- ];
170
-
171
281
  Self {
172
- url: "".to_string(),
282
+ url: String::new(),
173
283
  method: Method::Get,
174
- body: "".to_string(),
175
- headers,
284
+ body: String::new(),
285
+ headers: vec![(
286
+ "User-Agent".to_string(),
287
+ format!("Standout-AppBridge/{}", env!("CARGO_PKG_VERSION")),
288
+ )],
176
289
  }
177
290
  }
178
291
  }
@@ -182,33 +295,38 @@ impl Default for Response {
182
295
  Self {
183
296
  status: 0,
184
297
  headers: Vec::new(),
185
- body: "".to_string(),
298
+ body: String::new(),
186
299
  }
187
300
  }
188
301
  }
189
302
 
190
303
  impl std::fmt::Display for Method {
191
304
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192
- match self {
193
- Method::Get => write!(f, "GET"),
194
- Method::Post => write!(f, "POST"),
195
- Method::Put => write!(f, "PUT"),
196
- Method::Delete => write!(f, "DELETE"),
197
- Method::Patch => write!(f, "PATCH"),
198
- Method::Head => write!(f, "HEAD"),
199
- Method::Options => write!(f, "OPTIONS"),
200
- }
305
+ f.write_str(match self {
306
+ Self::Get => "GET",
307
+ Self::Post => "POST",
308
+ Self::Put => "PUT",
309
+ Self::Delete => "DELETE",
310
+ Self::Patch => "PATCH",
311
+ Self::Head => "HEAD",
312
+ Self::Options => "OPTIONS",
313
+ })
201
314
  }
202
315
  }
203
316
 
317
+ // ============================================================================
318
+ // Tests
319
+ // ============================================================================
204
320
 
205
321
  #[cfg(test)]
206
322
  mod tests {
207
323
  use super::*;
208
- use httpmock::{MockServer, Method::GET};
324
+ use httpmock::{Method::GET, MockServer};
209
325
 
210
326
  #[test]
211
327
  fn sends_request_with_default_user_agent() {
328
+ use v4::standout::app::http::HostRequestBuilder;
329
+
212
330
  let version = env!("CARGO_PKG_VERSION");
213
331
  let user_agent = format!("Standout-AppBridge/{version}");
214
332
 
@@ -0,0 +1,78 @@
1
+ //! Canonical types used internally by the bridge.
2
+ //!
3
+ //! These are version-agnostic and converted to/from versioned WIT types at the boundary.
4
+
5
+ use std::fmt;
6
+
7
+ // ============================================================================
8
+ // Canonical Types - used throughout the codebase, not tied to any version
9
+ // ============================================================================
10
+
11
+ #[derive(Debug, Clone)]
12
+ pub struct AppError {
13
+ pub code: ErrorCode,
14
+ pub message: String,
15
+ }
16
+
17
+ impl fmt::Display for AppError {
18
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19
+ write!(f, "{:?}: {}", self.code, self.message)
20
+ }
21
+ }
22
+
23
+ impl std::error::Error for AppError {}
24
+
25
+ #[derive(Debug, Clone, Copy)]
26
+ pub enum ErrorCode {
27
+ Unauthenticated,
28
+ Forbidden,
29
+ Misconfigured,
30
+ Unsupported,
31
+ RateLimit,
32
+ Timeout,
33
+ Unavailable,
34
+ InternalError,
35
+ MalformedResponse,
36
+ Other,
37
+ CompleteWorkflow,
38
+ CompleteParent,
39
+ }
40
+
41
+ #[derive(Debug, Clone)]
42
+ pub struct Connection {
43
+ pub id: String,
44
+ pub name: String,
45
+ pub serialized_data: String,
46
+ }
47
+
48
+ #[derive(Debug, Clone)]
49
+ pub struct TriggerContext {
50
+ pub trigger_id: String,
51
+ pub connection: Connection,
52
+ pub store: String,
53
+ pub serialized_input: String,
54
+ }
55
+
56
+ #[derive(Debug, Clone)]
57
+ pub struct ActionContext {
58
+ pub action_id: String,
59
+ pub connection: Connection,
60
+ pub serialized_input: String,
61
+ }
62
+
63
+ #[derive(Debug, Clone)]
64
+ pub struct TriggerEvent {
65
+ pub id: String,
66
+ pub serialized_data: String,
67
+ }
68
+
69
+ #[derive(Debug, Clone)]
70
+ pub struct TriggerResponse {
71
+ pub store: String,
72
+ pub events: Vec<TriggerEvent>,
73
+ }
74
+
75
+ #[derive(Debug, Clone)]
76
+ pub struct ActionResponse {
77
+ pub serialized_output: String,
78
+ }
@@ -1,5 +1,5 @@
1
1
  use magnus::{prelude::*, Error, TryConvert, Value};
2
- use crate::component::standout::app::types::ActionContext;
2
+ use crate::types::ActionContext;
3
3
  use super::connection::RConnection;
4
4
 
5
5
  #[magnus::wrap(class = "AppBridge::ActionContext")]
@@ -20,7 +20,7 @@ impl RActionContext {
20
20
  };
21
21
 
22
22
  let inner = ActionContext {
23
- action_id: action_id,
23
+ action_id,
24
24
  connection: wrapped_connection.clone().into(),
25
25
  serialized_input,
26
26
  };
@@ -60,7 +60,7 @@ impl TryConvert for RActionContext {
60
60
  };
61
61
 
62
62
  let inner = ActionContext {
63
- action_id: action_id,
63
+ action_id,
64
64
  connection: wrapped_connection.clone().inner,
65
65
  serialized_input,
66
66
  };
@@ -1,4 +1,4 @@
1
- use crate::component::standout::app::types::ActionResponse;
1
+ use crate::types::ActionResponse;
2
2
 
3
3
  #[magnus::wrap(class = "AppBridge::ActionResponse")]
4
4
  pub struct RActionResponse {
@@ -8,7 +8,7 @@ pub struct RActionResponse {
8
8
  impl RActionResponse {
9
9
  pub fn new(serialized_output: String) -> Self {
10
10
  let inner = ActionResponse {
11
- serialized_output: serialized_output,
11
+ serialized_output,
12
12
  };
13
13
  Self { inner }
14
14
  }
@@ -16,6 +16,11 @@ impl RActionResponse {
16
16
  pub fn serialized_output(&self) -> String {
17
17
  self.inner.serialized_output.clone()
18
18
  }
19
+
20
+ /// Returns a new ActionResponse with the given output
21
+ pub fn with_output(&self, value: String) -> Self {
22
+ Self::new(value)
23
+ }
19
24
  }
20
25
 
21
26
  impl From<ActionResponse> for RActionResponse {
@@ -29,4 +34,3 @@ impl From<RActionResponse> for ActionResponse {
29
34
  value.inner
30
35
  }
31
36
  }
32
-