Skip to content

Commit 6058ddf

Browse files
committed
initial implementation
1 parent a8cbee7 commit 6058ddf

5 files changed

Lines changed: 129 additions & 47 deletions

File tree

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ version = "0.1.0-nightly"
66
edition = "2021"
77
license = "Apache-2.0"
88
repository = "https://github.com/SwissDatascienceCenter/renku-cli"
9-
keywords = [ "renku", "cli" ]
10-
categories = [ "command-line-utilities" ]
9+
keywords = ["renku", "cli"]
10+
categories = ["command-line-utilities"]
1111
build = "build.rs"
1212

1313
# See more keys and their definitions at
@@ -16,11 +16,15 @@ build = "build.rs"
1616
[dependencies]
1717
clap = { version = "4.5.13", features = ["derive", "wrap_help"] }
1818
clap_complete = "4.5.13"
19-
console = {version = "0.15.8"}
19+
console = { version = "0.15.8" }
2020
env_logger = { version = "0.11.5" }
2121
log = { version = "0.4.22" }
2222
openssl = { version = "0.10.64", optional = true }
23-
reqwest = { version = "0.12.7", default-features = false, features = ["json", "multipart"] }
23+
reqwest = { version = "0.12.7", default-features = false, features = [
24+
"json",
25+
"multipart",
26+
"stream",
27+
] }
2428
serde = { version = "1.0.210", features = ["derive"] }
2529
serde_json = "1.0.128"
2630
snafu = { version = "0.8.5" }
@@ -31,21 +35,37 @@ futures = { version = "0.3" }
3135
regex = { version = "1.11.0" }
3236
iso8601-timestamp = { version = "0.2.17" }
3337
toml = { version = "0.8.19" }
34-
git2 = { version = "0.19.0", default-features = false, features = [ "vendored-libgit2", "https", "ssh" ]}
38+
git2 = { version = "0.19.0", default-features = false, features = [
39+
"vendored-libgit2",
40+
"https",
41+
"ssh",
42+
] }
3543
url = { version = "2.5.1" }
36-
openidconnect = { version = "3.5.0", default-features = false, features = [ "reqwest" ] }
44+
mime_guess = "2.0"
45+
openidconnect = { version = "3.5.0", default-features = false, features = [
46+
"reqwest",
47+
] }
3748
directories = { version = "5.0" }
3849
comrak = { version = "0.28.0", optional = true }
3950

4051
[features]
4152
default = ["reqwest/default-tls"] # link against system library
42-
rustls = ["reqwest/rustls-tls", "openidconnect/rustls-tls"] # include rustls, ssl library written in rust
53+
rustls = [
54+
"reqwest/rustls-tls",
55+
"openidconnect/rustls-tls",
56+
] # include rustls, ssl library written in rust
4357
vendored-openssl = ["openssl/vendored"] # include compiled openssl library
44-
user-doc = [ "dep:comrak" ]
58+
user-doc = ["dep:comrak"]
4559

4660
[dev-dependencies]
4761
assert_cmd = "2.0.16"
4862
predicates = "3.1.2"
4963

5064
[build-dependencies]
51-
vergen = { version = "8.3.2", features = ["build", "git", "gitcl", "rustc", "cargo"] }
65+
vergen = { version = "8.3.2", features = [
66+
"build",
67+
"git",
68+
"gitcl",
69+
"rustc",
70+
"cargo",
71+
] }

src/cli/cmd/dataset.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
pub mod deposit;
22
pub mod zenodo;
33

4-
use std::env::args;
5-
64
use super::Context;
75
use clap::Parser;
8-
use snafu::{ResultExt, Snafu};
6+
use snafu::Snafu;
97

