pubmed_parser/europe_pmc/
search.rs1use serde::{Deserialize, Serialize};
4
5use crate::error::Result;
6
7use super::models::EuropePmcResult;
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
11#[serde(from = "RawSearchResponse")]
12pub struct EuropePmcSearchResponse {
13 pub hit_count: u64,
15 pub next_cursor_mark: Option<String>,
18 pub results: Vec<EuropePmcResult>,
20}
21
22#[derive(Deserialize)]
23#[serde(rename_all = "camelCase")]
24struct RawSearchResponse {
25 #[serde(default)]
26 hit_count: u64,
27 #[serde(default)]
28 next_cursor_mark: Option<String>,
29 #[serde(default)]
30 result_list: RawResultList,
31}
32
33#[derive(Deserialize, Default)]
34struct RawResultList {
35 #[serde(default)]
36 result: Vec<EuropePmcResult>,
37}
38
39impl From<RawSearchResponse> for EuropePmcSearchResponse {
40 fn from(raw: RawSearchResponse) -> Self {
41 Self {
42 hit_count: raw.hit_count,
43 next_cursor_mark: raw.next_cursor_mark,
44 results: raw.result_list.result,
45 }
46 }
47}
48
49pub fn parse_search_response(json: &str) -> Result<EuropePmcSearchResponse> {
51 Ok(serde_json::from_str(json)?)
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn test_parse_search_lite() {
60 let json = r#"{
61 "hitCount": 2,
62 "nextCursorMark": "AoJ123",
63 "resultList": {
64 "result": [
65 {
66 "id": "33515491",
67 "source": "MED",
68 "pmid": "33515491",
69 "pmcid": "PMC7894017",
70 "doi": "10.1000/x",
71 "title": "First",
72 "authorString": "Smith J.",
73 "journalTitle": "Nature",
74 "pubYear": "2021",
75 "isOpenAccess": "Y"
76 },
77 {
78 "id": "PPR12345",
79 "source": "PPR",
80 "title": "A preprint",
81 "pubYear": "2022"
82 }
83 ]
84 }
85 }"#;
86
87 let resp = parse_search_response(json).unwrap();
88 assert_eq!(resp.hit_count, 2);
89 assert_eq!(resp.next_cursor_mark.as_deref(), Some("AoJ123"));
90 assert_eq!(resp.results.len(), 2);
91 assert_eq!(resp.results[0].pmcid.as_deref(), Some("PMC7894017"));
92 assert_eq!(resp.results[0].pub_year.as_deref(), Some("2021"));
93 assert_eq!(resp.results[1].source, "PPR");
94 }
95
96 #[test]
97 fn test_parse_search_core_keeps_extra_fields() {
98 let json = r#"{
99 "hitCount": 1,
100 "resultList": {
101 "result": [
102 {
103 "id": "1",
104 "source": "MED",
105 "title": "Has extra",
106 "citedByCount": 42,
107 "inEPMC": "Y"
108 }
109 ]
110 }
111 }"#;
112
113 let resp = parse_search_response(json).unwrap();
114 let result = &resp.results[0];
115 assert_eq!(
116 result.extra.get("citedByCount").and_then(|v| v.as_i64()),
117 Some(42)
118 );
119 assert_eq!(
120 result.extra.get("inEPMC").and_then(|v| v.as_str()),
121 Some("Y")
122 );
123 }
124
125 #[test]
126 fn test_parse_empty_results() {
127 let json = r#"{"hitCount": 0, "resultList": {"result": []}}"#;
128 let resp = parse_search_response(json).unwrap();
129 assert_eq!(resp.hit_count, 0);
130 assert!(resp.results.is_empty());
131 assert!(resp.next_cursor_mark.is_none());
132 }
133}