app_bridge 3.0.0 → 4.1.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,338 @@
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, v4_1};
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, $has_body_bytes: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
+ if let Some(bytes) = self.request_body_bytes.get(&id).cloned() {
38
+ self.request_body_bytes.insert(new_id, bytes);
39
+ }
40
+ Resource::new_own(new_id)
41
+ } else {
42
+ Resource::new_own(id)
43
+ }
44
+ }
44
45
 
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
- }
46
+ fn url(
47
+ &mut self,
48
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
49
+ url: String,
50
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
51
+ let id = self_.rep();
52
+ if let Some(mut request) = self.request_list.get(&id).cloned() {
53
+ request.url = url;
54
+ let new_id = self.next_request_id;
55
+ self.next_request_id += 1;
56
+ self.request_list.insert(new_id, request);
57
+ if let Some(bytes) = self.request_body_bytes.get(&id).cloned() {
58
+ self.request_body_bytes.insert(new_id, bytes);
59
+ }
60
+ Resource::new_own(new_id)
61
+ } else {
62
+ Resource::new_own(id)
63
+ }
64
+ }
65
+
66
+ fn header(
67
+ &mut self,
68
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
69
+ key: String,
70
+ value: String,
71
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
72
+ let id = self_.rep();
73
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
74
+ request.headers.push((key, value));
75
+ let new_id = self.next_request_id;
76
+ self.next_request_id += 1;
77
+ self.request_list.insert(new_id, request);
78
+ if let Some(bytes) = self.request_body_bytes.get(&id).cloned() {
79
+ self.request_body_bytes.insert(new_id, bytes);
80
+ }
81
+ Resource::new_own(new_id)
82
+ }
83
+
84
+ fn headers(
85
+ &mut self,
86
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
87
+ headers: Vec<(String, String)>,
88
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
89
+ let id = self_.rep();
90
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
91
+ request.headers.extend(headers);
92
+ let new_id = self.next_request_id;
93
+ self.next_request_id += 1;
94
+ self.request_list.insert(new_id, request);
95
+ if let Some(bytes) = self.request_body_bytes.get(&id).cloned() {
96
+ self.request_body_bytes.insert(new_id, bytes);
97
+ }
98
+ Resource::new_own(new_id)
99
+ }
100
+
101
+ fn body(
102
+ &mut self,
103
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
104
+ body: String,
105
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
106
+ let id = self_.rep();
107
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
108
+ request.body = body;
109
+ let new_id = self.next_request_id;
110
+ self.next_request_id += 1;
111
+ self.request_list.insert(new_id, request);
112
+ self.request_body_bytes.remove(&new_id);
113
+ Resource::new_own(new_id)
114
+ }
115
+
116
+ fn send(
117
+ &mut self,
118
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
119
+ ) -> Result<$v::standout::app::http::Response, $v::standout::app::http::RequestError> {
120
+ let id = self_.rep();
121
+ match self.request_list.get(&id).cloned() {
122
+ Some(request) => {
123
+ let body_bytes = self.request_body_bytes.get(&id).map(|b| b.as_slice());
124
+ send_request(&self.client, &request, body_bytes)
125
+ .map(Into::into)
126
+ .map_err(Into::into)
127
+ }
128
+ None => Err($v::standout::app::http::RequestError::Other(
129
+ "Request not found".to_string(),
130
+ )),
131
+ }
132
+ }
60
133
 
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));
134
+ fn drop(
135
+ &mut self,
136
+ rep: Resource<$v::standout::app::http::RequestBuilder>,
137
+ ) -> wasmtime::Result<()> {
138
+ self.request_list.remove(&rep.rep());
139
+ self.request_body_bytes.remove(&rep.rep());
140
+ Ok(())
141
+ }
142
+
143
+ fn object(
144
+ &mut self,
145
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
146
+ ) -> $v::standout::app::http::Request {
147
+ self.request_list
148
+ .get(&self_.rep())
149
+ .cloned()
150
+ .unwrap_or_default()
151
+ .into()
152
+ }
153
+
154
+ impl_host_request_builder_body_bytes!($v, $has_body_bytes);
73
155
  }
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
- }
156
+ };
157
+ }
158
+
159
+ macro_rules! impl_host_request_builder_body_bytes {
160
+ ($v:ident, yes) => {
161
+ fn body_bytes(
162
+ &mut self,
163
+ self_: Resource<$v::standout::app::http::RequestBuilder>,
164
+ body: Vec<u8>,
165
+ ) -> Resource<$v::standout::app::http::RequestBuilder> {
166
+ let id = self_.rep();
167
+ let mut request = self.request_list.get(&id).cloned().unwrap_or_default();
168
+ request.body.clear();
169
+ let new_id = self.next_request_id;
170
+ self.next_request_id += 1;
171
+ self.request_list.insert(new_id, request);
172
+ self.request_body_bytes.insert(new_id, body);
173
+ Resource::new_own(new_id)
174
+ }
175
+ };
176
+ ($v:ident, no) => {};
177
+ }
79
178
 
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
- }
94
179
 
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);
180
+ // ============================================================================
181
+ // Macro to implement HTTP type conversions for a version
182
+ // ============================================================================
183
+
184
+ macro_rules! impl_http_type_conversions {
185
+ ($v:ident) => {
186
+ impl From<$v::standout::app::http::Method> for Method {
187
+ fn from(m: $v::standout::app::http::Method) -> Self {
188
+ use $v::standout::app::http::Method as V;
189
+ match m {
190
+ V::Get => Self::Get,
191
+ V::Post => Self::Post,
192
+ V::Put => Self::Put,
193
+ V::Delete => Self::Delete,
194
+ V::Patch => Self::Patch,
195
+ V::Options => Self::Options,
196
+ V::Head => Self::Head,
197
+ }
198
+ }
106
199
  }
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
- ));
200
+
201
+ impl From<Method> for $v::standout::app::http::Method {
202
+ fn from(m: Method) -> Self {
203
+ match m {
204
+ Method::Get => Self::Get,
205
+ Method::Post => Self::Post,
206
+ Method::Put => Self::Put,
207
+ Method::Delete => Self::Delete,
208
+ Method::Patch => Self::Patch,
209
+ Method::Options => Self::Options,
210
+ Method::Head => Self::Head,
120
211
  }
121
- response.body = resp.text().unwrap_or_default();
122
- Ok(response)
123
212
  }
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)
213
+ }
214
+
215
+ impl From<Response> for $v::standout::app::http::Response {
216
+ fn from(r: Response) -> Self {
217
+ Self {
218
+ status: r.status,
219
+ headers: r.headers,
220
+ body: r.body,
221
+ }
133
222
  }
134
223
  }