108
#[derive(Debug, Snafu)]
119
pub enum Error {
@@ -33,3 +31,11 @@ pub enum DepositCommand {
3331
#[command()]
3432
CopyFiles(deposit::CopyInput),
3533
}
34+
35+
impl Input {
36+
pub async fn exec(&self, ctx: Context) -> Result<(), Error> {
37+
match self.subcmd {
38+
DatasetCommand::Deposit { cmd: _ } => Ok(print!("Hi")),
39+
}
40+
}
41+
}

src/cli/cmd/dataset/deposit.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
use super::zenodo;
22
use super::Context;
3-
use crate::httpclient::Error as HttpError;
4-
use clap::Parser;
3+
use clap::{Parser, ValueEnum};
54
use snafu::{ResultExt, Snafu};
6-
use url::Url;
5+
use std::env::VarError;
6+
use std::path::PathBuf;
77

8+
#[derive(Debug, Clone, ValueEnum)]
89
enum Provider {
910
Zenodo,
1011
}
1112

1213
#[derive(Debug, Snafu)]
1314
pub enum Error {
1415
#[snafu(display("A dataset deposit error occured: {}", source))]
15-
HttpClient { source: HttpError },
16+
Zenodo { source: zenodo::Error },
17+
18+
#[snafu(display("Env variable error: {}", source))]
19+
EnvVarMissing { source: VarError },
1620
}
1721

1822
/// Copies the data from a location into a data deposit
@@ -32,12 +36,14 @@ pub struct CopyInput {
3236
}
3337

3438
impl CopyInput {
35-
pub async fn exec(&self, ctx: Context) -> Result<(), Error> {
39+
pub async fn exec(&self, _ctx: Context) -> Result<(), Error> {
3640
match self.provider {
3741
Provider::Zenodo => {
38-
let token = std::env::var("ZENODO_API_KEY")?;
42+
let token = std::env::var("ZENODO_API_KEY").context(EnvVarMissingSnafu)?;
3943
let clnt = zenodo::ZenodoClient::new(token);
40-
clnt.upload_files(self.deposit_id, self.source_dir).await?;
44+
clnt.upload_files(&self.deposit_id, &self.source_dir)
45+
.await
46+
.context(ZenodoSnafu)
4147
}
4248
}
4349
}

src/cli/cmd/dataset/zenodo.rs

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
1-
use crate::httpclient::{Error as HttpError, HttpSnafu, UrlParseSnafu};
1+
use mime_guess::from_path;
22
use reqwest;
33
use serde::de::DeserializeOwned;
44
use snafu::{ResultExt, Snafu};
5-
use std::path::Path;
5+
use std::path::{Path, PathBuf};
66
use tokio::fs::File;
77
use tokio_util::codec::{BytesCodec, FramedRead};
8-
use url::Url;
8+
use url::{ParseError, Url};
99
use walkdir::WalkDir;
1010

11+
use std::io;
12+
use std::sync::LazyLock;
1113
pub struct ZenodoClient {
1214
http_client: reqwest::Client,
1315
base_url: Url,
1416
token: String,
1517
}
1618

17-
const BASE_URL: Url = Url::parse("https://zenodo.org").unwrap();
19+
static BASE_URL: LazyLock<Url> =
20+
LazyLock::new(|| Url::parse("https://zenodo.org").expect("Invalid Base URL config"));
1821

19-
#[derive(Debug, Serialize, Deserialize)]
20-
pub struct Deposition {
21-
pub search: SearchServiceVersion,
22-
pub data: SimpleVersion,
22+
#[derive(Debug, Snafu)]
23+
pub enum Error {
24+
#[snafu(display("A zendoo client error occured: {}", source))]
25+
Reqwest { source: reqwest::Error },
26+
#[snafu(display("A directory listing error occured: {}", source))]
27+
DirWalk { source: walkdir::Error },
28+
#[snafu(display("An error occured reading the response: {}", source))]
29+
DeserializeResp { source: reqwest::Error },
30+
#[snafu(display("An error occured reading the response: {}", source))]
31+
FileReading { source: io::Error },
32+
#[snafu(display("An error occured parsing the file path: {}", fp.to_string_lossy()))]
33+
FileParsing { fp: PathBuf },
34+
#[snafu(display("An error occured parsing the url {}", source))]
35+
UrlParse { source: ParseError },
2336
}
2437

2538
impl ZenodoClient {
2639
pub fn new(token: String) -> ZenodoClient {
2740
let http_client = reqwest::Client::new();
2841
return ZenodoClient {
2942
http_client,
30-
base_url: BASE_URL,
43+
base_url: BASE_URL.clone(),
3144
token,
3245
};
3346
}
@@ -36,55 +49,76 @@ impl ZenodoClient {
3649
return self.base_url.join(path).context(UrlParseSnafu);
3750
}
3851

39-
pub async fn get_deposition<R: DeserializeOwned>(&self, id: &str) -> Result<R, Error> {
40-
let endpoint = self.make_url(format!("/api/deposit/depositions/{id}"));
52+
pub async fn get_deposition<R: DeserializeOwned>(
53+
&self,
54+
deposition_id: &str,
55+
) -> Result<R, Error> {
56+
let endpoint = self.make_url(&format!("/api/deposit/depositions/{deposition_id}"))?;
4157
let res = self
4258
.http_client
43-
.get(enpdoint)
59+
.get(endpoint)
4460
.send()
4561
.await
46-
.context(HttpSnafu { url: endpoint })?;
47-
res.error_for_status()?;
48-
return res.json();
62+
.context(ReqwestSnafu)?;
63+
// res.error_for_status().context(ReqwestSnafu)?;
64+
return res.json::<R>().await.context(DeserializeRespSnafu);
4965
}
5066

5167
pub async fn upload_files(&self, deposition_id: &str, source_path: &Path) -> Result<(), Error> {
5268
for f in WalkDir::new(source_path) {
53-
self.upload_file(deposition_id, f?.path()).await?;
69+
self.upload_file::<()>(deposition_id, f.context(DirWalkSnafu)?.path())
70+
.await?;
5471
}
72+
Ok(())
5573
}
5674

5775
pub async fn list_files<R: DeserializeOwned>(&self, deposition_id: &str) -> Result<R, Error> {
58-
let endpoint = self.make_url(format!("/api/deposit/depositions/{id}/files"));
76+
let endpoint = self.make_url(&format!("/api/deposit/depositions/{deposition_id}/files"))?;
5977
let res = self
6078
.http_client
61-
.get(enpdoint)
79+
.get(endpoint)
6280
.send()
6381
.await
64-
.context(HttpSnafu { url: endpoint })?;
65-
res.error_for_status()?;
66-
return res.json();
82+
.context(ReqwestSnafu)?;
83+
// res.error_for_status().context(ReqwestSnafu)?;
84+
return res.json::<R>().await.context(DeserializeRespSnafu);
6785
}
6886

6987
async fn upload_file<R: DeserializeOwned>(
7088
&self,
7189
deposition_id: &str,
7290
file_path: &Path,
7391
) -> Result<R, Error> {
74-
let file = File::open(file_path).await?;
75-
let endpoint = self.make_url(format!("/api/deposit/depositions/{id}/files"));
92+
let file = File::open(file_path).await.context(FileReadingSnafu)?;
93+
let endpoint = self.make_url(&format!("/api/deposit/depositions/{deposition_id}/files"))?;
7694
let stream = FramedRead::new(file, BytesCodec::new());
95+
let file_name = file_path
96+
.file_name()
97+
.ok_or(Error::FileParsing {
98+
fp: file_path.to_path_buf(),
99+
})?
100+
.to_str()
101+
.ok_or(Error::FileParsing {
102+
fp: file_path.to_path_buf(),
103+
})?;
104+
let mime_type = from_path(file_path).first_or_octet_stream();
77105
let form = reqwest::multipart::Form::new()
78-
.text("name", file_path.file_name()?.to_str()?)
79-
.part("file", stream);
106+
.text("name", file_name.to_owned())
107+
.part(
108+
"file",
109+
reqwest::multipart::Part::stream(reqwest::Body::wrap_stream(stream))
110+
.file_name(file_name.to_owned())
111+
.mime_str(mime_type.as_ref())
112+
.context(ReqwestSnafu)?,
113+
);
80114
let res = self
81115
.http_client
82116
.post(endpoint)
83117
.multipart(form)
84118
.send()
85119
.await
86-
.context(HttpSnafu { url: endpoint })?;
87-
res.error_for_status()?;
88-
return res.json();
120+
.context(ReqwestSnafu)?;
121+
// res.error_for_status().context(ReqwestSnafu)?;
122+
return res.json::<R>().await.context(DeserializeRespSnafu);
89123
}
90124
}

0 commit comments

Comments
 (0)