Skip to content

Commit 20b2823

Browse files
authored
feat: provide API for using your own object store (#730)
Closes #643 I'm still not convinced by my decision to use `Format` as the base for all this IO, but at least it makes clear what format you're expecting to be reading/writing stuff in so 🤷🏼? No immediate plans to refactor ... In support of stac-utils/rustac-py#117
1 parent c8f20c3 commit 20b2823

File tree

3 files changed

+75
-14
lines changed

3 files changed

+75
-14
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref }}
11-
cancel-in-progress: true
11+
cancel-in-progress: false
1212

1313
env:
1414
CARGO_TERM_COLOR: always

crates/core/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1212
- `Clone` for `Container` ([#666](https://github.com/stac-utils/rustac/pull/666))
1313
- `Serialize` for `Container` ([#667](https://github.com/stac-utils/rustac/pull/667))
1414
- More permissive datetime interval parsing ([#715](https://github.com/stac-utils/rustac/pull/715))
15+
- `Format` methods for providing your own object store ([#730](https://github.com/stac-utils/rustac/pull/730))
1516

1617
### Changed
1718

crates/core/src/format.rs

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,14 @@ impl Format {
163163
let href = href.into();
164164
match href.clone().realize() {
165165
RealizedHref::Url(url) => {
166-
use object_store::ObjectStore;
167-
168166
let (object_store, path) = parse_url_opts(&url, options)?;
169-
tracing::debug!("getting {self} from {url} with object store");
170-
let get_result = object_store.get(&path).await.map_err(|err| Error::Get {
171-
href,
172-
message: err.to_string(),
173-
})?;
174-
let mut value: T = self.from_bytes(get_result.bytes().await?)?;
167+
let mut value: T =
168+
self.get_store(object_store.into(), path)
169+
.await
170+
.map_err(|err| Error::Get {
171+
href,
172+
message: err.to_string(),
173+
})?;
175174
*value.self_href_mut() = Some(Href::Url(url));
176175
Ok(value)
177176
}
@@ -188,6 +187,23 @@ impl Format {
188187
}
189188
}
190189

190+
/// Gets a STAC value from an object store.
191+
#[cfg(feature = "object-store")]
192+
pub async fn get_store<T>(
193+
&self,
194+
object_store: std::sync::Arc<dyn object_store::ObjectStore>,
195+
path: impl Into<object_store::path::Path>,
196+
) -> Result<T>
197+
where
198+
T: SelfHref + FromJson + FromNdjson + FromGeoparquet,
199+
{
200+
let path = path.into();
201+
tracing::debug!("getting {self} from {path} with object store");
202+
let get_result = object_store.get(&path).await?;
203+
let value: T = self.from_bytes(get_result.bytes().await?)?;
204+
Ok(value)
205+
}
206+
191207
/// Writes a STAC value to the provided path.
192208
///
193209
/// # Examples
@@ -257,17 +273,31 @@ impl Format {
257273
{
258274
let href = href.to_string();
259275
if let Ok(url) = url::Url::parse(&href) {
260-
use object_store::ObjectStore;
261-
262276
let (object_store, path) = parse_url_opts(&url, options)?;
263-
let bytes = self.into_vec(value)?;
264-
let put_result = object_store.put(&path, bytes.into()).await?;
265-
Ok(Some(put_result))
277+
self.put_store(object_store.into(), path, value)
278+
.await
279+
.map(Some)
266280
} else {
267281
self.write(href, value).map(|_| None)
268282
}
269283
}
270284

285+
/// Puts a STAC value into an object store.
286+
#[cfg(feature = "object-store")]
287+
pub async fn put_store<T>(
288+
&self,
289+
object_store: std::sync::Arc<dyn object_store::ObjectStore>,
290+
path: impl Into<object_store::path::Path>,
291+
value: T,
292+
) -> Result<object_store::PutResult>
293+
where
294+
T: ToJson + ToNdjson + IntoGeoparquet,
295+
{
296+
let bytes = self.into_vec(value)?;
297+
let put_result = object_store.put(&path.into(), bytes.into()).await?;
298+
Ok(put_result)
299+
}
300+
271301
/// Returns the default JSON format (compact).
272302
pub fn json() -> Format {
273303
Format::Json(false)
@@ -384,6 +414,7 @@ impl FromStr for Format {
384414
#[cfg(test)]
385415
mod tests {
386416
use super::Format;
417+
use crate::Item;
387418
use crate::geoparquet::Compression;
388419

389420
#[test]
@@ -425,4 +456,33 @@ mod tests {
425456
Format::infer_from_href("out.parquet").unwrap()
426457
);
427458
}
459+
460+
#[tokio::test]
461+
#[cfg(feature = "object-store")]
462+
async fn prefix_store_read() {
463+
use std::sync::Arc;
464+
465+
let object_store =
466+
object_store::local::LocalFileSystem::new_with_prefix("examples").unwrap();
467+
let _: Item = Format::json()
468+
.get_store(Arc::new(object_store), "simple-item.json")
469+
.await
470+
.unwrap();
471+
}
472+
473+
#[tokio::test]
474+
#[cfg(feature = "object-store")]
475+
async fn store_write() {
476+
use object_store::ObjectStore;
477+
use std::sync::Arc;
478+
479+
let object_store = Arc::new(object_store::memory::InMemory::new());
480+
let item = Item::new("an-id");
481+
let _ = Format::json()
482+
.put_store(object_store.clone(), "item.json", item)
483+
.await
484+
.unwrap();
485+
let get_result = object_store.get(&"item.json".into()).await.unwrap();
486+
let _: Item = serde_json::from_slice(&get_result.bytes().await.unwrap()).unwrap();
487+
}
428488
}

0 commit comments

Comments
 (0)