135
- }
136
224
 
137
- fn drop(&mut self, rep: Resource<RequestBuilder>) -> wasmtime::Result<()> {
138
- let id = rep.rep();
139
- self.request_list.remove(&id);
140
- Ok(())
225
+ impl From<Request> for $v::standout::app::http::Request {
226
+ fn from(r: Request) -> Self {
227
+ Self {
228
+ method: r.method.into(),
229
+ url: r.url,
230
+ headers: r.headers,
231
+ body: r.body,
232
+ }
233
+ }
234
+ }
235
+
236
+ impl From<RequestError> for $v::standout::app::http::RequestError {
237
+ fn from(e: RequestError) -> Self {
238
+ match e {
239
+ RequestError::Other(msg) => Self::Other(msg),
240
+ }
241
+ }
242
+ }
243
+ };
244
+ }
245
+
246
+ // ============================================================================
247
+ // Generate implementations for all supported versions
248
+ // When adding v5, just add:
249
+ // impl_host_request_builder!(v5);
250
+ // impl_http_type_conversions!(v5);
251
+ // ============================================================================
252
+
253
+ impl_host_request_builder!(v3, no);
254
+ impl_host_request_builder!(v4, no);
255
+ impl_host_request_builder!(v4_1, yes);
256
+
257
+ impl_http_type_conversions!(v3);
258
+ // Note: v4 doesn't need conversions since we use v4 types as the canonical internal types
259
+ impl_http_type_conversions!(v4_1);
260
+
261
+ // ============================================================================
262
+ // Shared request sending logic
263
+ // ============================================================================
264
+
265
+ fn send_request(
266
+ client: &std::sync::Arc<std::sync::Mutex<reqwest::blocking::Client>>,
267
+ request: &Request,
268
+ body_bytes: Option<&[u8]>,
269
+ ) -> Result<Response, RequestError> {
270
+ let client = client.lock().unwrap();
271
+ let mut builder = client.request(request.method.clone().into(), &request.url);
272
+
273
+ for (key, value) in &request.headers {
274
+ builder = builder.header(key, value);
141
275
  }
276
+ builder = if let Some(bytes) = body_bytes {
277
+ builder.body(bytes.to_vec())
278
+ } else {
279
+ builder.body(request.body.clone())
280
+ };
142
281
 
143
- fn object(&mut self, self_: Resource<RequestBuilder>) -> Request {
144
- let id = self_.rep();
145
- self.request_list.get(&id).cloned().unwrap_or_default()
282
+ match builder.send() {
283
+ Ok(resp) => {
284
+ let headers = resp
285
+ .headers()
286
+ .iter()
287
+ .map(|(k, v)| {
288
+ (
289
+ k.as_str().to_string(),
290
+ v.to_str().unwrap_or_default().to_string(),
291
+ )
292
+ })
293
+ .collect();
294
+
295
+ Ok(Response {
296
+ status: resp.status().as_u16(),
297
+ headers,
298
+ body: resp.text().unwrap_or_default(),
299
+ })
300
+ }
301
+ Err(error) => Err(RequestError::Other(format!(
302
+ "Request failed to {} {}: {}",
303
+ request.method, request.url, error
304
+ ))),
146
305
  }
147
306
  }
148
307
 
308
+ // ============================================================================
309
+ // Standard type implementations (used by all versions)
310
+ // ============================================================================
311
+
149
312
  impl From<Method> for ReqwestMethod {
150
313
  fn from(method: Method) -> Self {
151
314
  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,
315
+ Method::Get => Self::GET,
316
+ Method::Post => Self::POST,
317
+ Method::Put => Self::PUT,
318
+ Method::Delete => Self::DELETE,
319
+ Method::Patch => Self::PATCH,
320
+ Method::Head => Self::HEAD,
321
+ Method::Options => Self::OPTIONS,
159
322
  }
160
323
  }
161
324
  }
