Skip to content

Commit acfaff4

Browse files
committed
refactor(core)!: move a bunch of io around
1 parent 6cba20d commit acfaff4

File tree

25 files changed

+899
-1571
lines changed

25 files changed

+899
-1571
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target
22
Cargo.lock
33
dist/
4+
pyrightconfig.json

cli/src/args/item.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::{Result, Value};
33
use stac::{item::Builder, Asset};
44
use std::path::Path;
55
use tokio::sync::mpsc::Sender;
6+
use url::Url;
67

78
/// Arguments for the `item` subcommand.
89
#[derive(clap::Args, Debug)]
@@ -33,14 +34,12 @@ pub(crate) struct Args {
3334

3435
impl Run for Args {
3536
async fn run(self, _: Input, _: Option<Sender<Value>>) -> Result<Option<Value>> {
36-
let (id, href): (Option<String>, Option<String>) = if stac::href_to_url(&self.id_or_href)
37-
.is_none()
38-
&& !Path::new(&self.id_or_href).exists()
39-
{
40-
(Some(self.id_or_href), None)
41-
} else {
42-
(None, Some(self.id_or_href))
43-
};
37+
let (id, href): (Option<String>, Option<String>) =
38+
if Url::parse(&self.id_or_href).is_err() && !Path::new(&self.id_or_href).exists() {
39+
(Some(self.id_or_href), None)
40+
} else {
41+
(None, Some(self.id_or_href))
42+
};
4443
let id = id
4544
.or_else(|| {
4645
Path::new(href.as_ref().expect("if id is none, href should exist"))

cli/src/args/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ mod validate;
1212

1313
use crate::{input::Input, options::KeyValue, output::Output, Result, Value};
1414
use clap::Parser;
15-
use stac::Format;
16-
use tokio::sync::mpsc::Sender;
17-
use tokio::task::JoinHandle;
15+
use stac::io::Format;
16+
use tokio::{sync::mpsc::Sender, task::JoinHandle};
1817
use tracing::metadata::Level;
1918

2019
const BUFFER: usize = 100;

cli/src/args/search.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,22 +185,21 @@ impl Run for Args {
185185
} else {
186186
Some(self.collections)
187187
};
188-
#[cfg(feature = "duckdb")]
188+
#[cfg(all(feature = "duckdb", feature = "geoparquet"))]
189189
{
190-
if self
191-
.duckdb
192-
.unwrap_or_else(|| stac::geoparquet::has_extension(&self.href))
193-
{
190+
if self.duckdb.unwrap_or_else(|| {
191+
matches!(
192+
stac::io::Format::infer_from_href(&self.href),
193+
Some(stac::io::Format::Geoparquet(_))
194+
)
195+
}) {
194196
search_geoparquet(self.href, search, stream, self.max_items).await
195197
} else {
196198
search_api(self.href, search, stream, self.max_items).await
197199
}
198200
}
199201
#[cfg(not(feature = "duckdb"))]
200202
{
201-
if stac::geoparquet::has_extension(&self.href) {
202-
tracing::warn!("'{}' has a geoparquet extension, but the `duckdb` feature is not enabled — attempting to search with an ApiClient", self.href);
203-
}
204203
search_api(self.href, search, stream, self.max_items).await
205204
}
206205
}

cli/src/input.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use std::io::Read;
2+
13
use crate::{options::Options, Error, Result};
2-
use stac::{io::Config, Format, Value};
4+
use stac::{io::Format, Value};
35

46
/// The input to a CLI run.
57
#[derive(Debug, Default)]
68
pub(crate) struct Input {
7-
config: Config,
9+
format: Option<Format>,
10+
options: Options,
811
href: Option<String>,
912
}
1013

@@ -18,25 +21,34 @@ impl Input {
1821
let href = href
1922
.into()
2023
.and_then(|href| if href == "-" { None } else { Some(href) });
21-
let config = Config::new().format(format).options(options.into());
22-
Input { config, href }
24+
Input {
25+
format: format.into(),
26+
href,
27+
options: options.into(),
28+
}
2329
}
2430

2531
/// Creates a new input with the given href.
2632
pub(crate) fn with_href(&self, href: impl Into<Option<String>>) -> Input {
2733
Input {
28-
config: self.config.clone(),
34+
format: self.format,
2935
href: href.into(),
36+
options: self.options.clone(),
3037
}
3138
}
3239

3340
/// Gets a STAC value from the input.
3441
pub(crate) async fn get(&self) -> Result<Value> {
35-
if let Some(href) = self.href.as_ref() {
36-
self.config.get(href.clone()).await.map_err(Error::from)
42+
if let Some(href) = self.href.as_deref() {
43+
stac::io::get_format_opts(href, self.format, self.options.iter())
44+
.await
45+
.map_err(Error::from)
3746
} else {
38-
self.config
39-
.from_reader(std::io::stdin())
47+
let mut buf = Vec::new();
48+
let _ = std::io::stdin().read_to_end(&mut buf);
49+
self.format
50+
.unwrap_or_default()
51+
.from_bytes(buf.into())
4052
.map_err(Error::from)
4153
}
4254
}

cli/src/options.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ pub(crate) struct KeyValue {
1111
value: String,
1212
}
1313

14+
impl Options {
15+
pub(crate) fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
16+
self.0.iter().map(|kv| (kv.key.as_str(), kv.value.as_str()))
17+
}
18+
}
19+
1420
impl FromStr for KeyValue {
1521
type Err = Infallible;
1622
fn from_str(s: &str) -> Result<Self, Infallible> {

cli/src/output.rs

Lines changed: 11 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//! Structures for writing output data.
22
33
use crate::{options::Options, value::Value, Error, Result};
4-
use object_store::{local::LocalFileSystem, ObjectStore, PutResult};
5-
use stac::{io::Config, Format};
6-
use std::{io::IsTerminal, path::Path, pin::Pin};
4+
use object_store::PutResult;
5+
use stac::io::{Format, IntoFormattedBytes};
6+
use std::{path::Path, pin::Pin};
77
use tokio::{
88
fs::File,
99
io::{AsyncWrite, AsyncWriteExt},
@@ -15,7 +15,7 @@ pub(crate) struct Output {
1515
pub(crate) format: Format,
1616
href: Option<String>,
1717
stream: Pin<Box<dyn AsyncWrite + Send>>,
18-
config: Config,
18+
options: Options,
1919
}
2020

2121
impl Output {
@@ -26,10 +26,9 @@ impl Output {
2626
options: impl Into<Options>,
2727
create_parent_directories: bool,
2828
) -> Result<Output> {
29-
let mut format = format
29+
let format = format
3030
.or_else(|| href.as_deref().and_then(Format::infer_from_href))
3131
.unwrap_or_default();
32-
let config = Config::new().format(Some(format)).options(options.into());
3332
let stream = if let Some(href) = href.as_deref() {
3433
if let Ok(url) = Url::parse(href) {
3534
if url.scheme() == "file" {
@@ -39,53 +38,39 @@ impl Output {
3938
)
4039
.await?
4140
} else {
42-
Box::pin(config.buf_writer(&url)?)
41+
unimplemented!("streaming to object stores is not supported");
42+
// FIXME turn this into an actual error
4343
}
4444
} else {
4545
create_file_stream(href, create_parent_directories).await?
4646
}
4747
} else {
48-
if std::io::stdout().is_terminal() {
49-
format = format.pretty();
50-
}
5148
Box::pin(tokio::io::stdout())
5249
};
5350
Ok(Output {
5451
href,
5552
format,
5653
stream,
57-
config,
54+
options: options.into(),
5855
})
5956
}
6057

6158
/// Streams a value to the output
6259
pub(crate) async fn stream(&mut self, value: Value) -> Result<()> {
63-
let bytes = value.into_ndjson()?;
60+
let bytes = value.into_formatted_bytes(Format::NdJson)?;
6461
self.stream.write_all(&bytes).await?;
6562
self.stream.flush().await?;
6663
Ok(())
6764
}
6865

6966
/// Puts a value to the output.
7067
pub(crate) async fn put(&mut self, value: Value) -> Result<Option<PutResult>> {
71-
let bytes = match value {
72-
Value::Json(value) => self.format.json_to_vec(value)?,
73-
Value::Stac(value) => self.format.value_to_vec(value)?,
74-
};
7568
if let Some(href) = self.href.as_deref() {
76-
let (object_store, path): (Box<dyn ObjectStore>, _) = match Url::parse(href) {
77-
Ok(url) => self.config.object_store(&url)?,
78-
Err(_) => {
79-
let path = object_store::path::Path::from_filesystem_path(href)?;
80-
(Box::new(LocalFileSystem::new()), path)
81-
}
82-
};
83-
object_store
84-
.put(&path, bytes.into())
69+
stac::io::put_format_opts(href, value, self.format, self.options.iter())
8570
.await
86-
.map(Some)
8771
.map_err(Error::from)
8872
} else {
73+
let bytes = value.into_formatted_bytes(self.format)?;
8974
self.stream.write_all(&bytes).await?;
9075
self.stream.flush().await?;
9176
Ok(None)

cli/src/value.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{Error, Result};
22
use serde::Serialize;
3-
use stac::Format;
3+
use stac::io::{Format, IntoFormattedBytes};
44

55
/// An output value, which can either be a [serde_json::Value] or a [stac::Value].
66
#[derive(Debug, Serialize)]
@@ -13,15 +13,6 @@ pub enum Value {
1313
Json(serde_json::Value),
1414
}
1515

16-
impl Value {
17-
pub(crate) fn into_ndjson(self) -> Result<Vec<u8>> {
18-
match self {
19-
Value::Json(value) => Format::NdJson.json_to_vec(value).map_err(Error::from),
20-
Value::Stac(value) => Format::NdJson.value_to_vec(value).map_err(Error::from),
21-
}
22-
}
23-
}
24-
2516
impl From<stac::Value> for Value {
2617
fn from(value: stac::Value) -> Self {
2718
Value::Stac(value)
@@ -49,3 +40,12 @@ impl TryFrom<Value> for stac::Value {
4940
}
5041
}
5142
}
43+
44+
impl IntoFormattedBytes for Value {
45+
fn into_formatted_bytes(self, format: Format) -> stac::Result<Vec<u8>> {
46+
match self {
47+
Self::Json(value) => value.into_formatted_bytes(format),
48+
Self::Stac(value) => value.into_formatted_bytes(format),
49+
}
50+
}
51+
}

core/CHANGELOG.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99
### Added
1010

1111
- Deref `ItemCollection` ([#363](https://github.com/stac-utils/stac-rs/pull/363))
12-
- `object_store` and `ndjson` ([#369](https://github.com/stac-utils/stac-rs/pull/369))
13-
- `Format` ([#372](https://github.com/stac-utils/stac-rs/pull/371))
12+
- `io::Format` ([#372](https://github.com/stac-utils/stac-rs/pull/371))
1413
- Read unknown versions ([#378](https://github.com/stac-utils/stac-rs/pull/378))
14+
- `io::IntoFormattedBytes` ([#386](https://github.com/stac-utils/stac-rs/pull/386))
1515

1616
### Changed
1717

1818
- Update **geoarrow** to v0.3.0 ([#367](https://github.com/stac-utils/stac-rs/pull/367))
1919

20-
### Removed
21-
22-
- `io` module ([#369](https://github.com/stac-utils/stac-rs/pull/369))
23-
2420
## [0.9.0] - 2024-09-05
2521

2622
### Added

core/Cargo.toml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ geoarrow = [
2121
"dep:arrow-schema",
2222
"dep:geo-types",
2323
]
24-
geoparquet = ["geoarrow", "geoarrow/parquet", "dep:parquet", "dep:bytes"]
24+
geoparquet = ["geoarrow", "geoarrow/parquet", "dep:parquet"]
2525
geoparquet-compression = [
2626
"geoparquet",
2727
"geoarrow/parquet_compression",
@@ -31,7 +31,7 @@ geoparquet-compression = [
3131
"parquet/lz4",
3232
"parquet/zstd",
3333
]
34-
object-store = ["dep:object_store", "dep:send-future"]
34+
object-store = ["dep:object_store"]
3535
object-store-aws = ["object-store", "object_store/aws"]
3636
object-store-azure = ["object-store", "object_store/azure"]
3737
object-store-gcp = ["object-store", "object_store/gcp"]
@@ -49,7 +49,7 @@ arrow-array = { version = "52", optional = true }
4949
arrow-cast = { version = "52", optional = true }
5050
arrow-json = { version = "52", optional = true }
5151
arrow-schema = { version = "52", optional = true }
52-
bytes = { version = "1", optional = true }
52+
bytes = "1"
5353
chrono = { version = "0.4", features = ["serde"] }
5454
gdal = { version = "0.17", optional = true }
5555
gdal-sys = { version = "0.10", optional = true }
@@ -62,7 +62,6 @@ mime = "0.3"
6262
object_store = { version = "0.11", optional = true }
6363
parquet = { version = "52", default-features = false, optional = true }
6464
reqwest = { version = "0.12", optional = true, features = ["json", "blocking"] }
65-
send-future = { version = "0.1", optional = true } # https://github.com/rust-lang/rust/issues/96865
6665
serde = { version = "1", features = ["derive"] }
6766
serde_json = { version = "1", features = ["preserve_order"] }
6867
thiserror = "1"
@@ -72,7 +71,6 @@ url = "2"
7271
assert-json-diff = "2"
7372
bytes = "1"
7473
rstest = "0.22"
75-
tokio = { version = "1", features = ["macros"] }
7674
tokio-test = "0.4"
7775

7876
[package.metadata.docs.rs]

0 commit comments

Comments
 (0)