Skip to content

Commit 09c0fa7

Browse files
authored
feat: jina and serper API checks (#133)
* added `jina` and `serper` checks * tiny typo fixes, update workflows
1 parent e0386e4 commit 09c0fa7

File tree

8 files changed

+243
-31
lines changed

8 files changed

+243
-31
lines changed

Cargo.lock

Lines changed: 24 additions & 25 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ default-members = ["compute"]
77

88
[workspace.package]
99
edition = "2021"
10-
version = "0.2.14"
10+
version = "0.2.15"
1111
license = "Apache-2.0"
1212
readme = "README.md"
1313

workflows/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ ollama-workflows = { git = "https://github.com/andthattoo/ollama-workflows" }
1414
# async stuff
1515
tokio-util.workspace = true
1616
tokio.workspace = true
17-
async-trait.workspace = true
1817

1918
# serialize & deserialize
2019
serde.workspace = true

workflows/src/apis/jina.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use eyre::{eyre, Context, Result};
2+
use reqwest::Client;
3+
use std::env;
4+
5+
/// Makes a request for `example.com`.
6+
const JINA_EXAMPLE_ENDPOINT: &str = "https://r.jina.ai/https://example.com";
7+
const ENV_VAR_NAME: &str = "JINA_API_KEY";
8+
9+
/// Jina-specific configurations.
10+
#[derive(Debug, Clone, Default)]
11+
pub struct JinaConfig {
12+
/// API key, if available.
13+
api_key: Option<String>,
14+
}
15+
16+
impl JinaConfig {
17+
/// Looks at the environment variables for Jina API key.
18+
pub fn new() -> Self {
19+
Self {
20+
api_key: env::var(ENV_VAR_NAME).ok(),
21+
}
22+
}
23+
24+
/// Sets the API key for Jina.
25+
pub fn with_api_key(mut self, api_key: String) -> Self {
26+
self.api_key = Some(api_key);
27+
self
28+
}
29+
30+
/// Checks API KEY, and if it exists tries a dummy request.
31+
/// Fails if the provided API KEY is not authorized enough for the dummy request.
32+
///
33+
/// Equivalent cURL is as follows:
34+
///
35+
/// ```sh
36+
/// curl 'https://r.jina.ai/https://example.com' \
37+
/// -H "Authorization: Bearer jina_key"
38+
/// ```
39+
pub async fn check_optional(&self) -> Result<()> {
40+
// check API key
41+
let Some(api_key) = &self.api_key else {
42+
log::debug!("Jina API key not found, skipping Jina check");
43+
return Ok(());
44+
};
45+
log::info!("Jina API key found, checking Jina service");
46+
47+
// make a dummy request models
48+
let client = Client::new();
49+
let request = client
50+
.get(JINA_EXAMPLE_ENDPOINT)
51+
.header("Authorization", format!("Bearer {}", api_key))
52+
.build()
53+
.wrap_err("failed to build request")?;
54+
55+
let response = client
56+
.execute(request)
57+
.await
58+
.wrap_err("failed to send request")?;
59+
60+
// parse response
61+
if response.status().is_client_error() {
62+
return Err(eyre!("Failed to make Jina request",))
63+
.wrap_err(response.text().await.unwrap_or_default());
64+
}
65+
66+
log::info!("Jina check succesful!");
67+
68+
Ok(())
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use super::*;
75+
76+
#[tokio::test]
77+
#[ignore = "requires Jina API key"]
78+
async fn test_jina_check() {
79+
let _ = dotenvy::dotenv();
80+
assert!(env::var(ENV_VAR_NAME).is_ok());
81+
let res = JinaConfig::new().check_optional().await;
82+
assert!(res.is_ok(), "should pass with api key");
83+
84+
env::set_var(ENV_VAR_NAME, "i-dont-work");
85+
let res = JinaConfig::new().check_optional().await;
86+
assert!(res.is_err(), "should fail with bad api key");
87+
88+
env::remove_var(ENV_VAR_NAME);
89+
let res = JinaConfig::new().check_optional().await;
90+
assert!(res.is_ok(), "should pass without api key");
91+
}
92+
}

workflows/src/apis/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod jina;
2+
pub use jina::JinaConfig;
3+
4+
mod serper;
5+
pub use serper::SerperConfig;

workflows/src/apis/serper.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use eyre::{eyre, Context, Result};
2+
use reqwest::Client;
3+
use std::env;
4+
5+
/// Makes a search request.
6+
const SERPER_EXAMPLE_ENDPOINT: &str = "https://google.serper.dev/search";
7+
const ENV_VAR_NAME: &str = "SERPER_API_KEY";
8+
9+
/// Serper-specific configurations.
10+
#[derive(Debug, Clone, Default)]
11+
pub struct SerperConfig {
12+
/// API key, if available.
13+
api_key: Option<String>,
14+
}
15+
16+
impl SerperConfig {
17+
/// Looks at the environment variables for Serper API key.
18+
pub fn new() -> Self {
19+
Self {
20+
api_key: env::var(ENV_VAR_NAME).ok(),
21+
}
22+
}
23+
24+
/// Sets the API key for Serper.
25+
pub fn with_api_key(mut self, api_key: String) -> Self {
26+
self.api_key = Some(api_key);
27+
self
28+
}
29+
30+
/// Check if Serper API KEY exists and if it does, tries a dummy request.
31+
/// Fails if the provided API KEY is not authorized enough for the dummy request.
32+
///
33+
/// Equivalent cURL is as follows:
34+
///
35+
/// ```sh
36+
/// curl -X POST 'https://google.serper.dev/search' \
37+
/// -H 'X-API-KEY: API_KEY' \
38+
/// -H 'Content-Type: application/json' \
39+
/// -d '{
40+
/// "q": "Your search query here"
41+
/// }'
42+
/// ```
43+
pub async fn check_optional(&self) -> Result<()> {
44+
// check API key
45+
let Some(api_key) = &self.api_key else {
46+
log::debug!("Serper API key not found, skipping Serper check");
47+
return Ok(());
48+
};
49+
log::info!("Serper API key found, checking Serper service");
50+
51+
// make a dummy request
52+
let client = Client::new();
53+
let request = client
54+
.post(SERPER_EXAMPLE_ENDPOINT)
55+
.header("X-API-KEY", api_key)
56+
.header("Content-Type", "application/json")
57+
.body("{\"q\": \"Your search query here\"}")
58+
.build()
59+
.wrap_err("failed to build request")?;
60+
61+
let response = client
62+
.execute(request)
63+
.await
64+
.wrap_err("failed to send request")?;
65+
66+
// parse response
67+
if response.status().is_client_error() {
68+
return Err(eyre!("Failed to make Serper request",))
69+
.wrap_err(response.text().await.unwrap_or_default());
70+
}
71+
72+
log::info!("Serper check succesful!");
73+
74+
Ok(())
75+
}
76+
}
77+
78+
#[cfg(test)]
79+
mod tests {
80+
use super::*;
81+
82+
#[tokio::test]
83+
#[ignore = "requires Serper API key"]
84+
async fn test_serper_check() {
85+
let _ = dotenvy::dotenv();
86+
assert!(env::var(ENV_VAR_NAME).is_ok());
87+
let res = SerperConfig::new().check_optional().await;
88+
assert!(res.is_ok(), "should pass with api key");
89+
90+
env::set_var(ENV_VAR_NAME, "i-dont-work");
91+
let res = SerperConfig::new().check_optional().await;
92+
assert!(res.is_err(), "should fail with bad api key");
93+
94+
env::remove_var(ENV_VAR_NAME);
95+
let res = SerperConfig::new().check_optional().await;
96+
assert!(res.is_ok(), "should pass without api key");
97+
}
98+
}

0 commit comments

Comments
 (0)