1use crate::errors::Result;
2use crate::v2::*;
3use async_stream::try_stream;
4use reqwest::{self, header, Url};
5use std::fmt::Debug;
6
7#[derive(Debug, Default, Deserialize, Serialize)]
12struct TagsChunk {
13 name: String,
15 tags: Vec<String>,
17}
18
19impl Client {
20 pub fn get_tags<'a, 'b: 'a, 'c: 'a>(
22 &'b self,
23 name: &'c str,
24 paginate: Option<u32>,
25 ) -> impl Stream<Item = Result<String>> + 'a {
26 let base_url = format!("{}/v2/{}/tags/list", self.base_url, name);
27 let mut link: Option<String> = None;
28
29 try_stream! {
30 loop {
31 let (tags_chunk, last) = self.fetch_tags_chunk(paginate, &base_url, &link).await?;
32 for tag in tags_chunk.tags {
33 yield tag;
34 }
35
36 link = match last {
37 None => break,
38 Some(ref s) if s == "" => None,
39 s => s,
40 };
41 }
42 }
43 }
44
45 async fn fetch_tags_chunk(
46 &self,
47 paginate: Option<u32>,
48 base_url: &str,
49 link: &Option<String>,
50 ) -> Result<(TagsChunk, Option<String>)> {
51 let url_paginated = match (paginate, link) {
52 (Some(p), None) => format!("{}?n={}", base_url, p),
53 (None, Some(l)) => format!("{}?next_page={}", base_url, l),
54 (Some(p), Some(l)) => format!("{}?n={}&next_page={}", base_url, p, l),
55 _ => base_url.to_string(),
56 };
57 let url = Url::parse(&url_paginated)?;
58
59 let resp = self
60 .build_reqwest(Method::GET, url.clone())
61 .header(header::ACCEPT, "application/json")
62 .send()
63 .await?
64 .error_for_status()?;
65
66 let ct_hdr = resp.headers().get(header::CONTENT_TYPE).cloned();
68
69 trace!("page url {:?}", ct_hdr);
70
71 let ok = match ct_hdr {
72 None => false,
73 Some(ref ct) => ct.to_str()?.starts_with("application/json"),
74 };
75 if !ok {
76 debug!("get_tags: wrong content type '{:?}', ignoring...", ct_hdr);
79 }
80
81 let next = parse_link(resp.headers().get(header::LINK));
83 trace!("next_page {:?}", next);
84
85 let tags_chunk = resp.json::<TagsChunk>().await?;
86 Ok((tags_chunk, next))
87 }
88}
89
90fn parse_link(hdr: Option<&header::HeaderValue>) -> Option<String> {
94 let hval = match hdr {
99 Some(v) => v,
100 None => return None,
101 };
102
103 let sval = match hval.to_str() {
105 Ok(v) => v.to_owned(),
106 _ => return None,
107 };
108
109 let uri = sval.trim_end_matches(">; rel=\"next\"");
111 let query: Vec<&str> = uri.splitn(2, "next_page=").collect();
112 let params = match query.get(1) {
113 Some(v) if *v != "" => v,
114 _ => return None,
115 };
116
117 let last: Vec<&str> = params.splitn(2, '&').collect();
119 match last.get(0).cloned() {
120 Some(v) if v != "" => Some(v.to_string()),
121 _ => None,
122 }
123}