(args: &Value, sandbox_dir: &str)
| 3834 | words.sort_by_key(|(i, _)| *i); |
| 3835 | words |
| 3836 | .iter() |
| 3837 | .map(|(_, w)| *w) |
| 3838 | .collect::<Vec<_>>() |
| 3839 | .join(" ") |
| 3840 | } |
| 3841 | |
| 3842 | async fn exec_search_papers(args: &Value) -> Value { |
| 3843 | let query = args["query"].as_str().unwrap_or(""); |
| 3844 | if query.is_empty() { |
| 3845 | return json!({ "ok": false, "error": "query is required" }); |
| 3846 | } |
| 3847 | let limit = args["limit"].as_u64().unwrap_or(10).clamp(1, 25); |
| 3848 | |
| 3849 | let mut filters: Vec<String> = Vec::new(); |
| 3850 | if let Some(y) = args["from_year"].as_u64() { |
| 3851 | filters.push(format!("from_publication_date:{y}-01-01")); |
| 3852 | } |
| 3853 | if let Some(y) = args["to_year"].as_u64() { |
| 3854 | filters.push(format!("to_publication_date:{y}-12-31")); |
| 3855 | } |
| 3856 | if args["open_access_only"].as_bool() == Some(true) { |
| 3857 | filters.push("is_oa:true".to_string()); |
| 3858 | } |
| 3859 | |
| 3860 | let mut url = format!( |
| 3861 | "https://api.openalex.org/works?search={}&per-page={}&select=id,doi,title,display_name,publication_year,cited_by_count,primary_location,open_access,authorships,abstract_inverted_index", |
| 3862 | urlencoding::encode(query), |
| 3863 | limit |
| 3864 | ); |
| 3865 | if !filters.is_empty() { |
| 3866 | url.push_str("&filter="); |
| 3867 | url.push_str(&filters.join(",")); |
| 3868 | } |
| 3869 | if args["sort"].as_str() == Some("citations") { |
| 3870 | url.push_str("&sort=cited_by_count:desc"); |
| 3871 | } |
| 3872 | |
| 3873 | let client = Client::new(); |
| 3874 | let resp = match timeout( |
| 3875 | Duration::from_secs(20), |
| 3876 | client.get(&url).header("User-Agent", "TigrimOS/1.0").send(), |
| 3877 | ) |
| 3878 | .await |
| 3879 | { |
| 3880 | Ok(Ok(r)) => r, |
| 3881 | Ok(Err(e)) => return json!({ "ok": false, "error": format!("OpenAlex request failed: {e}") }), |
| 3882 | Err(_) => return json!({ "ok": false, "error": "OpenAlex request timed out (20s)" }), |
| 3883 | }; |
| 3884 | let status = resp.status(); |
| 3885 | let data: Value = match resp.json().await { |
| 3886 | Ok(d) => d, |
| 3887 | Err(e) => { |
| 3888 | return json!({ "ok": false, "error": format!("OpenAlex returned invalid JSON (HTTP {status}): {e}") }) |
| 3889 | } |
| 3890 | }; |
| 3891 | if !status.is_success() { |
| 3892 | let msg = data["message"].as_str().unwrap_or(""); |
| 3893 | return json!({ "ok": false, "error": format!("OpenAlex HTTP {status}: {msg}") }); |
no test coverage detected