Skip to main content

superstac_core/models/
provider.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    errors::{SuperSTACError, ValidationError},
6    utils::{get_date_time, parse_url, validate_identifier},
7};
8
9/// YAML-deserialization shape for a provider entry. Converted to
10/// [`CatalogProvider`] via `TryFrom`.
11#[derive(Debug, Deserialize)]
12pub struct CatalogProviderConfig {
13    pub id: String,
14    pub name: Option<String>,
15    pub description: Option<String>,
16    pub logo_url: Option<String>,
17    pub website_url: Option<String>,
18    pub stac_version: Option<String>,
19    pub catalog_ids: Option<Vec<String>>,
20}
21
22impl TryFrom<CatalogProviderConfig> for CatalogProvider {
23    type Error = SuperSTACError;
24
25    fn try_from(cfg: CatalogProviderConfig) -> Result<Self, Self::Error> {
26        validate_identifier(&cfg.id)?;
27
28        let website_url = match cfg.website_url {
29            Some(w) => {
30                parse_url(&w).map_err(|e| ValidationError::InvalidUrl(e.to_string()))?;
31                Some(w)
32            }
33            None => None,
34        };
35
36        let logo_url = match cfg.logo_url {
37            Some(l) => {
38                parse_url(&l).map_err(|e| ValidationError::InvalidUrl(e.to_string()))?;
39                Some(l)
40            }
41            None => None,
42        };
43
44        let stac_version = cfg
45            .stac_version
46            .ok_or_else(|| ValidationError::MissingField("stac_version".into()))?;
47
48        Ok(Self {
49            id: cfg.id,
50            name: cfg.name,
51            description: cfg.description,
52            website_url,
53            logo_url,
54            stac_version: Some(stac_version),
55            catalog_ids: None,
56            created_at: Some(get_date_time()),
57            updated_at: None,
58        })
59    }
60}
61
62/// A vendor/organization that operates one or more STAC catalogs. Mostly
63/// metadata (name, logo, website) — the actual API endpoints live on
64/// [`super::catalog::Catalog`], linked back via `catalog_ids`.
65#[derive(Clone, Debug, Serialize, Deserialize, Default,PartialEq)]
66pub struct CatalogProvider {
67    /// A unique id for the provider. E.g microsoft, google, element84 e.t.c.
68    pub id: String,
69
70    /// The name of the provider. E.g Microsoft, Google, Element 84 e.t.c
71    pub name: Option<String>,
72
73    /// Detailed description of the provider.
74    pub description: Option<String>,
75
76    /// URL to the provider logo. Could be a URL or a file path.
77    pub logo_url: Option<String>,
78
79    /// The STAC version the provider is conforming to.
80    pub stac_version: Option<String>,
81
82    /// The URL to the provider website/public page.
83    pub website_url: Option<String>,
84
85    /// The related catalogs to this provider
86    pub catalog_ids: Option<Vec<String>>,
87
88    /// The date created. This is automatically populated after creation.
89    pub created_at: Option<DateTime<Utc>>,
90    /// The date updated. The is automatically populated after an update activity.
91    pub updated_at: Option<DateTime<Utc>>,
92}
93
94impl CatalogProvider {
95    /// Validate and construct a provider. Errors if the id contains
96    /// non-ASCII characters or either URL doesn't parse.
97    pub fn new(
98        id: String,
99        name: Option<String>,
100        description: Option<String>,
101        website_url: Option<String>,
102        logo_url: Option<String>,
103        stac_version: Option<String>,
104        catalog_ids: Option<Vec<String>>,
105    ) -> Result<Self, SuperSTACError> {
106        validate_identifier(&id)?;
107
108        let valid_website = if let Some(w) = website_url {
109            parse_url(w.as_str())
110                .map_err(|e| ValidationError::InvalidUrl(format!("Invalid website URL: {}", e)))?;
111            Some(w.to_string())
112        } else {
113            None
114        };
115
116        // Validate logo_url if provided
117        let valid_logo = if let Some(l) = logo_url {
118            parse_url(l.as_str())
119                .map_err(|e| ValidationError::InvalidUrl(format!("Invalid logo URL: {}", e)))?;
120            Some(l.to_string())
121        } else {
122            None
123        };
124
125        // How do validate the catalogs ?, access db.catalogs here ? pass db as a parameter ?
126
127        Ok(Self {
128            id,
129            name,
130            website_url: valid_website,
131            stac_version,
132            logo_url: valid_logo,
133            description,
134            catalog_ids,
135            created_at: Some(get_date_time()),
136            updated_at: None,
137        })
138    }
139
140    pub fn set_id(&mut self, id: String) {
141        self.id = id
142    }
143
144    /// Apply a partial update. `None` fields are left alone.
145    pub fn update(
146        &mut self,
147        name: Option<String>,
148        website: Option<String>,
149        logo_url: Option<String>,
150        description: Option<String>,
151        stac_version: Option<String>,
152        catalog_ids: Option<Vec<String>>,
153    ) -> Result<(), ValidationError> {
154        if let Some(updated_url) = logo_url {
155            match parse_url(&updated_url) {
156                Ok(valid_url) => {
157                    self.logo_url = Some(valid_url.to_string());
158                }
159                Err(err) => {
160                    return Err(ValidationError::InvalidUrl(err.to_string()));
161                }
162            }
163        }
164
165        if let Some(updated_url) = website {
166            match parse_url(&updated_url) {
167                Ok(valid_url) => {
168                    self.website_url = Some(valid_url.to_string());
169                }
170                Err(err) => {
171                    return Err(ValidationError::InvalidUrl(err.to_string()));
172                }
173            }
174        }
175
176        self.name = name;
177        self.description = description;
178        self.stac_version = stac_version;
179        self.catalog_ids = catalog_ids;
180        self.set_update_date();
181        Ok(())
182    }
183
184    /// Stamp `updated_at` with the current time.
185    pub fn set_update_date(&mut self) {
186        self.updated_at = Some(get_date_time());
187    }
188
189    /// Stamp `created_at` with the current time.
190    pub fn set_created_date(&mut self) {
191        self.created_at = Some(get_date_time());
192    }
193
194    /// Detach a catalog from this provider. No-op if not linked.
195    pub fn remove_catalog(&mut self, catalog_id: &str) {
196        if let Some(catalog_ids) = &mut self.catalog_ids {
197            catalog_ids.retain(|id| id != catalog_id);
198
199            if catalog_ids.is_empty() {
200                self.catalog_ids = None;
201            }
202        }
203    }
204
205    /// Attach a catalog to this provider. No-op if already linked.
206    pub fn add_catalog(&mut self, catalog_id: &str) {
207        let catalog_ids = self.catalog_ids.get_or_insert_with(Vec::new);
208       
209        if !catalog_ids.iter().any(|id| id == &catalog_id) {
210            catalog_ids.push(catalog_id.to_string());
211             
212        }
213    }
214}
215
216#[derive(Clone, Debug, Serialize, Deserialize)]
217pub struct CatalogProviderFilters {
218    /// Performs an exact match on the `id` field.
219    pub id: Option<String>,
220
221    /// Performs a string search on the provider name.
222    pub name: Option<String>,
223
224    /// Performs a string search on the provider description.
225    pub description: Option<String>,
226
227    /// Performs a string search on the provider stac_version.
228    pub stac_version: Option<String>,
229
230    /// Performs a string search on the provider catalogs.
231    pub catalog_id: Option<String>,
232
233    /// Performs a date search on the `created_at` field. Filters for for date `after` the provided date.
234    pub created_after: Option<DateTime<Utc>>,
235    /// Performs a date search on the `created_at` field. Filters for for date `before` the provided date.
236    pub created_before: Option<DateTime<Utc>>,
237    /// Performs a date search on the `updated_at` field. Filters for for date `after` the provided date.
238    pub updated_after: Option<DateTime<Utc>>,
239    /// Performs a date search on the `updated_at` field. Filters for for date `before` the provided date.
240    pub updated_before: Option<DateTime<Utc>>,
241}
242
243impl Default for CatalogProviderFilters {
244    fn default() -> Self {
245        CatalogProviderFilters{
246            id: None,
247            name:None,
248            stac_version:None,
249            catalog_id:None,
250           
251            description: None,
252            
253            created_after: None,
254            created_before: None,
255            updated_after: None,
256            updated_before: None,
257        }
258    }
259}
260
261#[derive(Clone, Debug, Serialize, Deserialize)]
262pub struct CatalogProviderUpdate {
263    /// Updates the provider name.
264    pub name: Option<String>,
265    /// Updates the catalog description.
266    pub description: Option<String>,
267    /// Updates the provider logo url.
268    pub logo_url: Option<String>,
269    /// Updates the provider logo url.
270    pub website_url: Option<String>,
271
272    /// Updates the provider stac_version.
273    pub stac_version: Option<String>,
274
275    /// Updates the provider stac_version.
276    pub catalog_ids: Option<Vec<String>>,
277}