stac_api/
url_builder.rs

1use std::str::FromStr;
2use url::{ParseError, Url};
3
4/// Builds urls on a root url.
5///
6/// # Examples
7///
8/// ```
9/// # use stac_api::UrlBuilder;
10/// let url_builder = UrlBuilder::new("http://stac-api.test/api/v1").unwrap();
11/// assert_eq!(
12///     url_builder.items("my-great-collection").unwrap().as_str(),
13///     "http://stac-api.test/api/v1/collections/my-great-collection/items"
14/// );
15/// ```
16#[derive(Clone, Debug)]
17pub struct UrlBuilder {
18    root: Url,
19    collections: Url,
20    collections_with_slash: Url,
21    conformance: Url,
22    service_desc: Url,
23    search: Url,
24}
25
26impl UrlBuilder {
27    /// Creates a new url builder.
28    ///
29    /// # Examples
30    ///
31    /// ```
32    /// # use stac_api::UrlBuilder;
33    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
34    /// ```
35    pub fn new(url: &str) -> Result<UrlBuilder, ParseError> {
36        let root: Url = if url.ends_with('/') {
37            url.parse()?
38        } else {
39            format!("{url}/").parse()?
40        };
41        Ok(UrlBuilder {
42            collections: root.join("collections")?,
43            collections_with_slash: root.join("collections/")?,
44            conformance: root.join("conformance")?,
45            service_desc: root.join("api")?,
46            search: root.join("search")?,
47            root,
48        })
49    }
50
51    /// Returns the root url.
52    ///
53    /// The root url always has a trailing slash, even if the builder was
54    /// created without one.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// # use stac_api::UrlBuilder;
60    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
61    /// assert_eq!(url_builder.root().as_str(), "http://stac-api.test/");
62    /// ```
63    pub fn root(&self) -> &Url {
64        &self.root
65    }
66
67    /// Returns the collections url.
68    ///
69    /// This url is created when the builder is created, so it can't error.
70    ///
71    /// # Examples
72    ///
73    /// ```
74    /// # use stac_api::UrlBuilder;
75    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
76    /// assert_eq!(url_builder.collections().as_str(), "http://stac-api.test/collections");
77    /// ```
78    pub fn collections(&self) -> &Url {
79        &self.collections
80    }
81
82    /// Returns a collection url.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// # use stac_api::UrlBuilder;
88    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
89    /// assert_eq!(
90    ///     url_builder.collection("a-collection").unwrap().as_str(),
91    ///     "http://stac-api.test/collections/a-collection"
92    /// );
93    /// ```
94    pub fn collection(&self, id: &str) -> Result<Url, ParseError> {
95        self.collections_with_slash.join(id)
96    }
97
98    /// Returns an items url.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// # use stac_api::UrlBuilder;
104    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
105    /// assert_eq!(
106    ///     url_builder.items("a-collection").unwrap().as_str(),
107    ///     "http://stac-api.test/collections/a-collection/items"
108    /// );
109    /// ```
110    pub fn items(&self, id: &str) -> Result<Url, ParseError> {
111        self.collections_with_slash.join(&format!("{id}/items"))
112    }
113
114    /// Returns a item url.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// # use stac_api::UrlBuilder;
120    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
121    /// assert_eq!(
122    ///     url_builder.item("a-collection", "an-item").unwrap().as_str(),
123    ///     "http://stac-api.test/collections/a-collection/items/an-item"
124    /// );
125    /// ```
126    pub fn item(&self, collection_id: &str, id: &str) -> Result<Url, ParseError> {
127        self.collections_with_slash
128            .join(&format!("{collection_id}/items/{id}"))
129    }
130
131    /// Returns the conformance url.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// # use stac_api::UrlBuilder;
137    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
138    /// assert_eq!(
139    ///     url_builder.conformance().as_str(),
140    ///     "http://stac-api.test/conformance"
141    /// );
142    /// ```
143    pub fn conformance(&self) -> &Url {
144        &self.conformance
145    }
146
147    /// Returns the service-desc url.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// # use stac_api::UrlBuilder;
153    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
154    /// assert_eq!(
155    ///     url_builder.service_desc().as_str(),
156    ///     "http://stac-api.test/api"
157    /// );
158    /// ```
159    pub fn service_desc(&self) -> &Url {
160        &self.service_desc
161    }
162
163    /// Returns the search url.
164    ///
165    /// # Examples
166    ///
167    /// ```
168    /// # use stac_api::UrlBuilder;
169    /// let url_builder = UrlBuilder::new("http://stac-api.test").unwrap();
170    /// assert_eq!(
171    ///     url_builder.search().as_str(),
172    ///     "http://stac-api.test/search"
173    /// );
174    /// ```
175    pub fn search(&self) -> &Url {
176        &self.search
177    }
178}
179
180impl FromStr for UrlBuilder {
181    type Err = ParseError;
182
183    fn from_str(s: &str) -> Result<Self, Self::Err> {
184        UrlBuilder::new(s)
185    }
186}