162
325
 
163
326
  impl Default for Request {
164
327
  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
328
  Self {
172
- url: "".to_string(),
329
+ url: String::new(),
173
330
  method: Method::Get,
174
- body: "".to_string(),
175
- headers,
331
+ body: String::new(),
332
+ headers: vec![(
333
+ "User-Agent".to_string(),
334
+ format!("Standout-AppBridge/{}", env!("CARGO_PKG_VERSION")),
335
+ )],
176
336
  }
177
337
  }
178
338
  }
@@ -182,33 +342,38 @@ impl Default for Response {
182
342
  Self {
183
343
  status: 0,
184
344
  headers: Vec::new(),
185
- body: "".to_string(),
345
+ body: String::new(),
186
346
  }
187
347
  }
188
348
  }
189
349
 
190
350
  impl std::fmt::Display for Method {
191
351
  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
- }
352
+ f.write_str(match self {
353
+ Self::Get => "GET",
354
+ Self::Post => "POST",
355
+ Self::Put => "PUT",
356
+ Self::Delete => "DELETE",
357
+ Self::Patch => "PATCH",
358
+ Self::Head => "HEAD",
359
+ Self::Options => "OPTIONS",
360
+ })
201
361
  }
202
362
  }
203
363
 
364
+ // ============================================================================
365
+ // Tests
366
+ // ============================================================================
204
367
 
205
368
  #[cfg(test)]
