diff --git a/Cargo.toml b/Cargo.toml index 9d5de44..bc5f544 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,6 @@ httpmock = "0.7.0" [dev-dependencies] tokio = { version="1.41.0", features = ["full"] } lambda_runtime = "0.13.0" +aws-config = "1.5.10" +aws-sdk-secretsmanager = "1.53.0" +worker = "0.4.2" diff --git a/examples/aws-lambda/main.rs b/examples/aws-lambda/main.rs index c1c66fd..4c5d0cb 100644 --- a/examples/aws-lambda/main.rs +++ b/examples/aws-lambda/main.rs @@ -1,9 +1,16 @@ -use lambda_runtime::{service_fn, LambdaEvent, Error}; +use std::io::ErrorKind; + +use aws_config::BehaviorVersion; +use aws_sdk_secretsmanager::Client as SecretsManagerClient; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use qstash_rs::{client::QstashClient, errors::QstashError}; +use reqwest::StatusCode; use serde::{Deserialize, Serialize}; +use serde_json::json; #[derive(Deserialize)] struct Request { - pub name: String, + pub message_id: String, } #[derive(Serialize)] @@ -13,12 +20,74 @@ struct Response { #[tokio::main] async fn main() -> Result<(), Error> { - let func = service_fn(func_handler); + let secret_name = "QSTASH_APIKEY"; // Replace with your secret name + let region_provider = + aws_config::meta::region::RegionProviderChain::default_provider().or_else("eu-west-1"); + let config = aws_config::defaults(BehaviorVersion::latest()) + .region(region_provider) + .load() + .await; + let client = SecretsManagerClient::new(&config); + + let secret_value = client + .get_secret_value() + .secret_id(secret_name) + .send() + .await? + .secret_string + .unwrap_or_default(); + + let qstash_client = QstashClient::new(secret_value).map_err(|e| Error::from(e.to_string()))?; + + let app = App::new(qstash_client)?; + let func = service_fn(|event: LambdaEvent| app.func_handler(event)); lambda_runtime::run(func).await } -async fn func_handler(event: LambdaEvent) -> Result { - let (request, _context) = event.into_parts(); - let message = format!("Hello, {}!", request.name); - Ok(Response { message }) +struct App { + qstash_client: QstashClient, +} + +impl App { + fn new(qstash_client: QstashClient) -> Result { + Ok(App { qstash_client }) + } + + async fn func_handler(&self, event: LambdaEvent) -> Result { + match self + .qstash_client + .get_message(&event.payload.message_id) + .await + { + Ok(message) => { + return Ok(Response { + message: json!({ "message": message }).to_string(), + }) + } + Err(e) => match e { + QstashError::RequestFailed(err) => match err.status() { + Some(StatusCode::BAD_REQUEST) => Err(Box::new(std::io::Error::new( + ErrorKind::Other, + "Bad request", + ))), + Some(StatusCode::NOT_FOUND) => Err(Box::new(std::io::Error::new( + ErrorKind::NotFound, + "Message not found", + ))), + Some(StatusCode::INTERNAL_SERVER_ERROR) => Err(Box::new(std::io::Error::new( + ErrorKind::Other, + "Internal server error", + ))), + _ => Err(Box::new(std::io::Error::new( + ErrorKind::Other, + format!("Error getting message: {}", err), + ))), + }, + _ => Err(Box::new(std::io::Error::new( + ErrorKind::Other, + format!("Error getting message: {}", e), + ))), + }, + } + } } diff --git a/examples/cloudflare-worker/mod.rs b/examples/cloudflare-worker/mod.rs new file mode 100644 index 0000000..87c6492 --- /dev/null +++ b/examples/cloudflare-worker/mod.rs @@ -0,0 +1,44 @@ +use reqwest::StatusCode; +use qstash_rs::{client::QstashClient, errors::QstashError}; +use serde_json::json; +use worker::*; + +#[event(fetch)] +pub async fn main(req: Request, env: Env, _ctx: Context) -> Result { + let api_key = match env.secret("QSTASH_APIKEY") { + Ok(secret) => secret.to_string(), + Err(e) => return Response::error(&format!("Error getting API key: {}", e), 500), + }; + let qstash_client = match QstashClient::new(api_key) { + Ok(client) => client, + Err(e) => return Response::error(&format!("Error creating Qstash client: {}", e), 500), + }; + + if !matches!(req.method(), Method::Get) { + return Response::error("Method Not Allowed", 405); + } + + let url = &req.url()?; + let message_id = match url.query_pairs().find(|(key, _)| key == "message_id") { + Some((_, value)) => value.to_string(), + None => return Response::error("Query parameter 'message_id' is missing", 400), + }; + + match qstash_client.get_message(&message_id).await { + Ok(message) => { + let json_message = json!({ "message": message }); + Response::from_json(&json_message) + } + Err(e) => match e { + QstashError::RequestFailed(err) => match err.status() { + Some(StatusCode::BAD_REQUEST) => return Response::error("Bad request", 400), + Some(StatusCode::NOT_FOUND) => return Response::error("Message not found", 404), + Some(StatusCode::INTERNAL_SERVER_ERROR) => { + return Response::error("Internal server error", 500) + } + _ => return Response::error(&format!("Error getting message: {}", err), 500), + }, + _ => return Response::error(&format!("Error getting message: {}", e), 500), + }, + } +} \ No newline at end of file diff --git a/examples/cloudlfare-workers/main.rs b/examples/cloudlfare-workers/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/examples/cloudlfare-workers/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/src/client.rs b/src/client.rs index 7124977..0cc323b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,11 +18,11 @@ impl QstashClient { }) } - pub fn new(api_key: &str) -> Result { + pub fn new(api_key: String) -> Result { // Create a default instance let mut qstash_client = QstashClient::default()?; // Create rate limited client with API Key - qstash_client.client = RateLimitedClient::new(api_key.to_string()); + qstash_client.client = RateLimitedClient::new(api_key); Ok(qstash_client) } diff --git a/src/events.rs b/src/events.rs index 8d8d23e..219f46c 100644 --- a/src/events.rs +++ b/src/events.rs @@ -29,5 +29,4 @@ impl QstashClient { } #[cfg(test)] -mod tests { -} +mod tests {} diff --git a/src/llm_types.rs b/src/llm_types.rs index 757b7fd..da95bae 100644 --- a/src/llm_types.rs +++ b/src/llm_types.rs @@ -278,16 +278,15 @@ mod tests { use crate::rate_limited_client::RateLimitedClient; use super::*; - use reqwest::{Method, Url}; use httpmock::prelude::*; + use reqwest::{Method, Url}; #[tokio::test] async fn test_send_request_success() { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(200); }); @@ -308,8 +307,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("RateLimit-Limit", "1000") .header("RateLimit-Reset", "3600"); @@ -335,8 +333,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("Burst-RateLimit-Limit", "100") .header("Burst-RateLimit-Reset", "60"); @@ -362,8 +359,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("x-ratelimit-limit-requests", "100") .header("x-ratelimit-reset-requests", "30") @@ -379,10 +375,13 @@ mod tests { // Assert match result { - Err(QstashError::ChatRateLimitExceeded { reset_requests, reset_tokens }) => { + Err(QstashError::ChatRateLimitExceeded { + reset_requests, + reset_tokens, + }) => { assert_eq!(reset_requests, 30); assert_eq!(reset_tokens, 45); - }, + } _ => panic!("Expected ChatRateLimitExceeded error"), } mock.assert(); @@ -393,8 +392,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429); }); @@ -412,4 +410,4 @@ mod tests { } mock.assert(); } -} \ No newline at end of file +} diff --git a/src/rate_limited_client.rs b/src/rate_limited_client.rs index 4cddf82..e93ca93 100644 --- a/src/rate_limited_client.rs +++ b/src/rate_limited_client.rs @@ -78,16 +78,15 @@ fn parse_reset_time(headers: &HeaderMap, header_name: &str) -> u64 { #[cfg(test)] mod tests { use super::*; - use reqwest::Method; use httpmock::prelude::*; + use reqwest::Method; #[tokio::test] async fn test_send_request_success() { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(200); }); @@ -108,8 +107,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("RateLimit-Limit", "1000") .header("RateLimit-Reset", "3600"); @@ -135,8 +133,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("Burst-RateLimit-Limit", "100") .header("Burst-RateLimit-Reset", "60"); @@ -162,8 +159,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429) .header("x-ratelimit-limit-requests", "100") .header("x-ratelimit-reset-requests", "30") @@ -179,10 +175,13 @@ mod tests { // Assert match result { - Err(QstashError::ChatRateLimitExceeded { reset_requests, reset_tokens }) => { + Err(QstashError::ChatRateLimitExceeded { + reset_requests, + reset_tokens, + }) => { assert_eq!(reset_requests, 30); assert_eq!(reset_tokens, 45); - }, + } _ => panic!("Expected ChatRateLimitExceeded error"), } mock.assert(); @@ -193,8 +192,7 @@ mod tests { // Arrange let server = MockServer::start_async().await; let mock = server.mock(|when, then| { - when.method(GET) - .path("/test"); + when.method(GET).path("/test"); then.status(429); }); @@ -212,4 +210,4 @@ mod tests { } mock.assert(); } -} \ No newline at end of file +}