mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-09 03:21:23 +00:00
polling
This commit is contained in:
1 parent
25b2be92c8
commit
d2d56d7220
6 files changed
+98
-1
No files matched your search
@@ -12,6 +12,8 @@ pub enum FalErrorPlus {
|
||||
FalApiKeyError(String),
|
||||
/// The fal account has a billing issue
|
||||
FalBillingError(String),
|
||||
/// Error with an invalid polling URL.
|
||||
InvalidPollingUrl(String),
|
||||
/// Another error we didn't handle.
|
||||
AnyhowError(anyhow::Error),
|
||||
/// URL parse errors.
|
||||
@@ -28,6 +30,7 @@ impl Display for FalErrorPlus {
|
||||
Self::FalError(err) => write!(f, "FalErrorPlus::FalError: {:?}", err),
|
||||
Self::FalApiKeyError(reason) => write!(f, "FalErrorPlus::FalApiKeyError: {}", reason),
|
||||
Self::FalBillingError(reason) => write!(f, "FalErrorPlus::FalBillingError: {}", reason),
|
||||
Self::InvalidPollingUrl(url) => write!(f, "FalErrorPlus::InvalidPollingUrl: {}", url),
|
||||
Self::AnyhowError(err) => write!(f, "FalErrorPlus::AnyhowError: {:?}", err),
|
||||
Self::UrlParseError(err) => write!(f, "FalErrorPlus::UrlParseError: {:?}", err),
|
||||
Self::UnhandledEndpoint(endpoint) => write!(f, "FalErrorPlus::UnhandledEndpoint: {:?}", endpoint),
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod polling;
|
||||
pub mod requests;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
pub mod webhook_payload;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod poll_job_status;
|
||||
@@ -0,0 +1,87 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use log::info;
|
||||
use url::Url;
|
||||
|
||||
const EXPECTED_HOST: &str = "queue.fal.run";
|
||||
|
||||
pub struct PollJobStatusArgs<'a> {
|
||||
pub status_url: &'a str,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
/// Poll the status of a queued FAL job.
|
||||
///
|
||||
/// The `status_url` must point to `queue.fal.run`. Returns the raw response
|
||||
/// body as a string so the caller can deserialize it as needed.
|
||||
pub async fn poll_job_status(args: PollJobStatusArgs<'_>) -> Result<String, FalErrorPlus> {
|
||||
let parsed = Url::parse(args.status_url)?;
|
||||
|
||||
let host = parsed.host_str().unwrap_or("");
|
||||
if host != EXPECTED_HOST {
|
||||
return Err(FalErrorPlus::InvalidPollingUrl(format!(
|
||||
"Expected host '{}' but got '{}' in status URL: {}",
|
||||
EXPECTED_HOST,
|
||||
host,
|
||||
args.status_url,
|
||||
)));
|
||||
}
|
||||
|
||||
info!("Polling FAL job status: {}", args.status_url);
|
||||
|
||||
let response = reqwest::Client::new()
|
||||
.get(args.status_url)
|
||||
.header("Authorization", format!("Key {}", args.api_key.0))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status();
|
||||
let body = response.text().await?;
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(FalErrorPlus::AnyhowError(anyhow::anyhow!(
|
||||
"FAL poll returned HTTP {}: {}",
|
||||
status,
|
||||
body,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_wrong_host() {
|
||||
let api_key = FalApiKey::from_str("test-key");
|
||||
let args = PollJobStatusArgs {
|
||||
status_url: "https://evil.example.com/fal-ai/flux/requests/abc123",
|
||||
api_key: &api_key,
|
||||
};
|
||||
let result = poll_job_status(args).await;
|
||||
assert!(result.is_err());
|
||||
let err = format!("{}", result.unwrap_err());
|
||||
assert!(err.contains("evil.example.com"), "error should mention the bad host: {}", err);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn rejects_invalid_url() {
|
||||
let api_key = FalApiKey::from_str("test-key");
|
||||
let args = PollJobStatusArgs {
|
||||
status_url: "not a url at all",
|
||||
api_key: &api_key,
|
||||
};
|
||||
let result = poll_job_status(args).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_valid_host() {
|
||||
// Just verify URL parsing succeeds — the actual HTTP call will fail without a real key,
|
||||
// but we can check that the host validation passes.
|
||||
let parsed = Url::parse("https://queue.fal.run/fal-ai/flux/requests/019e18d8-8c36-7bc1-aa77-2bc2f70268c6").unwrap();
|
||||
assert_eq!(parsed.host_str(), Some(EXPECTED_HOST));
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,11 @@ impl<Params: Serialize, Response: DeserializeOwned> FalRequest<Params, Response>
|
||||
return Err(error.into());
|
||||
}
|
||||
|
||||
let payload: QueueResponse = response.error_for_status()?.json().await?;
|
||||
let body = response.text().await?;
|
||||
|
||||
println!("Queue response: {}", body);
|
||||
|
||||
let payload: QueueResponse = serde_json::from_str(&body)?;
|
||||
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
Whitespace-only changes.
Reference in new issue
Block a user