1use papers_openalex::{Author, Domain, Field, Funder, Institution, ListMeta, ListResponse, Publisher, Source, Subfield, Topic, Work};
2use papers_openalex::OpenAlexError;
3use serde::Serialize;
4
5#[derive(Serialize)]
8pub struct SlimListResponse<S: Serialize> {
9 pub meta: ListMeta,
10 pub results: Vec<S>,
11}
12
13pub fn summary_list_result<T, S: Serialize>(
14 result: Result<ListResponse<T>, OpenAlexError>,
15 f: impl Fn(T) -> S,
16) -> Result<SlimListResponse<S>, OpenAlexError> {
17 result.map(|r| SlimListResponse {
18 meta: r.meta,
19 results: r.results.into_iter().map(f).collect(),
20 })
21}
22
23#[derive(Serialize)]
26pub struct WorkSummary {
27 pub id: String,
28 pub title: Option<String>,
29 pub doi: Option<String>,
30 pub publication_year: Option<i32>,
31 pub r#type: Option<String>,
32 pub authors: Vec<String>,
33 pub journal: Option<String>,
34 pub is_oa: Option<bool>,
35 pub oa_url: Option<String>,
36 pub cited_by_count: Option<i64>,
37 pub primary_topic: Option<String>,
38 pub abstract_text: Option<String>,
39}
40
41impl From<Work> for WorkSummary {
42 fn from(w: Work) -> Self {
43 let authors = w
44 .authorships
45 .unwrap_or_default()
46 .into_iter()
47 .filter_map(|a| a.author.and_then(|au| au.display_name))
48 .collect();
49
50 let journal = w
51 .primary_location
52 .as_ref()
53 .and_then(|l| l.source.as_ref())
54 .and_then(|s| s.display_name.clone());
55
56 let is_oa = w.open_access.as_ref().and_then(|oa| oa.is_oa);
57 let oa_url = w.open_access.and_then(|oa| oa.oa_url);
58
59 let primary_topic = w
60 .primary_topic
61 .and_then(|t| t.display_name);
62
63 WorkSummary {
64 id: w.id,
65 title: w.display_name,
66 doi: w.doi,
67 publication_year: w.publication_year,
68 r#type: w.r#type,
69 authors,
70 journal,
71 is_oa,
72 oa_url,
73 cited_by_count: w.cited_by_count,
74 primary_topic,
75 abstract_text: w.abstract_text,
76 }
77 }
78}
79
80#[derive(Serialize)]
83pub struct AuthorSummary {
84 pub id: String,
85 pub display_name: Option<String>,
86 pub orcid: Option<String>,
87 pub works_count: Option<i64>,
88 pub cited_by_count: Option<i64>,
89 pub h_index: Option<i64>,
90 pub last_known_institutions: Vec<String>,
91 pub top_topics: Vec<String>,
92}
93
94impl From<Author> for AuthorSummary {
95 fn from(a: Author) -> Self {
96 let h_index = a.summary_stats.as_ref().and_then(|s| s.h_index);
97
98 let last_known_institutions = a
99 .last_known_institutions
100 .unwrap_or_default()
101 .into_iter()
102 .filter_map(|i| i.display_name)
103 .collect();
104
105 let top_topics = a
106 .topics
107 .unwrap_or_default()
108 .into_iter()
109 .take(3)
110 .filter_map(|t| t.display_name)
111 .collect();
112
113 AuthorSummary {
114 id: a.id,
115 display_name: a.display_name,
116 orcid: a.orcid,
117 works_count: a.works_count,
118 cited_by_count: a.cited_by_count,
119 h_index,
120 last_known_institutions,
121 top_topics,
122 }
123 }
124}
125
126#[derive(Serialize)]
129pub struct SourceSummary {
130 pub id: String,
131 pub display_name: Option<String>,
132 pub issn_l: Option<String>,
133 pub r#type: Option<String>,
134 pub is_oa: Option<bool>,
135 pub is_in_doaj: Option<bool>,
136 pub works_count: Option<i64>,
137 pub cited_by_count: Option<i64>,
138 pub h_index: Option<i64>,
139 pub host_organization_name: Option<String>,
140}
141
142impl From<Source> for SourceSummary {
143 fn from(s: Source) -> Self {
144 let h_index = s.summary_stats.as_ref().and_then(|st| st.h_index);
145
146 SourceSummary {
147 id: s.id,
148 display_name: s.display_name,
149 issn_l: s.issn_l,
150 r#type: s.r#type,
151 is_oa: s.is_oa,
152 is_in_doaj: s.is_in_doaj,
153 works_count: s.works_count,
154 cited_by_count: s.cited_by_count,
155 h_index,
156 host_organization_name: s.host_organization_name,
157 }
158 }
159}
160
161#[derive(Serialize)]
164pub struct InstitutionSummary {
165 pub id: String,
166 pub display_name: Option<String>,
167 pub ror: Option<String>,
168 pub country_code: Option<String>,
169 pub r#type: Option<String>,
170 pub city: Option<String>,
171 pub works_count: Option<i64>,
172 pub cited_by_count: Option<i64>,
173 pub h_index: Option<i64>,
174}
175
176impl From<Institution> for InstitutionSummary {
177 fn from(i: Institution) -> Self {
178 let h_index = i.summary_stats.as_ref().and_then(|s| s.h_index);
179 let city = i.geo.and_then(|g| g.city);
180
181 InstitutionSummary {
182 id: i.id,
183 display_name: i.display_name,
184 ror: i.ror,
185 country_code: i.country_code,
186 r#type: i.r#type,
187 city,
188 works_count: i.works_count,
189 cited_by_count: i.cited_by_count,
190 h_index,
191 }
192 }
193}
194
195#[derive(Serialize)]
198pub struct TopicSummary {
199 pub id: String,
200 pub display_name: Option<String>,
201 pub description: Option<String>,
202 pub subfield: Option<String>,
203 pub field: Option<String>,
204 pub domain: Option<String>,
205 pub works_count: Option<i64>,
206 pub cited_by_count: Option<i64>,
207}
208
209impl From<Topic> for TopicSummary {
210 fn from(t: Topic) -> Self {
211 let subfield = t.subfield.and_then(|s| s.display_name);
212 let field = t.field.and_then(|f| f.display_name);
213 let domain = t.domain.and_then(|d| d.display_name);
214
215 TopicSummary {
216 id: t.id,
217 display_name: t.display_name,
218 description: t.description,
219 subfield,
220 field,
221 domain,
222 works_count: t.works_count,
223 cited_by_count: t.cited_by_count,
224 }
225 }
226}
227
228#[derive(Serialize)]
231pub struct PublisherSummary {
232 pub id: String,
233 pub display_name: Option<String>,
234 pub hierarchy_level: Option<i32>,
235 pub country_codes: Option<Vec<String>>,
236 pub works_count: Option<i64>,
237 pub cited_by_count: Option<i64>,
238}
239
240impl From<Publisher> for PublisherSummary {
241 fn from(p: Publisher) -> Self {
242 PublisherSummary {
243 id: p.id,
244 display_name: p.display_name,
245 hierarchy_level: p.hierarchy_level,
246 country_codes: p.country_codes,
247 works_count: p.works_count,
248 cited_by_count: p.cited_by_count,
249 }
250 }
251}
252
253#[derive(Serialize)]
256pub struct FunderSummary {
257 pub id: String,
258 pub display_name: Option<String>,
259 pub country_code: Option<String>,
260 pub description: Option<String>,
261 pub awards_count: Option<i64>,
262 pub works_count: Option<i64>,
263 pub cited_by_count: Option<i64>,
264}
265
266impl From<Funder> for FunderSummary {
267 fn from(f: Funder) -> Self {
268 FunderSummary {
269 id: f.id,
270 display_name: f.display_name,
271 country_code: f.country_code,
272 description: f.description,
273 awards_count: f.awards_count,
274 works_count: f.works_count,
275 cited_by_count: f.cited_by_count,
276 }
277 }
278}
279
280#[derive(Serialize)]
283pub struct DomainSummary {
284 pub id: String,
285 pub display_name: Option<String>,
286 pub description: Option<String>,
287 pub fields: Vec<String>,
288 pub works_count: Option<i64>,
289 pub cited_by_count: Option<i64>,
290}
291
292impl From<Domain> for DomainSummary {
293 fn from(d: Domain) -> Self {
294 let fields = d
295 .fields
296 .unwrap_or_default()
297 .into_iter()
298 .filter_map(|f| f.display_name)
299 .collect();
300
301 DomainSummary {
302 id: d.id,
303 display_name: d.display_name,
304 description: d.description,
305 fields,
306 works_count: d.works_count,
307 cited_by_count: d.cited_by_count,
308 }
309 }
310}
311
312#[derive(Serialize)]
315pub struct FieldSummary {
316 pub id: String,
317 pub display_name: Option<String>,
318 pub description: Option<String>,
319 pub domain: Option<String>,
320 pub subfield_count: usize,
321 pub works_count: Option<i64>,
322 pub cited_by_count: Option<i64>,
323}
324
325impl From<Field> for FieldSummary {
326 fn from(f: Field) -> Self {
327 let domain = f.domain.and_then(|d| d.display_name);
328 let subfield_count = f.subfields.as_ref().map_or(0, |s| s.len());
329
330 FieldSummary {
331 id: f.id,
332 display_name: f.display_name,
333 description: f.description,
334 domain,
335 subfield_count,
336 works_count: f.works_count,
337 cited_by_count: f.cited_by_count,
338 }
339 }
340}
341
342#[derive(Serialize)]
345pub struct SubfieldSummary {
346 pub id: String,
347 pub display_name: Option<String>,
348 pub description: Option<String>,
349 pub field: Option<String>,
350 pub domain: Option<String>,
351 pub works_count: Option<i64>,
352 pub cited_by_count: Option<i64>,
353}
354
355impl From<Subfield> for SubfieldSummary {
356 fn from(s: Subfield) -> Self {
357 let field = s.field.and_then(|f| f.display_name);
358 let domain = s.domain.and_then(|d| d.display_name);
359
360 SubfieldSummary {
361 id: s.id,
362 display_name: s.display_name,
363 description: s.description,
364 field,
365 domain,
366 works_count: s.works_count,
367 cited_by_count: s.cited_by_count,
368 }
369 }
370}