oxigdal_stac/builder.rs
1//! Builder patterns for STAC objects.
2//!
3//! This module provides fluent builder APIs for creating STAC Catalogs,
4//! Collections, and Items.
5
6use crate::{
7 asset::Asset,
8 catalog::Catalog,
9 collection::{Collection, Provider},
10 error::Result,
11 item::{Item, Link},
12};
13use chrono::{DateTime, Utc};
14
15/// Builder for creating STAC Catalogs.
16#[derive(Debug, Clone)]
17pub struct CatalogBuilder {
18 catalog: Catalog,
19}
20
21impl CatalogBuilder {
22 /// Creates a new CatalogBuilder.
23 ///
24 /// # Arguments
25 ///
26 /// * `id` - Unique identifier for the catalog
27 /// * `description` - Description of the catalog
28 ///
29 /// # Returns
30 ///
31 /// A new CatalogBuilder instance
32 pub fn new(id: impl Into<String>, description: impl Into<String>) -> Self {
33 Self {
34 catalog: Catalog::new(id, description),
35 }
36 }
37
38 /// Sets the title of the catalog.
39 ///
40 /// # Arguments
41 ///
42 /// * `title` - Title for the catalog
43 ///
44 /// # Returns
45 ///
46 /// Self for method chaining
47 pub fn title(mut self, title: impl Into<String>) -> Self {
48 self.catalog = self.catalog.with_title(title);
49 self
50 }
51
52 /// Adds a link to the catalog.
53 ///
54 /// # Arguments
55 ///
56 /// * `href` - URI to the linked resource
57 /// * `rel` - Relationship type
58 ///
59 /// # Returns
60 ///
61 /// Self for method chaining
62 pub fn link(mut self, href: impl Into<String>, rel: impl Into<String>) -> Self {
63 self.catalog = self.catalog.add_link(Link::new(href, rel));
64 self
65 }
66
67 /// Adds a self link to the catalog.
68 ///
69 /// # Arguments
70 ///
71 /// * `href` - URI to self
72 ///
73 /// # Returns
74 ///
75 /// Self for method chaining
76 pub fn self_link(self, href: impl Into<String>) -> Self {
77 self.link(href, "self")
78 }
79
80 /// Adds a root link to the catalog.
81 ///
82 /// # Arguments
83 ///
84 /// * `href` - URI to root catalog
85 ///
86 /// # Returns
87 ///
88 /// Self for method chaining
89 pub fn root_link(self, href: impl Into<String>) -> Self {
90 self.link(href, "root")
91 }
92
93 /// Adds a child link to the catalog.
94 ///
95 /// # Arguments
96 ///
97 /// * `href` - URI to child catalog or collection
98 ///
99 /// # Returns
100 ///
101 /// Self for method chaining
102 pub fn child_link(self, href: impl Into<String>) -> Self {
103 self.link(href, "child")
104 }
105
106 /// Adds an extension to the catalog.
107 ///
108 /// # Arguments
109 ///
110 /// * `extension` - Extension schema URI
111 ///
112 /// # Returns
113 ///
114 /// Self for method chaining
115 pub fn extension(mut self, extension: impl Into<String>) -> Self {
116 self.catalog = self.catalog.add_extension(extension);
117 self
118 }
119
120 /// Builds the catalog.
121 ///
122 /// # Returns
123 ///
124 /// The constructed Catalog
125 pub fn build(self) -> Result<Catalog> {
126 self.catalog.validate()?;
127 Ok(self.catalog)
128 }
129}
130
131/// Builder for creating STAC Collections.
132#[derive(Debug, Clone)]
133pub struct CollectionBuilder {
134 collection: Collection,
135}
136
137impl CollectionBuilder {
138 /// Creates a new CollectionBuilder.
139 ///
140 /// # Arguments
141 ///
142 /// * `id` - Unique identifier for the collection
143 /// * `description` - Description of the collection
144 /// * `license` - License identifier or URL
145 ///
146 /// # Returns
147 ///
148 /// A new CollectionBuilder instance
149 pub fn new(
150 id: impl Into<String>,
151 description: impl Into<String>,
152 license: impl Into<String>,
153 ) -> Self {
154 Self {
155 collection: Collection::new(id, description, license),
156 }
157 }
158
159 /// Sets the title of the collection.
160 ///
161 /// # Arguments
162 ///
163 /// * `title` - Title for the collection
164 ///
165 /// # Returns
166 ///
167 /// Self for method chaining
168 pub fn title(mut self, title: impl Into<String>) -> Self {
169 self.collection = self.collection.with_title(title);
170 self
171 }
172
173 /// Sets the keywords of the collection.
174 ///
175 /// # Arguments
176 ///
177 /// * `keywords` - Vector of keywords
178 ///
179 /// # Returns
180 ///
181 /// Self for method chaining
182 pub fn keywords(mut self, keywords: Vec<String>) -> Self {
183 self.collection = self.collection.with_keywords(keywords);
184 self
185 }
186
187 /// Adds a provider to the collection.
188 ///
189 /// # Arguments
190 ///
191 /// * `name` - Provider name
192 ///
193 /// # Returns
194 ///
195 /// Self for method chaining
196 pub fn provider(mut self, name: impl Into<String>) -> Self {
197 self.collection = self.collection.add_provider(Provider::new(name));
198 self
199 }
200
201 /// Sets the spatial extent of the collection.
202 ///
203 /// # Arguments
204 ///
205 /// * `west` - Western longitude
206 /// * `south` - Southern latitude
207 /// * `east` - Eastern longitude
208 /// * `north` - Northern latitude
209 ///
210 /// # Returns
211 ///
212 /// Self for method chaining
213 pub fn spatial_extent(mut self, west: f64, south: f64, east: f64, north: f64) -> Self {
214 self.collection = self
215 .collection
216 .with_spatial_extent(vec![west, south, east, north]);
217 self
218 }
219
220 /// Sets the temporal extent of the collection.
221 ///
222 /// # Arguments
223 ///
224 /// * `start` - Start datetime (None for open start)
225 /// * `end` - End datetime (None for open end)
226 ///
227 /// # Returns
228 ///
229 /// Self for method chaining
230 pub fn temporal_extent(
231 mut self,
232 start: Option<DateTime<Utc>>,
233 end: Option<DateTime<Utc>>,
234 ) -> Self {
235 self.collection = self.collection.with_temporal_extent(start, end);
236 self
237 }
238
239 /// Adds a link to the collection.
240 ///
241 /// # Arguments
242 ///
243 /// * `href` - URI to the linked resource
244 /// * `rel` - Relationship type
245 ///
246 /// # Returns
247 ///
248 /// Self for method chaining
249 pub fn link(mut self, href: impl Into<String>, rel: impl Into<String>) -> Self {
250 self.collection = self.collection.add_link(Link::new(href, rel));
251 self
252 }
253
254 /// Adds a self link to the collection.
255 ///
256 /// # Arguments
257 ///
258 /// * `href` - URI to self
259 ///
260 /// # Returns
261 ///
262 /// Self for method chaining
263 pub fn self_link(self, href: impl Into<String>) -> Self {
264 self.link(href, "self")
265 }
266
267 /// Adds an extension to the collection.
268 ///
269 /// # Arguments
270 ///
271 /// * `extension` - Extension schema URI
272 ///
273 /// # Returns
274 ///
275 /// Self for method chaining
276 pub fn extension(mut self, extension: impl Into<String>) -> Self {
277 self.collection = self.collection.add_extension(extension);
278 self
279 }
280
281 /// Builds the collection.
282 ///
283 /// # Returns
284 ///
285 /// The constructed Collection
286 pub fn build(self) -> Result<Collection> {
287 self.collection.validate()?;
288 Ok(self.collection)
289 }
290}
291
292/// Builder for creating STAC Items.
293#[derive(Debug, Clone)]
294pub struct ItemBuilder {
295 item: Item,
296}
297
298impl ItemBuilder {
299 /// Creates a new ItemBuilder.
300 ///
301 /// # Arguments
302 ///
303 /// * `id` - Unique identifier for the item
304 ///
305 /// # Returns
306 ///
307 /// A new ItemBuilder instance
308 pub fn new(id: impl Into<String>) -> Self {
309 Self {
310 item: Item::new(id),
311 }
312 }
313
314 /// Sets the geometry of the item.
315 ///
316 /// # Arguments
317 ///
318 /// * `geometry` - GeoJSON geometry
319 ///
320 /// # Returns
321 ///
322 /// Self for method chaining
323 pub fn geometry(mut self, geometry: geojson::Geometry) -> Self {
324 self.item = self.item.with_geometry(geometry);
325 self
326 }
327
328 /// Sets the bounding box of the item.
329 ///
330 /// # Arguments
331 ///
332 /// * `west` - Western longitude
333 /// * `south` - Southern latitude
334 /// * `east` - Eastern longitude
335 /// * `north` - Northern latitude
336 ///
337 /// # Returns
338 ///
339 /// Self for method chaining
340 pub fn bbox(mut self, west: f64, south: f64, east: f64, north: f64) -> Self {
341 self.item = self.item.with_bbox(vec![west, south, east, north]);
342 self
343 }
344
345 /// Sets the datetime of the item.
346 ///
347 /// # Arguments
348 ///
349 /// * `datetime` - Date and time in UTC
350 ///
351 /// # Returns
352 ///
353 /// Self for method chaining
354 pub fn datetime(mut self, datetime: DateTime<Utc>) -> Self {
355 self.item = self.item.with_datetime(datetime);
356 self
357 }
358
359 /// Sets the datetime range of the item.
360 ///
361 /// # Arguments
362 ///
363 /// * `start` - Start date and time in UTC
364 /// * `end` - End date and time in UTC
365 ///
366 /// # Returns
367 ///
368 /// Self for method chaining
369 pub fn datetime_range(mut self, start: DateTime<Utc>, end: DateTime<Utc>) -> Self {
370 self.item = self.item.with_datetime_range(start, end);
371 self
372 }
373
374 /// Adds an asset to the item.
375 ///
376 /// # Arguments
377 ///
378 /// * `key` - Asset key
379 /// * `asset` - Asset to add
380 ///
381 /// # Returns
382 ///
383 /// Self for method chaining
384 pub fn asset(mut self, key: impl Into<String>, asset: Asset) -> Self {
385 self.item = self.item.add_asset(key, asset);
386 self
387 }
388
389 /// Adds a simple asset with just an href.
390 ///
391 /// # Arguments
392 ///
393 /// * `key` - Asset key
394 /// * `href` - URI to the asset
395 ///
396 /// # Returns
397 ///
398 /// Self for method chaining
399 pub fn simple_asset(self, key: impl Into<String>, href: impl Into<String>) -> Self {
400 self.asset(key, Asset::new(href))
401 }
402
403 /// Adds a link to the item.
404 ///
405 /// # Arguments
406 ///
407 /// * `href` - URI to the linked resource
408 /// * `rel` - Relationship type
409 ///
410 /// # Returns
411 ///
412 /// Self for method chaining
413 pub fn link(mut self, href: impl Into<String>, rel: impl Into<String>) -> Self {
414 self.item = self.item.add_link(Link::new(href, rel));
415 self
416 }
417
418 /// Sets the collection ID.
419 ///
420 /// # Arguments
421 ///
422 /// * `collection_id` - Collection identifier
423 ///
424 /// # Returns
425 ///
426 /// Self for method chaining
427 pub fn collection(mut self, collection_id: impl Into<String>) -> Self {
428 self.item = self.item.with_collection(collection_id);
429 self
430 }
431
432 /// Adds an extension to the item.
433 ///
434 /// # Arguments
435 ///
436 /// * `extension` - Extension schema URI
437 ///
438 /// # Returns
439 ///
440 /// Self for method chaining
441 pub fn extension(mut self, extension: impl Into<String>) -> Self {
442 self.item = self.item.add_extension(extension);
443 self
444 }
445
446 /// Sets a property value.
447 ///
448 /// # Arguments
449 ///
450 /// * `key` - Property key
451 /// * `value` - Property value
452 ///
453 /// # Returns
454 ///
455 /// Self for method chaining
456 pub fn property(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
457 self.item
458 .properties
459 .additional_fields
460 .insert(key.into(), value);
461 self
462 }
463
464 /// Builds the item.
465 ///
466 /// # Returns
467 ///
468 /// The constructed Item
469 pub fn build(self) -> Result<Item> {
470 self.item.validate()?;
471 Ok(self.item)
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_catalog_builder() {
481 let catalog = CatalogBuilder::new("test-catalog", "A test catalog")
482 .title("Test Catalog")
483 .self_link("https://example.com/catalog.json")
484 .child_link("https://example.com/collection.json")
485 .build();
486
487 assert!(catalog.is_ok());
488 let catalog = catalog.expect("Failed to build catalog");
489 assert_eq!(catalog.id, "test-catalog");
490 assert_eq!(catalog.title, Some("Test Catalog".to_string()));
491 assert_eq!(catalog.links.len(), 2);
492 }
493
494 #[test]
495 fn test_collection_builder() {
496 let now = Utc::now();
497 let collection = CollectionBuilder::new("test-collection", "A test collection", "MIT")
498 .title("Test Collection")
499 .keywords(vec!["test".to_string(), "example".to_string()])
500 .provider("Test Provider")
501 .spatial_extent(-180.0, -90.0, 180.0, 90.0)
502 .temporal_extent(Some(now), None)
503 .build();
504
505 assert!(collection.is_ok());
506 let collection = collection.expect("Failed to build collection");
507 assert_eq!(collection.id, "test-collection");
508 assert_eq!(collection.title, Some("Test Collection".to_string()));
509 }
510
511 #[test]
512 fn test_item_builder() {
513 let now = Utc::now();
514 let geometry = geojson::Geometry::new_point([-122.0, 37.0]);
515
516 let item = ItemBuilder::new("test-item")
517 .geometry(geometry)
518 .bbox(-122.5, 36.5, -121.5, 37.5)
519 .datetime(now)
520 .simple_asset("data", "https://example.com/data.tif")
521 .collection("test-collection")
522 .build();
523
524 assert!(item.is_ok());
525 let item = item.expect("Failed to build item");
526 assert_eq!(item.id, "test-item");
527 assert_eq!(item.assets.len(), 1);
528 assert_eq!(item.collection, Some("test-collection".to_string()));
529 }
530
531 #[test]
532 fn test_item_builder_with_properties() {
533 let now = Utc::now();
534
535 let item = ItemBuilder::new("test-item")
536 .datetime(now)
537 .property("cloud_cover", serde_json::json!(10.5))
538 .property("platform", serde_json::json!("sentinel-2a"))
539 .build();
540
541 assert!(item.is_ok());
542 let item = item.expect("Failed to build item");
543 assert_eq!(
544 item.properties.additional_fields.get("cloud_cover"),
545 Some(&serde_json::json!(10.5))
546 );
547 }
548}