1use crate::Error;
2use http::header::HeaderName;
3use reqwest::{header::HeaderMap, IntoUrl, Method, StatusCode};
4use serde::{de::DeserializeOwned, Serialize};
5use serde_json::{Map, Value};
6use stac::{Href, Link};
7
8#[derive(Clone, Debug)]
10pub struct Client(pub reqwest::Client);
11
12impl Client {
13 pub fn new() -> Client {
36 Client(reqwest::Client::new())
37 }
38
39 pub async fn get<V>(&self, url: impl IntoUrl) -> Result<Option<V>, Error>
54 where
55 V: DeserializeOwned + Href,
56 {
57 let url = url.into_url()?;
58 if let Some(mut value) = self
59 .request::<(), V>(Method::GET, url.clone(), None, None)
60 .await?
61 {
62 value.set_href(url);
63 Ok(Some(value))
64 } else {
65 Ok(None)
66 }
67 }
68
69 pub async fn post<S, R>(&self, url: impl IntoUrl, data: &S) -> Result<Option<R>, Error>
84 where
85 S: Serialize + 'static,
86 R: DeserializeOwned,
87 {
88 self.request(Method::POST, url, Some(data), None).await
89 }
90
91 pub async fn request<S, R>(
106 &self,
107 method: Method,
108 url: impl IntoUrl,
109 params: impl Into<Option<&S>>,
110 headers: impl Into<Option<HeaderMap>>,
111 ) -> Result<Option<R>, Error>
112 where
113 S: Serialize + 'static,
114 R: DeserializeOwned,
115 {
116 let url = url.into_url()?;
117 let mut request = match method {
118 Method::GET => {
119 let mut request = self.0.get(url);
120 if let Some(query) = params.into() {
121 request = request.query(query);
122 }
123 request
124 }
125 Method::POST => {
126 let mut request = self.0.post(url);
127 if let Some(data) = params.into() {
128 request = request.json(&data);
129 }
130 request
131 }
132 _ => unimplemented!(),
133 };
134 if let Some(headers) = headers.into() {
135 request = request.headers(headers);
136 }
137 let response = request.send().await?;
138 if response.status() == StatusCode::NOT_FOUND {
139 return Ok(None);
140 }
141 let response = response.error_for_status()?;
142 response.json().await.map_err(Error::from)
143 }
144
145 pub async fn request_from_link<R>(&self, link: Link) -> Result<Option<R>, Error>
160 where
161 R: DeserializeOwned,
162 {
163 let method = if let Some(method) = link.method {
164 method.parse()?
165 } else {
166 Method::GET
167 };
168 let headers = if let Some(headers) = link.headers {
169 let mut header_map = HeaderMap::new();
170 for (key, value) in headers.into_iter() {
171 let header_name: HeaderName = key.parse()?;
172 let _ = header_map.insert(header_name, value.to_string().parse()?);
173 }
174 Some(header_map)
175 } else {
176 None
177 };
178 self.request::<Map<String, Value>, R>(method, link.href, &link.body, headers)
179 .await
180 }
181}
182
183impl Default for Client {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::Client;
192 use mockito::Server;
193 use stac::{Href, Item};
194 use stac_api::Search;
195
196 #[tokio::test]
197 async fn client_get() {
198 let client = Client::new();
199 let href = "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/simple-item.json";
200 let item: Item = client.get(href).await.unwrap().unwrap();
201 assert_eq!(item.href().unwrap(), href);
202 }
203
204 #[tokio::test]
205 async fn client_get_404() {
206 let client = Client::new();
207 let href = "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/not-an-item.json";
208 assert!(client.get::<Item>(href).await.unwrap().is_none());
209 }
210
211 #[tokio::test]
212 async fn client_post() {
213 let mut server = Server::new_async().await;
214 let page = server
215 .mock("POST", "/search")
216 .with_body(include_str!("../mocks/search-page-1.json"))
217 .with_header("content-type", "application/geo+json")
218 .create_async()
219 .await;
220 let client = Client::new();
221 let href = format!("{}/search", server.url());
222 let mut search = Search::default();
223 search.items.limit = Some(1);
224 let _: stac_api::ItemCollection = client.post(href, &search).await.unwrap().unwrap();
225 page.assert_async().await;
226 }
227}