Skip to main content

oxigdal/
cloud_detect.rs

1//! Cloud URI detection and transparent dispatch for [`crate::Dataset::open`].
2//!
3//! This module provides lightweight helpers that classify a path string as a
4//! cloud URI (`s3://`, `gs://`, `az://`, `http://`, `https://`) and, when the
5//! `cloud` feature is enabled, open a stub [`crate::Dataset`] descriptor that
6//! records the cloud path for subsequent streaming operations.
7//!
8//! When `cloud` is **not** enabled `open_cloud_dataset` returns a
9//! [`crate::OxiGdalError::NotSupported`] error with a helpful message.
10
11use crate::{Dataset, OxiGdalError, Result};
12#[cfg(feature = "cloud")]
13use crate::{DatasetFormat, DatasetInfo};
14
15// ─── Detection ───────────────────────────────────────────────────────────────
16
17/// Returns `true` if `path` starts with a known cloud storage URI scheme.
18///
19/// Recognised schemes: `s3://`, `gs://`, `az://`, `abfs://`, `http://`, `https://`.
20///
21/// # Examples
22///
23/// ```rust
24/// assert!(oxigdal::cloud_detect::is_cloud_uri("s3://my-bucket/data.tif"));
25/// assert!(oxigdal::cloud_detect::is_cloud_uri("gs://bucket/path/to/file.geojson"));
26/// assert!(!oxigdal::cloud_detect::is_cloud_uri("/local/path/to/file.tif"));
27/// assert!(!oxigdal::cloud_detect::is_cloud_uri("relative/path.tif"));
28/// ```
29pub fn is_cloud_uri(path: &str) -> bool {
30    path.starts_with("s3://")
31        || path.starts_with("gs://")
32        || path.starts_with("az://")
33        || path.starts_with("abfs://")
34        || path.starts_with("http://")
35        || path.starts_with("https://")
36}
37
38// ─── Dispatch ─────────────────────────────────────────────────────────────────
39
40/// Open a cloud-hosted dataset, returning a lightweight [`Dataset`] descriptor.
41///
42/// When the `cloud` feature is enabled this function constructs a `Dataset`
43/// whose metadata is inferred from the URL path extension (e.g. `"*.tif"` →
44/// [`DatasetFormat::GeoTiff`]).  Actual pixel/feature data must be fetched
45/// via the `oxigdal-cloud` streaming interface.
46///
47/// When the `cloud` feature is **not** enabled, always returns
48/// [`OxiGdalError::NotSupported`].
49///
50/// # Errors
51///
52/// - [`OxiGdalError::NotSupported`] — `cloud` feature not compiled in.
53/// - [`OxiGdalError::InvalidParameter`] — `path` is not a valid cloud URI.
54pub fn open_cloud_dataset(path: &str) -> Result<Dataset> {
55    if !is_cloud_uri(path) {
56        return Err(OxiGdalError::InvalidParameter {
57            parameter: "path",
58            message: format!("'{}' is not a recognised cloud URI", path),
59        });
60    }
61
62    #[cfg(feature = "cloud")]
63    {
64        open_cloud_with_feature(path)
65    }
66
67    #[cfg(not(feature = "cloud"))]
68    {
69        let _ = path;
70        Err(OxiGdalError::NotSupported {
71            operation: format!(
72                "Dataset::open(\"{path}\") — cloud URIs require the 'cloud' feature flag",
73            ),
74        })
75    }
76}
77
78/// Feature-gated inner implementation that creates the cloud Dataset stub.
79#[cfg(feature = "cloud")]
80fn open_cloud_with_feature(path: &str) -> Result<Dataset> {
81    // Infer format from the URL extension for metadata purposes.
82    let guessed_format = DatasetFormat::from_extension(path);
83
84    let info = DatasetInfo {
85        format: guessed_format,
86        path: Some(path.to_string()),
87        width: None,
88        height: None,
89        band_count: 0,
90        layer_count: 0,
91        crs: None,
92        geotransform: None,
93        feature_count: None,
94        bounds: None,
95    };
96
97    // Validate that the oxigdal-cloud crate can at least parse the URI scheme
98    // (scheme-level parsing only, no network I/O).
99    // On error we surface the problem early with a good message.
100    validate_cloud_uri(path)?;
101
102    Ok(crate::Dataset::from_info(path.to_string(), info))
103}
104
105/// Validate the cloud URI via the oxigdal-cloud crate (scheme-level check only).
106///
107/// Returns `Ok(())` if the URI is well-formed for the detected scheme.
108#[cfg(feature = "cloud")]
109fn validate_cloud_uri(path: &str) -> Result<()> {
110    // Use oxigdal-cloud's URL parser for scheme validation.
111    // `CloudBackend::from_url` does not initiate network I/O — it only parses.
112    let _backend = oxigdal_cloud::CloudBackend::from_url(path).map_err(|e| {
113        OxiGdalError::InvalidParameter {
114            parameter: "path",
115            message: format!("invalid cloud URI '{}': {e}", path),
116        }
117    })?;
118    Ok(())
119}