206
369
  mod tests {
207
370
  use super::*;
208
- use httpmock::{MockServer, Method::GET};
371
+ use httpmock::{Method::GET, Method::POST, MockServer};
209
372
 
210
373
  #[test]
211
374
  fn sends_request_with_default_user_agent() {
375
+ use v4::standout::app::http::HostRequestBuilder;
376
+
212
377
  let version = env!("CARGO_PKG_VERSION");
213
378
  let user_agent = format!("Standout-AppBridge/{version}");
214
379
 
@@ -231,4 +396,30 @@ mod tests {
231
396
  assert_eq!(response.status, 200);
232
397
  mock.assert();
233
398
  }
399
+
400
+ #[test]
401
+ fn sends_binary_body_when_bytes_present() {
402
+ use v4_1::standout::app::http::HostRequestBuilder;
403
+
404
+ let server = MockServer::start();
405
+ let mock = server.mock(|when, then| {
406
+ when.method(POST)
407
+ .path("/upload")
408
+ .body("raw-bytes");
409
+ then.status(200);
410
+ });
411
+ let url = format!("{}/upload", server.base_url());
412
+
413
+ let mut app_state = AppState::default();
414
+ let builder = app_state.new();
415
+ let builder = app_state.method(builder, v4_1::standout::app::http::Method::Post);
416
+ let builder = app_state.url(builder, url);
417
+ let builder = app_state.body(builder, "string-body".to_string());
418
+ let builder = app_state.body_bytes(builder, b"raw-bytes".to_vec());
419
+
420
+ let response = app_state.send(builder).expect("Request failed");
421
+
422
+ assert_eq!(response.status, 200);
423
+ mock.assert();
424
+ }
234
425
  }
@@ -0,0 +1,95 @@
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 AppError {
18
+ pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
19
+ Self {
20
+ code,
21
+ message: message.into(),
22
+ }
23
+ }
24
+ }
25
+
26
+ impl fmt::Display for AppError {
27
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28
+ write!(f, "{:?}: {}", self.code, self.message)
29
+ }
30
+ }
31
+
32
+ impl std::error::Error for AppError {}
33
+
34
+ #[derive(Debug, Clone)]
35
+ pub enum ErrorCode {
36
+ Unauthenticated,
37
+ Forbidden,
38
+ Misconfigured,
39
+ Unsupported,
40
+ RateLimit,
41
+ Timeout,
42
+ Unavailable,
43
+ InternalError,
44
+ MalformedResponse,
45
+ Other,
46
+ RetryWithReference(ReferenceObject),
47
+ CompleteWorkflow,
48
+ CompleteParent,
49
+ }
50
+
51
+ #[derive(Debug, Clone)]
52
+ pub struct ReferenceObject {
53
+ pub reference: String,
54
+ pub status: String,
55
+ }
56
+
57
+ #[derive(Debug, Clone)]
58
+ pub struct Connection {
59
+ pub id: String,
60
+ pub name: String,
61
+ pub serialized_data: String,
62
+ }
63
+
64
+ #[derive(Debug, Clone)]
65
+ pub struct TriggerContext {
66
+ pub trigger_id: String,
67
+ pub connection: Connection,
68
+ pub store: String,
69
+ pub serialized_input: String,
70
+ }
71
+
72
+ #[derive(Debug, Clone)]
73
+ pub struct ActionContext {
74
+ pub action_id: String,
75
+ pub connection: Connection,
76
+ pub serialized_input: String,
77
+ pub reference_object: Option<ReferenceObject>,
78
+ }
79
+
80
+ #[derive(Debug, Clone)]
81
+ pub struct TriggerEvent {
82
+ pub id: String,
83
+ pub serialized_data: String,
84
+ }
85
+
86
+ #[derive(Debug, Clone)]
87
+ pub struct TriggerResponse {
88
+ pub store: String,
89
+ pub events: Vec<TriggerEvent>,
90
+ }
91
+
92
+ #[derive(Debug, Clone)]
93
+ pub struct ActionResponse {
94
+ pub serialized_output: String,
95
+ }