Skip to content

Commit cd5e1ba

Browse files
authored
Bridge: receivers with no transformation set body as payload field (#985)
Follow-on from #982. For cases where the webhook receiver has not transformation set, we assume the body is JSON. Formerly, that JSON body would fail to validate if it didn't happen to have a root key named "payload" to align with our expectations. This is a bad default. Instead, we take the body, parse it as JSON, then shove it in the payload field.
2 parents c011f63 + 79f74cd commit cd5e1ba

File tree

3 files changed

+29
-43
lines changed

3 files changed

+29
-43
lines changed

bridge/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Senders should produce JSON following an expected shape:
149149

150150
For detail on the `message` field, see: <https://api.svix.com/docs#tag/Message/operation/v1.message.create>
151151

152-
Receivers can accept arbitrary body data but their outputs require a JSON object with a `payload` field representing the
152+
Receivers can accept arbitrary body data but the outputs require a JSON object with a `payload` field representing the
153153
message to publish.
154154

155155
```json
@@ -158,8 +158,12 @@ message to publish.
158158
}
159159
```
160160

161+
When a transformation is _not configured_, the default behavior is to parse the request body as JSON and set this value
162+
as the `payload` field before forwarding to the output.
163+
161164
By configuring a transformation, you should be able to consume a variety of `POST` bodies and
162-
produce a valid output.
165+
produce a valid output, but just remember to make sure the _return value_ has your data attached to the `payload` field.
166+
163167

164168
See the example configs for how to configure each input and output in more detail:
165169
- [senders](./svix-bridge.example.senders.yaml)

bridge/svix-bridge/src/webhook_receiver/mod.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,15 @@ async fn parse_payload(
134134
};
135135
transform(input, xform.source().clone(), transformer_tx).await
136136
}
137-
// Keep the original payload as-is if there's no transformation specified.
137+
// Keep the original payload as-is if there's no transformation specified, but stuff the
138+
// whole thing into the payload field.
138139
// The as_json() only gets us to `Value`, so we also need a `from_value` call to marshal
139140
// into a [`ForwardRequest`] type.
140-
None => serde_json::from_value(payload.as_json().map_err(|_| {
141-
tracing::error!("Unable to parse request body as json");
142-
http::StatusCode::BAD_REQUEST
143-
})?)
144-
.map_err(|e| {
145-
tracing::error!("Error forwarding request: {}", e);
146-
http::StatusCode::INTERNAL_SERVER_ERROR
141+
None => Ok(ForwardRequest {
142+
payload: payload.as_json().map_err(|_| {
143+
tracing::error!("Unable to parse request body as json");
144+
http::StatusCode::BAD_REQUEST
145+
})?,
147146
}),
148147
}
149148
}

bridge/svix-bridge/src/webhook_receiver/tests.rs

Lines changed: 16 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,7 @@ async fn test_forwarding_no_verification() {
6464
.uri("/webhook/a")
6565
.method("POST")
6666
.header("content-type", "application/json")
67-
.body(
68-
serde_json::to_vec(&json!({"payload": {"a": true}}))
69-
.unwrap()
70-
.into(),
71-
)
67+
.body(serde_json::to_vec(&json!({"a": true})).unwrap().into())
7268
.unwrap(),
7369
)
7470
.await
@@ -112,11 +108,7 @@ async fn test_forwarding_multiple_receivers() {
112108
.uri("/webhook/a")
113109
.method("POST")
114110
.header("content-type", "application/json")
115-
.body(
116-
serde_json::to_vec(&json!({"payload": {"a": true}}))
117-
.unwrap()
118-
.into(),
119-
)
111+
.body(serde_json::to_vec(&json!({"a": true})).unwrap().into())
120112
.unwrap();
121113

122114
let response = ServiceExt::<Request<Body>>::ready(&mut app)
@@ -134,11 +126,7 @@ async fn test_forwarding_multiple_receivers() {
134126
.uri("/webhook/b")
135127
.method("POST")
136128
.header("content-type", "application/json")
137-
.body(
138-
serde_json::to_vec(&json!({"payload": {"b": true}}))
139-
.unwrap()
140-
.into(),
141-
)
129+
.body(serde_json::to_vec(&json!({"b": true})).unwrap().into())
142130
.unwrap();
143131

144132
let response = ServiceExt::<Request<Body>>::ready(&mut app)
@@ -163,15 +151,18 @@ async fn test_transformation_json() {
163151
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<TransformerJob>();
164152
let _handle = tokio::spawn(async move {
165153
while let Some(x) = rx.recv().await {
166-
let mut out = match x.input {
154+
let mut input = match x.input {
167155
TransformerInput::JSON(input) => input.as_object().unwrap().clone(),
168156
_ => unreachable!(),
169157
};
170-
out["payload"]
171-
.as_object_mut()
172-
.unwrap()
173-
.insert("__TRANSFORMED__".into(), json!(true));
174-
x.callback_tx.send(Ok(TransformerOutput::Object(out))).ok();
158+
input.insert("__TRANSFORMED__".into(), json!(true));
159+
let out = json!({ "payload": input });
160+
161+
x.callback_tx
162+
.send(Ok(TransformerOutput::Object(
163+
out.as_object().unwrap().clone(),
164+
)))
165+
.ok();
175166
}
176167
});
177168

@@ -184,7 +175,7 @@ async fn test_transformation_json() {
184175
verifier: NoVerifier.into(),
185176
output: Arc::new(Box::new(a_output)),
186177
transformation: Some(
187-
"handler = (x) => ({ payload: {__TRANSFORMED__: true, ...x.payload }})".into(),
178+
"handler = (x) => ({ payload: {__TRANSFORMED__: true, ...x }})".into(),
188179
),
189180
},
190181
),
@@ -207,11 +198,7 @@ async fn test_transformation_json() {
207198
.uri("/webhook/transformed")
208199
.method("POST")
209200
.header("content-type", "application/json")
210-
.body(
211-
serde_json::to_vec(&json!({"payload": {"a": true}}))
212-
.unwrap()
213-
.into(),
214-
)
201+
.body(serde_json::to_vec(&json!({"a": true})).unwrap().into())
215202
.unwrap();
216203

217204
let response = ServiceExt::<Request<Body>>::ready(&mut app)
@@ -233,11 +220,7 @@ async fn test_transformation_json() {
233220
.uri("/webhook/as-is")
234221
.method("POST")
235222
.header("content-type", "application/json")
236-
.body(
237-
serde_json::to_vec(&json!({"payload": {"b": true}}))
238-
.unwrap()
239-
.into(),
240-
)
223+
.body(serde_json::to_vec(&json!({"b": true})).unwrap().into())
241224
.unwrap();
242225

243226
let response = ServiceExt::<Request<Body>>::ready(&mut app)
@@ -371,7 +354,7 @@ async fn test_forwarding_svix_verification_match() {
371354

372355
let webhook = Arc::new(Webhook::new("whsec_C2FVsBQIhrscChlQIMV+b5sSYspob7oD").unwrap());
373356

374-
let payload = json!({"payload": {"a": true}});
357+
let payload = json!({"a": true});
375358
let payload_bytes = serde_json::to_vec(&payload).unwrap();
376359
let timestamp = chrono::Utc::now().timestamp();
377360
let signature = webhook

0 commit comments

Comments
 (0)