extract payloads

This commit is contained in:
Brandon Thomas committed 2026-04-11 20:37:11 -04:00
1 parent b8895fbae9
commit eb8301ab43
9 files changed
+184 -12

No files matched your search

@@ -1,3 +1,4 @@
pub mod parse_webhook_inner_payload;
pub mod parse_webhook_payload;
pub mod payload;
pub mod payload;
pub mod success_case_extractors;
@@ -1,8 +1,9 @@
use serde_json::Value;
use crate::webhook_api::payload::webhook_error_type::WebhookErrorType;
use crate::webhook_api::payload::webhook_inner_payload::{ErrorData, ExtractedContents, PayloadErrorData, SuccessData, WebhookInnerPayload};
use crate::webhook_api::payload::webhook_inner_payload::{ErrorData, PayloadErrorData, SuccessData, WebhookInnerPayload};
use crate::webhook_api::payload::webhook_payload::{WebhookPayload, WebhookStatus};
use crate::webhook_api::success_case_extractors::extract_contents_from_payload;
/// Parse the inner payload of a FAL webhook into one of three cases.
///
@@ -28,18 +29,12 @@ pub fn parse_webhook_inner_payload(webhook: &WebhookPayload) -> WebhookInnerPayl
});
}
}
let value = webhook.payload
.clone()
.unwrap_or(Value::Null);
let mut extracted_contents = ExtractedContents {
image: None,
images: None,
video: None,
model_glb: None,
model_mesh: None,
};
let extracted_contents = extract_contents_from_payload(&value);
WebhookInnerPayload::Success(SuccessData {
payload: value,
@@ -28,7 +28,7 @@ pub struct SuccessData {
/// If there are any extracted sub-payload contents, such as "images" or "video",
/// then they are included here. This may not be fully inclusive of future
/// payload types.
pub extracted_contents: ExtractedContents,
pub extracted_contents: Option<ExtractedContents>,
}
#[derive(Debug)]
@@ -0,0 +1,110 @@
use serde_json::{Map, Value};
/// Extract the `image` key from a webhook success payload object.
/// Returns a clone of the value if the key exists.
pub fn extract_image(obj: &Map<String, Value>) -> Option<Value> {
obj.get("image").cloned()
}
#[cfg(test)]
mod tests {
use crate::webhook_api::parse_webhook_inner_payload::parse_webhook_inner_payload;
use crate::webhook_api::payload::webhook_inner_payload::WebhookInnerPayload;
use crate::webhook_api::payload::webhook_payload::{WebhookPayload, WebhookStatus};
fn load_test_webhook(filename: &str) -> WebhookPayload {
let path = format!("test_data/webhooks/{}", filename);
let json = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e));
serde_json::from_str(&json)
.unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e))
}
#[test]
fn images_payload_populates_images_field() {
let webhook = load_test_webhook("success/images_payload_1.json");
let result = parse_webhook_inner_payload(&webhook);
let WebhookInnerPayload::Success(data) = result else {
panic!("Expected Success, got {:?}", result);
};
// The raw payload should have "images".
assert!(data.payload.get("images").is_some());
// extracted_contents should be populated.
let contents = data.extracted_contents
.expect("extracted_contents should be Some for an images payload");
// The "images" field should be an array with 2 items.
let images = contents.images.expect("images should be Some");
let images_arr = images.as_array().expect("images should be an array");
assert_eq!(images_arr.len(), 2);
// First image should have a url.
let first = &images_arr[0];
assert_eq!(
first.get("url").and_then(|v| v.as_str()),
Some("https://v3b.fal.media/files/b/01234567/name1.png"),
);
assert_eq!(
first.get("content_type").and_then(|v| v.as_str()),
Some("image/png"),
);
// Second image.
let second = &images_arr[1];
assert_eq!(
second.get("url").and_then(|v| v.as_str()),
Some("https://v3b.fal.media/files/b/01234567/name2.png"),
);
// The "image" (singular) field should be None since the payload has "images" not "image".
assert!(contents.image.is_none());
// The video/model fields should be None.
assert!(contents.video.is_none());
assert!(contents.model_glb.is_none());
assert!(contents.model_mesh.is_none());
}
#[test]
fn payload_without_known_keys_has_none_extracted_contents() {
let webhook = WebhookPayload {
request_id: "test-no-keys".to_string(),
gateway_request_id: "test-no-keys".to_string(),
status: WebhookStatus::Ok,
error: None,
payload: Some(serde_json::json!({"some_other_key": "value"})),
payload_error: None,
};
let result = parse_webhook_inner_payload(&webhook);
let WebhookInnerPayload::Success(data) = result else {
panic!("Expected Success, got {:?}", result);
};
assert!(data.extracted_contents.is_none());
}
#[test]
fn null_payload_has_none_extracted_contents() {
let webhook = WebhookPayload {
request_id: "test-null".to_string(),
gateway_request_id: "test-null".to_string(),
status: WebhookStatus::Ok,
error: None,
payload: None,
payload_error: None,
};
let result = parse_webhook_inner_payload(&webhook);
let WebhookInnerPayload::Success(data) = result else {
panic!("Expected Success, got {:?}", result);
};
assert!(data.extracted_contents.is_none());
}
}
@@ -0,0 +1,7 @@
use serde_json::{Map, Value};
/// Extract the `images` key from a webhook success payload object.
/// Returns a clone of the value if the key exists.
pub fn extract_images(obj: &Map<String, Value>) -> Option<Value> {
obj.get("images").cloned()
}
@@ -0,0 +1,7 @@
use serde_json::{Map, Value};
/// Extract the `model_glb` key from a webhook success payload object.
/// Returns a clone of the value if the key exists.
pub fn extract_model_glb(obj: &Map<String, Value>) -> Option<Value> {
obj.get("model_glb").cloned()
}
@@ -0,0 +1,7 @@
use serde_json::{Map, Value};
/// Extract the `model_mesh` key from a webhook success payload object.
/// Returns a clone of the value if the key exists.
pub fn extract_model_mesh(obj: &Map<String, Value>) -> Option<Value> {
obj.get("model_mesh").cloned()
}
@@ -0,0 +1,7 @@
use serde_json::{Map, Value};
/// Extract the `video` key from a webhook success payload object.
/// Returns a clone of the value if the key exists.
pub fn extract_video(obj: &Map<String, Value>) -> Option<Value> {
obj.get("video").cloned()
}
@@ -0,0 +1,38 @@
pub mod extract_image_payload;
pub mod extract_images_payload;
pub mod extract_video_payload;
pub mod extract_model_glb_payload;
pub mod extract_model_mesh_payload;
use serde_json::Value;
use crate::webhook_api::payload::webhook_inner_payload::ExtractedContents;
/// Try to extract known content keys from a success payload.
///
/// Checks the payload (as a JSON object) for any of: `image`, `images`,
/// `video`, `model_glb`, `model_mesh`. If at least one is found, returns
/// `Some(ExtractedContents)` with copies of the relevant values. Multiple
/// keys can be populated simultaneously. Returns `None` if the payload is
/// not an object or none of the known keys are present.
pub fn extract_contents_from_payload(payload: &Value) -> Option<ExtractedContents> {
let obj = payload.as_object()?;
let image = extract_image_payload::extract_image(obj);
let images = extract_images_payload::extract_images(obj);
let video = extract_video_payload::extract_video(obj);
let model_glb = extract_model_glb_payload::extract_model_glb(obj);
let model_mesh = extract_model_mesh_payload::extract_model_mesh(obj);
if image.is_none() && images.is_none() && video.is_none() && model_glb.is_none() && model_mesh.is_none() {
return None;
}
Some(ExtractedContents {
image,
images,
video,
model_glb,
model_mesh,
})
}