crossref/lib.rs
1//! This crate provides a client for interacting with the crossref-api
2//!
3//! [Crossref API docs](https://github.com/CrossRef/rest-api-doc)
4//! `Crossref` - Crossref search API. The `Crossref` crate provides methods matching Crossref API routes:
5
6//! * `works` - `/works` route
7//! * `members` - `/members` route
8//! * `prefixes` - `/prefixes` route
9//! * `funders` - `/funders` route
10//! * `journals` - `/journals` route
11//! * `types` - `/types` route
12//! * `agency` - `/works/{doi}/agency` get DOI minting agency
13//!
14//! ## Usage
15
16//! ### Create a `Crossref` client:
17
18//! ```edition2018
19//! # use crossref::Crossref;
20//! # fn run() -> Result<(), crossref::Error> {
21//! let client = Crossref::builder().build()?;
22//! # Ok(())
23//! # }
24//! ```
25//!
26//! If you have an [Authorization token for Crossref's Plus service](https://github.com/CrossRef/rest-api-doc#authorization-token-for-plus-service):
27//!
28//! ```edition2018
29//! # use crossref::Crossref;
30//! # fn run() -> Result<(), crossref::Error> {
31//! let client = Crossref::builder()
32//! .token("token")
33//! .build()?;
34//! # Ok(())
35//! # }
36//! ```
37//!
38//! Encouraged to use the **The Polite Pool**:
39//!
40//! [Good manners = more reliable service](https://github.com/CrossRef/rest-api-doc#good-manners--more-reliable-service)
41//!
42//! To get into Crossref's polite pool include a email address
43//!
44//! ```edition2018
45//! # use crossref::Crossref;
46//! # fn run() -> Result<(), crossref::Error> {
47//! let client = Crossref::builder()
48//! .polite("polite@example.com")
49//! .token("your token")
50//! .build()?;
51//! # Ok(())
52//! # }
53//! ```
54//!
55//! ### Constructing Queries
56//! Not all components support queries and there are custom available parameters for each route that supports querying.
57//! For each resource components that supports querying there exist a Query struct: `WorksQuery`, `MembersQuery`, `FundersQuery`. The `WorksQuery` also differs from the others by supporting [deep paging with cursors](https://github.com/CrossRef/rest-api-doc#deep-paging-with-cursors) and [field queries](https://github.com/CrossRef/rest-api-doc#works-field-queries).
58//!
59//! Otherwise creating queries works the same for all resource components:
60//!
61//! ```edition2018
62//! # use crossref::*;
63//! # fn run() -> Result<()> {
64//! let query = WorksQuery::new("Machine Learning")
65//! // field queries supported for `Works`
66//! .field_query(FieldQuery::author("Some Author"))
67//! // filters are specific for each resource component
68//! .filter(WorksFilter::HasOrcid)
69//! .order(Order::Asc)
70//! .sort(Sort::Score);
71//! # Ok(())
72//! # }
73//! ```
74//!
75//!
76//! ### Get Records
77//!
78//! See [this table](https://github.com/CrossRef/rest-api-doc#resource-components) for a detailed overview of the major components.
79//!
80//! There are 3 available targets:
81//!
82//! * **standalone resource components**: `/works`, `/members`, etc. that return a list list of the corresponding items and can be specified with queries
83//! * **Resource component with identifiers**: `/works/{doi}?<query>`,`/members/{member_id}?<query>`, etc. that returns a single item if found.
84//! * **combined with the `works` route**: The works component can be appended to other resources: `/members/{member_id}/works?<query>` etc. that returns a list of matching `Work` items.
85//!
86//! This resembles in the enums of the resource components, eg. for `Members`:
87//!
88//! ```edition2018
89//! # use crossref::query::*;
90//! pub enum Members {
91//! /// target a specific member at `/members/{id}`
92//! Identifier(String),
93//! /// target all members that match the query at `/members?query...`
94//! Query(MembersQuery),
95//! /// target a `Work` for a specific member at `/members/{id}/works?query..`
96//! Works(WorksIdentQuery),
97//! }
98//! ```
99//!
100//! All options are supported by the client:
101//!
102//! **Single Item by DOI (ID)**
103//!
104//! Analogous methods exist for all resource components
105//!
106//! ```edition2018
107//! # use crossref::*;
108//! # fn run() -> Result<()> {
109//! # let client = Crossref::builder().build()?;
110//! let work = client.work("10.1037/0003-066X.59.1.29")?;
111//!
112//! let agency = client.work_agency("10.1037/0003-066X.59.1.29")?;
113//!
114//! let funder = client.funder("funder_id")?;
115//!
116//! let member = client.member("member_id")?;
117//! # Ok(())
118//! # }
119//! ```
120//!
121//! **Query**
122//!
123//! ```edition2018
124//! # use crossref::*;
125//! # fn run() -> Result<()> {
126//! # let client = Crossref::builder().build()?;
127//! let query = WorksQuery::new("Machine Learning");
128//!
129//! // one page of the matching results
130//! let works = client.works(query)?;
131//! # Ok(())
132//! # }
133//! ```
134//!
135//! Alternatively insert a free form query term directly
136//!
137//! ```edition2018
138//! # use crossref::*;
139//! # fn run() -> Result<()> {
140//! # let client = Crossref::builder().build()?;
141//!
142//! // one page of the matching results
143//! let works = client.works("Machine Learning")?;
144//! # Ok(())
145//! # }
146//! ```
147//!
148//! **Combining Routes with the `Works` route**
149//!
150//! For each resource component other than `Works` there exist methods to append a `WorksQuery` with the ID option `/members/{member_id}/works?<query>?`
151//!
152//! ```edition2018
153//! # use crossref::*;
154//! # fn run() -> Result<()> {
155//! # let client = Crossref::builder().build()?;
156//! let works = client.member_works( WorksQuery::new("machine learning")
157//! .sort(Sort::Score).into_ident("member_id"))?;
158//! # Ok(())
159//! # }
160//! ```
161//!
162//! This would be the same as using the [`Crossref::works`] method by supplying the combined type
163//!
164//! ```edition2018
165//! # use crossref::*;
166//! # fn run() -> Result<()> {
167//! # let client = Crossref::builder().build()?;
168//! let works = client.works(WorksQuery::new("machine learning")
169//! .sort(Sort::Score)
170//! .into_combined_query::<Members>("member_id"))?;
171//! # Ok(())
172//! # }
173//! ```
174//!
175//! ** Deep paging for `Works` **
176//! [Deep paging results](https://github.com/CrossRef/rest-api-doc#deep-paging-with-cursors)
177//! Deep paging is supported for all queries, that return a list of `Work`, `WorkList`.
178//! This function returns a new iterator over pages of `Work`, which is returned as bulk of items as a `WorkList` by crossref.
179//! Usually a single page `WorkList` contains 20 items.
180//!
181//! # Example
182//!
183//! Iterate over all `Works` linked to search term `Machine Learning`
184//!
185//! ```edition2018
186//! use crossref::{Crossref, WorksQuery, Work};
187//! # fn run() -> Result<(), crossref::Error> {
188//! let client = Crossref::builder().build()?;
189//!
190//! let all_works: Vec<Work> = client.deep_page(WorksQuery::new("Machine Learning")).flat_map(|x|x.items).collect();
191//!
192//! # Ok(())
193//! # }
194//! ```
195//!
196//! Which can be simplified to
197//!
198//! ```edition2018
199//! use crossref::{Crossref, WorksQuery, Work};
200//! # fn run() -> Result<(), crossref::Error> {
201//! let client = Crossref::builder().build()?;
202//!
203//! let all_works: Vec<Work> = client.deep_page("Machine Learning").into_work_iter().collect();
204//!
205//! # Ok(())
206//! # }
207//! ```
208//!
209//!
210//! # Example
211//!
212//! Iterate over all the pages (`WorkList`) of the funder with id `funder id` by using a combined query.
213//! A single `WorkList` usually holds 20 `Work` items.
214//!
215//! ```edition2018
216//! use crossref::{Crossref, Funders, WorksQuery, Work, WorkList};
217//! # fn run() -> Result<(), crossref::Error> {
218//! let client = Crossref::builder().build()?;
219//!
220//! let all_funder_work_list: Vec<WorkList> = client.deep_page(WorksQuery::default().into_combined_query::<Funders>("funder id")).collect();
221//!
222//! # Ok(())
223//! # }
224//! ```
225//! # Example
226//!
227//! Iterate over all `Work` items of a specfic funder directly.
228//!
229//! ```edition2018
230//! use crossref::{Crossref, Funders, WorksQuery, Work, WorkList};
231//! # fn run() -> Result<(), crossref::Error> {
232//! let client = Crossref::builder().build()?;
233//!
234//! let all_works: Vec<Work> = client.deep_page(WorksQuery::default()
235//! .into_combined_query::<Funders>("funder id"))
236//! .into_work_iter()
237//! .collect();
238//!
239//! # Ok(())
240//! # }
241//! ```
242
243#![deny(warnings)]
244#![deny(missing_docs)]
245#![allow(unused)]
246
247mod error;
248/// provides types to construct a specific query
249pub mod query;
250/// provides the response types of the crossref api
251pub mod response;
252
253// TODO extract to optional feature?
254/// content negotiation
255pub mod cn;
256/// textual data mining
257pub mod tdm;
258
259#[doc(inline)]
260pub use self::error::{Error, Result};
261
262#[doc(inline)]
263pub use self::query::works::{
264 FieldQuery, WorkListQuery, WorkResultControl, Works, WorksFilter, WorksIdentQuery, WorksQuery,
265};
266
267#[doc(inline)]
268pub use self::query::{Component, CrossrefQuery, CrossrefRoute, Order, Sort};
269pub use self::query::{Funders, Journals, Members, Prefixes, Type, Types};
270pub use self::response::{
271 CrossrefType, Funder, FunderList, Journal, JournalList, Member, MemberList, TypeList, Work,
272 WorkAgency, WorkList,
273};
274
275pub(crate) use self::response::{Message, Response};
276
277use crate::error::ErrorKind;
278use crate::query::{FundersQuery, MembersQuery, ResourceComponent};
279use crate::response::{MessageType, Prefix};
280use reqwest::{self, Client};
281use std::iter::FlatMap;
282use std::rc::Rc;
283
284macro_rules! get_item {
285 ($ident:ident, $value:expr, $got:expr) => {
286 if let Some(msg) = $value {
287 match msg {
288 Message::$ident(item) => Ok(item),
289 _ => Err(ErrorKind::UnexpectedItem {
290 expected: MessageType::$ident,
291 got: $got,
292 }
293 .into()),
294 }
295 } else {
296 Err(ErrorKind::MissingMessage {
297 expected: MessageType::$ident,
298 }
299 .into())
300 }
301 };
302}
303
304macro_rules! impl_combined_works_query {
305 ($($name:ident $component:ident,)*) => {
306 $(
307 /// Return one page of the components's `Work` that match the query
308 ///
309 pub fn $name(&self, ident: WorksIdentQuery) -> Result<WorkList> {
310 let resp = self.get_response(&$component::Works(ident))?;
311 get_item!(WorkList, resp.message, resp.message_type)
312 })+
313 };
314}
315
316/// Struct for Crossref search API methods
317#[derive(Debug, Clone)]
318pub struct Crossref {
319 /// use another base url than `api.crossref.org`
320 pub base_url: String,
321 /// the reqwest client that handles the requests
322 pub client: Rc<Client>,
323}
324
325impl Crossref {
326 const BASE_URL: &'static str = "https://api.crossref.org";
327
328 /// Constructs a new `CrossrefBuilder`.
329 ///
330 /// This is the same as `Crossref::builder()`.
331 pub fn builder() -> CrossrefBuilder {
332 CrossrefBuilder::new()
333 }
334
335 // generate all functions to query combined endpoints
336 impl_combined_works_query!(funder_works Funders, member_works Members,
337 type_works Types, journal_works Journals, prefix_works Prefixes,);
338
339 /// Transforms the `CrossrefQuery` in the request route and executes the request
340 ///
341 /// # Errors
342 ///
343 /// If it was a bad url, the server will return `Resource not found` a `ResourceNotFound` error will be returned in this case
344 /// Also fails if the json response body could be parsed into `Response`
345 /// Fails if there was an error in reqwest executing the request [::reqwest::RequestBuilder::send]
346 fn get_response<T: CrossrefQuery>(&self, query: &T) -> Result<Response> {
347 let resp = self
348 .client
349 .get(&query.to_url(&self.base_url)?)
350 .send()?
351 .text()?;
352 if resp.starts_with("Resource not found") {
353 Err(ErrorKind::ResourceNotFound {
354 resource: Box::new(query.clone().resource_component()),
355 }
356 .into())
357 } else {
358 Ok(serde_json::from_str(&resp)?)
359 }
360 }
361
362 /// Return the `Work` items that match a certain query.
363 ///
364 /// To search only by query terms use the convenience query method [Crossref::query_works]
365 ///
366 /// # Example
367 ///
368 /// ```edition2018
369 /// use crossref::{Crossref, WorksQuery, WorksFilter, FieldQuery};
370 /// # fn run() -> Result<(), crossref::Error> {
371 /// let client = Crossref::builder().build()?;
372 ///
373 /// let query = WorksQuery::new("Machine Learning")
374 /// .filter(WorksFilter::HasOrcid)
375 /// .order(crossref::Order::Asc)
376 /// .field_query(FieldQuery::author("Some Author"))
377 /// .sort(crossref::Sort::Score);
378 ///
379 /// let works = client.works(query)?;
380 ///
381 /// # Ok(())
382 /// # }
383 /// ```
384 ///
385 /// # Errors
386 ///
387 /// This method fails if the `works` element expands to a bad route `ResourceNotFound`
388 /// Fails if the response body doesn't have `message` field `MissingMessage`.
389 /// Fails if anything else than a `WorkList` is returned as message `UnexpectedItem`
390 pub fn works<T: Into<WorkListQuery>>(&self, query: T) -> Result<WorkList> {
391 let resp = self.get_response(&query.into())?;
392 get_item!(WorkList, resp.message, resp.message_type)
393 }
394
395 /// Return the `Work` that is identified by the `doi`.
396 ///
397 /// # Errors
398 /// This method fails if the doi could not identified `ResourceNotFound`
399 ///
400 pub fn work(&self, doi: &str) -> Result<Work> {
401 let resp = self.get_response(&Works::Identifier(doi.to_string()))?;
402 get_item!(Work, resp.message, resp.message_type).map(|x| *x)
403 }
404
405 /// [Deep paging results](https://github.com/CrossRef/rest-api-doc#deep-paging-with-cursors)
406 /// Deep paging is supported for all queries, that return a list of `Work`, `WorkList`.
407 /// This function returns a new iterator over pages of `Work`, which is returned as bulk of items as a `WorkList` by crossref.
408 /// Usually a single page `WorkList` contains 20 items.
409 ///
410 /// # Example
411 ///
412 /// Iterate over all `Works` linked to search term `Machine Learning`
413 ///
414 /// ```edition2018
415 /// use crossref::{Crossref, WorksQuery, Work};
416 /// # fn run() -> Result<(), crossref::Error> {
417 /// let client = Crossref::builder().build()?;
418 ///
419 /// let all_works: Vec<Work> = client.deep_page(WorksQuery::new("Machine Learning")).flat_map(|x|x.items).collect();
420 ///
421 /// # Ok(())
422 /// # }
423 /// ```
424 ///
425 /// # Example
426 ///
427 /// Iterate over all the pages (`WorkList`) of the funder with id `funder id` by using a combined query.
428 /// A single `WorkList` usually holds 20 `Work` items.
429 ///
430 /// ```edition2018
431 /// use crossref::{Crossref, Funders, WorksQuery, Work, WorkList};
432 /// # fn run() -> Result<(), crossref::Error> {
433 /// let client = Crossref::builder().build()?;
434 ///
435 /// let all_funder_work_list: Vec<WorkList> = client.deep_page(WorksQuery::default().into_combined_query::<Funders>("funder id")).collect();
436 ///
437 /// # Ok(())
438 /// # }
439 /// ```
440 /// # Example
441 ///
442 /// Iterate over all `Work` items of a specfic funder directly.
443 ///
444 /// ```edition2018
445 /// use crossref::{Crossref, Funders, WorksQuery, Work, WorkList};
446 /// # fn run() -> Result<(), crossref::Error> {
447 /// let client = Crossref::builder().build()?;
448 ///
449 /// let all_works: Vec<Work> = client.deep_page(WorksQuery::default()
450 /// .into_combined_query::<Funders>("funder id"))
451 /// .into_work_iter()
452 /// .collect();
453 ///
454 /// # Ok(())
455 /// # }
456 /// ```
457 ///
458 /// # Example
459 ///
460 /// Alternatively deep page without an iterator by handling the cursor directly
461 ///
462 /// ```edition2018
463 /// use crossref::{Crossref, WorksQuery, WorksFilter};
464 /// # fn run() -> Result<(), crossref::Error> {
465 /// let client = Crossref::builder().build()?;
466 ///
467 /// // request a next-cursor first
468 /// let query = WorksQuery::new("Machine Learning")
469 /// .new_cursor();
470 ///
471 /// let works = client.works(query.clone())?;
472 ///
473 /// // this continues from where this first response stopped
474 /// // if no more work items are available then a empty list will be returned
475 /// let deep_works = client.works(
476 /// query.next_cursor(&works.next_cursor.unwrap())
477 /// )?;
478 /// # Ok(())
479 /// # }
480 /// ```
481 ///
482 pub fn deep_page<T: Into<WorkListQuery>>(&self, query: T) -> WorkListIterator {
483 WorkListIterator {
484 query: query.into(),
485 client: self,
486 index: 0,
487 finish_next_iteration: false,
488 }
489 }
490
491 /// Return the `Agency` that registers the `Work` identified by the `doi`.
492 ///
493 /// # Errors
494 /// This method fails if the doi could not identified `ResourceNotFound`
495 ///
496 pub fn work_agency(&self, doi: &str) -> Result<WorkAgency> {
497 let resp = self.get_response(&Works::Agency(doi.to_string()))?;
498 get_item!(WorkAgency, resp.message, resp.message_type)
499 }
500
501 /// Return the matching `Funders` items.
502 pub fn funders(&self, funders: FundersQuery) -> Result<FunderList> {
503 let resp = self.get_response(&Funders::Query(funders))?;
504 get_item!(FunderList, resp.message, resp.message_type)
505 }
506
507 /// Return the `Funder` for the `id`
508 pub fn funder(&self, id: &str) -> Result<Funder> {
509 let resp = self.get_response(&Funders::Identifier(id.to_string()))?;
510 get_item!(Funder, resp.message, resp.message_type).map(|x| *x)
511 }
512
513 /// Return the matching `Members` items.
514 pub fn members(&self, members: MembersQuery) -> Result<MemberList> {
515 let resp = self.get_response(&Members::Query(members))?;
516 get_item!(MemberList, resp.message, resp.message_type)
517 }
518
519 /// Return the `Member` for the `id`
520 pub fn member(&self, member_id: &str) -> Result<Member> {
521 let resp = self.get_response(&Members::Identifier(member_id.to_string()))?;
522 get_item!(Member, resp.message, resp.message_type).map(|x| *x)
523 }
524
525 /// Return the `Prefix` for the `id`
526 pub fn prefix(&self, id: &str) -> Result<Prefix> {
527 let resp = self.get_response(&Prefixes::Identifier(id.to_string()))?;
528 get_item!(Prefix, resp.message, resp.message_type)
529 }
530 /// Return a specific `Journal`
531 pub fn journal(&self, id: &str) -> Result<Journal> {
532 let resp = self.get_response(&Journals::Identifier(id.to_string()))?;
533 get_item!(Journal, resp.message, resp.message_type).map(|x| *x)
534 }
535
536 /// Return all available `Type`
537 pub fn types(&self) -> Result<TypeList> {
538 let resp = self.get_response(&Types::All)?;
539 get_item!(TypeList, resp.message, resp.message_type)
540 }
541
542 /// Return the `Type` for the `id`
543 pub fn type_(&self, id: &Type) -> Result<CrossrefType> {
544 let resp = self.get_response(&Types::Identifier(id.id().to_string()))?;
545 get_item!(Type, resp.message, resp.message_type)
546 }
547
548 /// Get a random set of DOIs
549 ///
550 /// # Example
551 ///
552 /// ```edition2018
553 /// use crossref::Crossref;
554 /// # fn run() -> Result<(), crossref::Error> {
555 /// # let client = Crossref::builder().build()?;
556 /// // this will return 10 random dois from the crossref api
557 /// let random_dois = client.random_dois(10)?;
558 /// # Ok(())
559 /// # }
560 /// ```
561 pub fn random_dois(&self, len: usize) -> Result<Vec<String>> {
562 self.works(WorksQuery::random(len))
563 .map(|x| x.items.into_iter().map(|x| x.doi).collect())
564 }
565}
566
567/// A `CrossrefBuilder` can be used to create `Crossref` with additional config.
568///
569/// # Example
570///
571/// ```edition2018
572/// use crossref::Crossref;
573/// # fn run() -> Result<(), crossref::Error> {
574///
575/// let client = Crossref::builder()
576/// .polite("polite@example.com")
577/// .token("your token")
578/// .build()?;
579/// # Ok(())
580/// # }
581/// ```
582#[derive(Default)]
583pub struct CrossrefBuilder {
584 /// [Good manners = more reliable service.](https://github.com/CrossRef/rest-api-doc#good-manners--more-reliable-service)
585 ///
586 /// will add a `User-Agent` header by default with with the `email` email.
587 /// crossref can contact you if your script misbehaves
588 /// this will get you directed to the "polite pool"
589 user_agent: Option<String>,
590 /// the token for the Crossref Plus service will be included as `Authorization` header
591 /// This token will ensure that said requests get directed to a pool of machines that are reserved for "Plus" SLA users.
592 plus_token: Option<String>,
593 /// use a different base url than `Crossref::BASE_URL` https://api.crossref.org
594 base_url: Option<String>,
595}
596
597impl CrossrefBuilder {
598 /// Constructs a new `CrossrefBuilder`.
599 ///
600 /// This is the same as `Crossref::builder()`.
601 pub fn new() -> CrossrefBuilder {
602 CrossrefBuilder::default()
603 }
604
605 /// be polite and set your email as `User-Agent`
606 /// will get you in the polite pool of crossref
607 pub fn polite(mut self, email: &str) -> Self {
608 self.user_agent = Some(format!("mailto:{}", email));
609 self
610 }
611
612 /// set the user agent directly
613 pub fn user_agent(mut self, user_agent: &str) -> Self {
614 self.user_agent = Some(user_agent.to_string());
615 self
616 }
617
618 /// set a crossref plus service API token
619 pub fn token(mut self, token: &str) -> Self {
620 self.plus_token = Some(token.to_string());
621 self
622 }
623
624 /// Returns a `Crossref` that uses this `CrossrefBuilder` configuration.
625 /// # Errors
626 ///
627 /// This will fail if TLS backend cannot be initialized see [reqwest::ClientBuilder::build]
628 pub fn build(self) -> Result<Crossref> {
629 use reqwest::header;
630 let mut headers = header::HeaderMap::new();
631 if let Some(agent) = &self.user_agent {
632 headers.insert(
633 header::USER_AGENT,
634 header::HeaderValue::from_str(agent).map_err(|_| ErrorKind::Config {
635 msg: format!("failed to create User Agent header for `{}`", agent),
636 })?,
637 );
638 }
639 if let Some(token) = &self.plus_token {
640 headers.insert(
641 header::AUTHORIZATION,
642 header::HeaderValue::from_str(token).map_err(|_| ErrorKind::Config {
643 msg: format!("failed to create AUTHORIZATION header for `{}`", token),
644 })?,
645 );
646 }
647 let client = reqwest::Client::builder()
648 .default_headers(headers)
649 .build()
650 .map_err(|_| ErrorKind::Config {
651 msg: "failed to initialize TLS backend".to_string(),
652 })?;
653
654 Ok(Crossref {
655 base_url: self
656 .base_url
657 .unwrap_or_else(|| Crossref::BASE_URL.to_string()),
658 client: Rc::new(client),
659 })
660 }
661}
662
663/// Allows iterating of deep page work request
664pub struct WorkListIterator<'a> {
665 /// the query
666 query: WorkListQuery,
667 /// performs each request
668 client: &'a Crossref,
669 /// stores how many results already retrieved
670 index: usize,
671 /// whether the iterator should finish next iteration
672 finish_next_iteration: bool,
673}
674impl<'a> WorkListIterator<'a> {
675 /// convenience method to create a `WorkIterator`
676 pub fn into_work_iter(self) -> impl Iterator<Item = Work> + 'a {
677 self.flat_map(|x| x.items)
678 }
679}
680
681impl<'a> Iterator for WorkListIterator<'a> {
682 type Item = WorkList;
683
684 fn next(&mut self) -> Option<Self::Item> {
685 if self.finish_next_iteration {
686 return None;
687 }
688
689 {
690 let control = &mut self.query.query_mut().result_control;
691
692 // if no result control is set, set a new cursor
693 if control.is_none() {
694 *control = Some(WorkResultControl::new_cursor());
695 }
696 }
697
698 let resp = self.client.get_response(&self.query);
699 if let Ok(resp) = resp {
700 let worklist: Result<WorkList> = get_item!(WorkList, resp.message, resp.message_type);
701 if let Ok(worklist) = worklist {
702 if let Some(cursor) = &worklist.next_cursor {
703 match &mut self.query.query_mut().result_control {
704 Some(WorkResultControl::Cursor { token, .. }) => {
705 // use the received cursor token in next iteration
706 *token = Some(cursor.clone())
707 }
708 Some(WorkResultControl::Standard(_)) => {
709 // standard result control was set, don't deep page and return next iteration
710 self.finish_next_iteration = true;
711 }
712 _ => (),
713 }
714 } else {
715 // no cursor received, end next iteration
716 self.finish_next_iteration = true;
717 }
718
719 if worklist.items.is_empty() {
720 None
721 } else {
722 Some(worklist)
723 }
724 } else {
725 // failed to deserialize response into `WorkList`
726 None
727 }
728 } else {
729 // no response received
730 None
731 }
732 }
733}