enqueuing to webhook works

This commit is contained in:
Brandon Thomas committed 2025-06-02 14:25:02 -04:00
1 parent d730ffb7bd
commit 3f85ef0884
12 files changed
+151 -7

No files matched your search

Generated
+7
View File
@@ -3567,6 +3567,7 @@ dependencies = [
"fal-derive",
"futures 0.3.31",
"image",
"percent-encoding-rfc3986",
"reqwest 0.12.15",
"serde",
"serde_json",
@@ -7243,6 +7244,12 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "percent-encoding-rfc3986"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3637c05577168127568a64e9dc5a6887da720efef07b3d9472d45f63ab191166"
[[package]]
name = "phf"
version = "0.8.0"
@@ -15,6 +15,8 @@ pub enum FalErrorPlus {
UrlParseError(url::ParseError),
/// An endpoint we don't support yet.
UnhandledEndpoint(String),
/// Error from the `reqwest` crate.
ReqwestError(reqwest::Error),
}
impl Display for FalErrorPlus {
@@ -25,6 +27,7 @@ impl Display for FalErrorPlus {
Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err),
Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err),
Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint),
Self::ReqwestError(err) => write!(f, "FalErrorPlus::ReqwestError: {:?}", err),
}
}
}
@@ -48,3 +51,9 @@ impl From<url::ParseError> for FalErrorPlus {
FalErrorPlus::UrlParseError(err)
}
}
impl From<reqwest::Error> for FalErrorPlus {
fn from(err: reqwest::Error) -> Self {
FalErrorPlus::ReqwestError(err)
}
}
@@ -1,4 +1,5 @@
pub mod enqueue_hunyuan2_image_to_3d;
pub mod image_gen;
pub mod remove_background_rembg;
pub mod video_gen;
pub mod video_gen;
pub mod webhook;
@@ -4,6 +4,7 @@ use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput};
use fal::prelude::Status;
use fal_client::file_to_base64_url::file_to_base64_url;
use futures::StreamExt;
use std::path::Path;
use url::Url;
@@ -2,8 +2,9 @@ use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput};
use fal::webhook::WebhookResponse;
pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Result<(), FalErrorPlus> {
pub async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Result<WebhookResponse, FalErrorPlus> {
let request = RemoveBackgroundInput {
image_url,
@@ -12,9 +13,8 @@ pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Re
};
let result = rembg(request)
.with_client()
.with_api_key(&api_key.0)
.queue()
.queue_webhook("https://api.storyteller.ai/webhook")
.await;
let result = match result {
@@ -22,7 +22,29 @@ pub (super) async fn rembg_request(image_url: String, api_key: &FalApiKey) -> Re
Err(err) => return Err(classify_fal_error(err)),
};
println!("{:?}", result.payload);
println!("{:?}", result);
Ok(())
Ok(result)
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::remove_background_rembg::rembg_request::rembg_request;
use std::fs::read_to_string;
#[tokio::test]
#[ignore] // NB: Manually test, don't run in CI!
async fn test_rembg_request() -> anyhow::Result<()> {
// XXX: Don't commit secrets!
let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&api_key);
let response = rembg_request("test_data/juno.jpg".into(), &api_key).await?;
assert_eq!(1, 2);
Ok(())
}
}
@@ -0,0 +1 @@
pub mod remove_background_rembg_webhook;
@@ -0,0 +1,62 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::imageutils::rembg::{rembg, RemoveBackgroundInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct RemoveBackgroundRembgWebhookArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub api_key: &'a FalApiKey
}
pub async fn remove_background_rembg_webhook<U: IntoUrl, V: IntoUrl>(
args: RemoveBackgroundRembgWebhookArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let image_url = args.image_url.into_url()?;
let request = RemoveBackgroundInput {
image_url: image_url.to_string(),
crop_to_bbox: None,
sync_mode: None
};
let result = rembg(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::image::remove_background_rembg_webhook::remove_background_rembg_webhook;
use crate::requests::webhook::image::remove_background_rembg_webhook::RemoveBackgroundRembgWebhookArgs;
use std::fs::read_to_string;
#[tokio::test]
#[ignore] // NB: Manually test, don't run in CI!
async fn test_rembg_request() -> anyhow::Result<()> {
// XXX: Don't commit secrets!
let api_key = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
let api_key = FalApiKey::from_str(&api_key);
let args = RemoveBackgroundRembgWebhookArgs {
image_url: "https://cdn-2.fakeyou.com/media/b/2/p/b/0/b2pb09bcbxdqjzgbnsapvfs1t897wdnx/image_b2pb09bcbxdqjzgbnsapvfs1t897wdnx.png",
webhook_url: "https://api.storyteller.ai/webhook",
api_key: &api_key,
};
let response = remove_background_rembg_webhook(args).await?;
println!("{:?}", response);
assert_eq!(1, 2);
Ok(())
}
}
@@ -0,0 +1 @@
pub mod image;
+1
View File
@@ -33,6 +33,7 @@ all-features = true
eventsource-stream = "0.2"
smart-default = "0.7"
thiserror = "1"
percent-encoding-rfc3986 = "0.1.3"
[dependencies.base64]
version = "0.22"
+1
View File
@@ -7,6 +7,7 @@ pub mod endpoints;
pub mod image;
pub mod queue;
pub mod request;
pub mod webhook;
use eventsource_stream::EventStreamError;
pub use fal_derive::endpoint;
+30 -1
View File
@@ -1,11 +1,13 @@
use std::marker::PhantomData;
use percent_encoding_rfc3986::{utf8_percent_encode, NON_ALPHANUMERIC};
use reqwest::IntoUrl;
use serde::{de::DeserializeOwned, Serialize};
use crate::{
queue::{Queue, QueueResponse},
FalError,
};
use crate::webhook::WebhookResponse;
/// A request to the FAL API
///
@@ -129,4 +131,31 @@ impl<Params: Serialize, Response: DeserializeOwned> FalRequest<Params, Response>
Ok(Queue::new(self.client, self.endpoint, key, payload))
}
pub async fn queue_webhook<U: IntoUrl>(self, url: U) -> Result<WebhookResponse, FalError> {
let key = self
.api_key
.expect("No fal API key provided, and FAL_API_KEY environment variable is not set");
let url_encoded = url.into_url()?;
let url_encoded = utf8_percent_encode(url_encoded.as_str(), NON_ALPHANUMERIC).to_string();
let response = self
.client
.post(format!("https://queue.fal.run/{}?fal_webhook={}", self.endpoint, url_encoded))
.json(&self.params)
.header("Authorization", format!("Key {}", &key))
.header("Content-Type", "application/json")
.send()
.await?;
if response.status() != 200 {
let error = response.text().await?;
return Err(error.into());
}
let payload: WebhookResponse = response.error_for_status()?.json().await?;
Ok(payload)
}
}
+9
View File
@@ -0,0 +1,9 @@
use crate::prelude::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct WebhookResponse {
pub status: Option<String>,
pub request_id: Option<String>,
pub gateway_request_id: Option<String>,
}