Skip to content

Commit 970c488

Browse files
authored
feat: set self href when getting from a store (#754)
1 parent 47f12b3 commit 970c488

File tree

2 files changed

+87
-78
lines changed

2 files changed

+87
-78
lines changed

crates/io/src/store.rs

Lines changed: 85 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,86 +2,87 @@ use crate::{Format, Readable, Result, Writeable};
22
use object_store::{ObjectStore, ObjectStoreScheme, PutResult, path::Path};
33
use stac::Href;
44
use std::sync::Arc;
5+
use url::Url;
56

67
/// Parses an href into a [StacStore] and a [Path].
7-
pub fn parse_href(href: impl AsRef<Href>) -> Result<(StacStore, Path)> {
8+
pub fn parse_href(href: impl Into<Href>) -> Result<(StacStore, Path)> {
89
parse_href_opts(href, [] as [(&str, &str); 0])
910
}
1011

1112
/// Parses an href and options into [StacStore] and a [Path].
1213
///
1314
/// Relative string hrefs are made absolute `file://` hrefs relative to the current directory.`
14-
pub fn parse_href_opts<I, K, V>(href: impl AsRef<Href>, options: I) -> Result<(StacStore, Path)>
15+
pub fn parse_href_opts<I, K, V>(href: impl Into<Href>, options: I) -> Result<(StacStore, Path)>
1516
where
1617
I: IntoIterator<Item = (K, V)>,
1718
K: AsRef<str>,
1819
V: Into<String>,
1920
{
21+
let mut url = match href.into() {
22+
Href::Url(url) => url,
23+
Href::String(s) => {
24+
let s = if s.starts_with("/") {
25+
format!("file://{s}")
26+
} else {
27+
let path_buf = std::fs::canonicalize(s)?;
28+
format!("file://{}", path_buf.display())
29+
};
30+
Url::parse(&s)?
31+
}
32+
};
2033
let parse = || -> Result<(Box<dyn ObjectStore>, Path)> {
21-
match href.as_ref() {
22-
Href::Url(url) => {
23-
tracing::debug!("parsing url={url}");
24-
// It's technically inefficient to parse it twice, but we're doing this to
25-
// then do IO so who cares.
26-
let (scheme, path) =
27-
ObjectStoreScheme::parse(url).map_err(object_store::Error::from)?;
28-
29-
#[cfg(feature = "store-aws")]
30-
if matches!(scheme, ObjectStoreScheme::AmazonS3) {
31-
let mut builder = object_store::aws::AmazonS3Builder::from_env();
32-
for (key, value) in options {
33-
builder = builder.with_config(key.as_ref().parse()?, value);
34-
}
35-
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
36-
}
34+
tracing::debug!("parsing url={url}");
35+
// It's technically inefficient to parse it twice, but we're doing this to
36+
// then do IO so who cares.
37+
let (scheme, path) = ObjectStoreScheme::parse(&url).map_err(object_store::Error::from)?;
3738

38-
#[cfg(feature = "store-azure")]
39-
if matches!(scheme, ObjectStoreScheme::AmazonS3) {
40-
let mut builder = object_store::azure::MicrosoftAzureBuilder::from_env();
41-
for (key, value) in options {
42-
builder = builder.with_config(key.as_ref().parse()?, value);
43-
}
44-
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
45-
}
46-
47-
#[cfg(feature = "store-gcp")]
48-
if matches!(scheme, ObjectStoreScheme::GoogleCloudStorage) {
49-
let mut builder = object_store::gcp::GoogleCloudStorageBuilder::from_env();
50-
for (key, value) in options {
51-
builder = builder.with_config(key.as_ref().parse()?, value);
52-
}
53-
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
54-
}
39+
#[cfg(feature = "store-aws")]
40+
if matches!(scheme, ObjectStoreScheme::AmazonS3) {
41+
let mut builder = object_store::aws::AmazonS3Builder::from_env();
42+
for (key, value) in options {
43+
builder = builder.with_config(key.as_ref().parse()?, value);
44+
}
45+
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
46+
}
5547

56-
let pair = object_store::parse_url_opts(url, options)?;
57-
Ok(pair)
48+
#[cfg(feature = "store-azure")]
49+
if matches!(scheme, ObjectStoreScheme::AmazonS3) {
50+
let mut builder = object_store::azure::MicrosoftAzureBuilder::from_env();
51+
for (key, value) in options {
52+
builder = builder.with_config(key.as_ref().parse()?, value);
5853
}
59-
Href::String(s) => {
60-
if s.starts_with("/") {
61-
let pair =
62-
object_store::parse_url_opts(&format!("file://{s}").parse()?, options)?;
63-
Ok(pair)
64-
} else {
65-
let s = std::env::current_dir()?.join(s);
66-
let pair = object_store::parse_url_opts(
67-
&format!("file://{}", s.display()).parse()?,
68-
options,
69-
)?;
70-
Ok(pair)
71-
}
54+
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
55+
}
56+
57+
#[cfg(feature = "store-gcp")]
58+
if matches!(scheme, ObjectStoreScheme::GoogleCloudStorage) {
59+
let mut builder = object_store::gcp::GoogleCloudStorageBuilder::from_env();
60+
for (key, value) in options {
61+
builder = builder.with_config(key.as_ref().parse()?, value);
7262
}
63+
return Ok((Box::new(builder.with_url(url.to_string()).build()?), path));
7364
}
65+
66+
let pair = object_store::parse_url_opts(&url, options)?;
67+
Ok(pair)
7468
};
7569
let (store, path) = parse()?;
76-
Ok((store.into(), path))
70+
url.set_path("");
71+
Ok((StacStore::new(Arc::new(store), url), path))
7772
}
7873

7974
/// Reads STAC from an [ObjectStore].
8075
#[derive(Debug)]
81-
pub struct StacStore(Arc<dyn ObjectStore>);
76+
pub struct StacStore {
77+
store: Arc<dyn ObjectStore>,
78+
root: Url,
79+
}
8280

8381
impl StacStore {
84-
/// Creates a new [StacStore] from an [ObjectStore].
82+
/// Creates a new [StacStore] from an [ObjectStore] and a root href.
83+
///
84+
/// The root href is used to set the self href on all read STAC values,
85+
/// since we can't get that from the store.
8586
///
8687
/// # Examples
8788
///
@@ -90,10 +91,13 @@ impl StacStore {
9091
/// use stac_io::StacStore;
9192
/// use std::sync::Arc;
9293
///
93-
/// let stac_store = StacStore::new(Arc::new(LocalFileSystem::new()));
94+
/// let stac_store = StacStore::new(Arc::new(LocalFileSystem::new()), "file://".parse().unwrap());
9495
/// ```
95-
pub fn new(store: Arc<dyn ObjectStore>) -> StacStore {
96-
StacStore(Arc::new(store))
96+
pub fn new(store: Arc<dyn ObjectStore>, root: Url) -> StacStore {
97+
StacStore {
98+
store: Arc::new(store),
99+
root,
100+
}
97101
}
98102

99103
/// Gets a STAC value from the store.
@@ -127,9 +131,10 @@ impl StacStore {
127131
T: Readable,
128132
{
129133
let path = path.into();
130-
let get_result = self.0.get(&path).await?;
134+
let get_result = self.store.get(&path).await?;
131135
let bytes = get_result.bytes().await?;
132-
let value: T = format.from_bytes(bytes)?;
136+
let mut value: T = format.from_bytes(bytes)?;
137+
value.set_self_href(self.root.join(path.as_ref())?);
133138
Ok(value)
134139
}
135140

@@ -155,31 +160,37 @@ impl StacStore {
155160
{
156161
let path = path.into();
157162
let bytes = format.into_vec(value)?;
158-
let put_result = self.0.put(&path, bytes.into()).await?;
163+
let put_result = self.store.put(&path, bytes.into()).await?;
159164
Ok(put_result)
160165
}
161166
}
162167

163-
impl<T> From<T> for StacStore
164-
where
165-
T: ObjectStore,
166-
{
167-
fn from(value: T) -> Self {
168-
StacStore(Arc::new(value))
169-
}
170-
}
171-
172168
#[cfg(test)]
173169
mod tests {
174-
use super::StacStore;
175-
use object_store::local::LocalFileSystem;
176-
use stac::Item;
170+
use stac::{Item, SelfHref};
177171

178172
#[tokio::test]
179173
async fn get_local() {
180-
let store = StacStore::from(
181-
LocalFileSystem::new_with_prefix(std::env::current_dir().unwrap()).unwrap(),
174+
let (store, path) = super::parse_href("examples/simple-item.json").unwrap();
175+
assert_eq!(
176+
path,
177+
std::fs::canonicalize("examples/simple-item.json")
178+
.unwrap()
179+
.to_string_lossy()
180+
.into_owned()
181+
.strip_prefix("/")
182+
.unwrap()
183+
.into()
182184
);
183-
let _: Item = store.get("examples/simple-item.json").await.unwrap();
185+
let item: Item = store.get(path).await.unwrap();
186+
assert_eq!(
187+
item.self_href().unwrap().to_string(),
188+
format!(
189+
"file://{}",
190+
std::fs::canonicalize("examples/simple-item.json")
191+
.unwrap()
192+
.display()
193+
)
194+
)
184195
}
185196
}

crates/io/tests/aws.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
use stac::Catalog;
2-
use stac_io::StacStore;
32

43
#[test]
54
fn read_from_s3() {
65
tokio_test::block_on(async {
7-
let (store, path) = object_store::parse_url_opts(
8-
&"s3://nz-elevation/catalog.json".parse().unwrap(),
6+
let (store, path) = stac_io::parse_href_opts(
7+
"s3://nz-elevation/catalog.json",
98
[("skip_signature", "true"), ("region", "ap-southeast-2")],
109
)
1110
.unwrap();
12-
let store = StacStore::from(store);
1311
let _: Catalog = store.get(path).await.unwrap();
1412
});
1513
}

0 commit comments

Comments
 (0)