Skip to content

Commit d266098

Browse files
author
Anish Cheraku
committed
apiclient: refactor get logic in apply.rs
1 parent 60b264f commit d266098

File tree

6 files changed

+181
-40
lines changed

6 files changed

+181
-40
lines changed

sources/Cargo.lock

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

sources/api/apiclient/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ tls = ["dep:rustls", "dep:aws-lc-rs", "reqwest/rustls-tls-native-roots"]
1515
fips = ["tls", "aws-lc-rs/fips", "rustls/fips"]
1616

1717
[dependencies]
18+
async-trait = "0.1"
19+
aws-smithy-types.workspace = true
20+
aws-config.workspace = true
1821
aws-lc-rs = { workspace = true, optional = true, features = ["bindgen"] }
1922
base64.workspace = true
2023
constants.workspace = true
@@ -31,7 +34,7 @@ log.workspace = true
3134
models.workspace = true
3235
nix.workspace = true
3336
rand = { workspace = true, features = ["default"] }
34-
reqwest.workspace = true
37+
reqwest.workspace = true
3538
retry-read.workspace = true
3639
rustls = { workspace = true, optional = true }
3740
serde = { workspace = true, features = ["derive"] }

sources/api/apiclient/src/apply.rs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22
//! TOML settings files, in the same format as user data, or the JSON equivalent. The inputs are
33
//! pulled and applied to the API server in a single transaction.
44
5+
use crate::uri_resolver::{StdinUri, FileUri, HttpUri, UriResolver};
56
use crate::rando;
67
use futures::future::{join, ready, TryFutureExt};
78
use futures::stream::{self, StreamExt};
89
use reqwest::Url;
910
use serde::de::{Deserialize, IntoDeserializer};
1011
use snafu::{futures::try_future::TryFutureExt as SnafuTryFutureExt, OptionExt, ResultExt};
12+
use std::convert::TryFrom;
1113
use std::path::Path;
1214
use tokio::io::AsyncReadExt;
1315

16+
1417
/// Reads settings in TOML or JSON format from files at the requested URIs (or from stdin, if given
1518
/// "-"), then commits them in a single transaction and applies them to the system.
1619
pub async fn apply<P>(socket_path: P, input_sources: Vec<String>) -> Result<()>
@@ -67,46 +70,36 @@ where
6770
}
6871

6972
/// Retrieves the given source location and returns the result in a String.
70-
async fn get<S>(input_source: S) -> Result<String>
71-
where
72-
S: Into<String>,
73-
{
74-
let input_source = input_source.into();
75-
76-
// Read from stdin if "-" was given.
77-
if input_source == "-" {
78-
let mut output = String::new();
79-
tokio::io::stdin()
80-
.read_to_string(&mut output)
81-
.context(error::StdinReadSnafu)
82-
.await?;
83-
return Ok(output);
73+
pub async fn get(input: &str) -> Result<String> {
74+
let resolver = select_resolver(input)?;
75+
resolver.resolve().await
76+
}
77+
78+
/// Choose which UriResolver applies to `input` (stdin, file://, http(s)://).
79+
fn select_resolver(input: &str) -> Result<Box<dyn UriResolver>> {
80+
// 1) "-" → stdin
81+
if let Ok(r) = StdinUri::try_from(input) {
82+
return Ok(Box::new(r));
8483
}
8584

86-
// Otherwise, the input should be a URI; parse it to know what kind.
87-
// Until reqwest handles file:// URIs: https://github.com/seanmonstar/reqwest/issues/178
88-
let uri = Url::parse(&input_source).context(error::UriSnafu {
89-
input_source: &input_source,
90-
})?;
91-
if uri.scheme() == "file" {
92-
// Turn the URI to a file path, and return a future that reads it.
93-
let path = uri.to_file_path().ok().context(error::FileUriSnafu {
94-
input_source: &input_source,
95-
})?;
96-
tokio::fs::read_to_string(path)
97-
.context(error::FileReadSnafu { input_source })
98-
.await
99-
} else {
100-
// Return a future that contains the text of the (non-file) URI.
101-
reqwest::get(uri)
102-
.and_then(|response| ready(response.error_for_status()))
103-
.and_then(|response| response.text())
104-
.context(error::ReqwestSnafu {
105-
uri: input_source,
106-
method: "GET",
107-
})
108-
.await
85+
// 2) parse as a URL
86+
let url = Url::parse(input).context(error::UriSnafu { input_source: input.to_string() })?;
87+
88+
// 3) file://
89+
if let Ok(r) = FileUri::try_from(url.clone()) {
90+
return Ok(Box::new(r));
10991
}
92+
93+
// 4) http(s)://
94+
if let Ok(r) = HttpUri::try_from(url.clone()) {
95+
return Ok(Box::new(r));
96+
}
97+
98+
99+
error::NoResolverSnafu {
100+
input_source: input.to_string(),
101+
}
102+
.fail()
110103
}
111104

112105
/// Takes a string of TOML or JSON settings data and reserializes
@@ -142,11 +135,11 @@ fn format_change(input: &str, input_source: &str) -> Result<String> {
142135
serde_json::to_string(&json_inner).context(error::JsonSerializeSnafu { input_source })
143136
}
144137

145-
mod error {
138+
pub(crate) mod error {
146139
use snafu::Snafu;
147140

148141
#[derive(Debug, Snafu)]
149-
#[snafu(visibility(pub(super)))]
142+
#[snafu(visibility(pub(crate)))]
150143
pub enum Error {
151144
#[snafu(display("Failed to commit combined settings to '{}': {}", uri, source))]
152145
CommitApply {
@@ -164,6 +157,9 @@ mod error {
164157
#[snafu(display("Given invalid file URI '{}'", input_source))]
165158
FileUri { input_source: String },
166159

160+
#[snafu(display("No URI resolver found for '{}'", input_source))]
161+
NoResolver { input_source: String },
162+
167163
#[snafu(display(
168164
"Input '{}' is not valid TOML or JSON. (TOML error: {}) (JSON error: {})",
169165
input_source,
@@ -218,6 +214,12 @@ mod error {
218214
source: reqwest::Error,
219215
},
220216

217+
#[snafu(display("Given invalid file URI '{}'", input_source))]
218+
InvalidFileUri { input_source: String },
219+
220+
#[snafu(display("Given HTTP(S) URI '{}'", input_source))]
221+
InvalidHTTPUri { input_source: String },
222+
221223
#[snafu(display("Failed to read standard input: {}", source))]
222224
StdinRead { source: std::io::Error },
223225

sources/api/apiclient/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod reboot;
2727
pub mod report;
2828
pub mod set;
2929
pub mod update;
30+
pub mod uri_resolver;
3031

3132
mod error {
3233
use snafu::Snafu;
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// src/uri_resolver.rs
2+
3+
use async_trait::async_trait;
4+
use snafu::{ensure, ResultExt, OptionExt};
5+
use std::convert::TryFrom;
6+
use std::path::PathBuf;
7+
use tokio::io::AsyncReadExt;
8+
9+
use reqwest::Url;
10+
use crate::apply::{Error, Result};
11+
use crate::apply::error::{
12+
FileReadSnafu, FileUriSnafu, ReqwestSnafu, StdinReadSnafu, InvalidFileUriSnafu, InvalidHTTPUriSnafu
13+
};
14+
15+
/// Anything that can fetch itself as a UTF-8 `String`.
16+
#[async_trait]
17+
pub trait UriResolver {
18+
/// Fetches the contents of this URI as a `String`.
19+
async fn resolve(&self) -> Result<String>;
20+
}
21+
22+
/// “-” → standard input
23+
pub struct StdinUri;
24+
25+
impl TryFrom<&str> for StdinUri {
26+
type Error = ();
27+
28+
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
29+
if s == "-" {
30+
Ok(StdinUri)
31+
} else {
32+
Err(())
33+
}
34+
}
35+
}
36+
37+
#[async_trait]
38+
impl UriResolver for StdinUri {
39+
async fn resolve(&self) -> Result<String> {
40+
let mut buf = String::new();
41+
tokio::io::stdin()
42+
.read_to_string(&mut buf)
43+
.await
44+
.context(StdinReadSnafu)?;
45+
Ok(buf)
46+
}
47+
}
48+
49+
/// file:// URLs map to local filesystem paths
50+
pub struct FileUri {
51+
path: PathBuf,
52+
}
53+
54+
impl TryFrom<Url> for FileUri {
55+
type Error = Error;
56+
57+
fn try_from(url: Url) -> std::result::Result<Self, Self::Error> {
58+
// only accept file://
59+
ensure!(
60+
url.scheme() == "file",
61+
InvalidFileUriSnafu { input_source: url.to_string() }
62+
);
63+
64+
// convert to PathBuf or error
65+
let path = url
66+
.to_file_path()
67+
.ok()
68+
.context(FileUriSnafu { input_source: url.to_string() })?;
69+
70+
Ok(FileUri { path })
71+
}
72+
}
73+
74+
#[async_trait]
75+
impl UriResolver for FileUri {
76+
async fn resolve(&self) -> Result<String> {
77+
tokio::fs::read_to_string(&self.path)
78+
.await
79+
.context(FileReadSnafu {
80+
input_source: self.path.to_string_lossy().into_owned(),
81+
})
82+
}
83+
}
84+
85+
/// http:// or https:// URLs
86+
pub struct HttpUri {
87+
url: Url,
88+
}
89+
90+
impl TryFrom<Url> for HttpUri {
91+
type Error = Error;
92+
93+
fn try_from(url: Url) -> std::result::Result<Self, Self::Error> {
94+
ensure!(
95+
url.scheme() == "http" || url.scheme() == "https",
96+
InvalidHTTPUriSnafu { input_source: url.to_string() }
97+
);
98+
Ok(HttpUri { url })
99+
}
100+
}
101+
102+
#[async_trait]
103+
impl UriResolver for HttpUri {
104+
async fn resolve(&self) -> Result<String> {
105+
let resp = reqwest::get(self.url.clone())
106+
.await
107+
.context(ReqwestSnafu {
108+
uri: self.url.to_string(),
109+
method: "GET".to_string(),
110+
})?
111+
.error_for_status()
112+
.context(ReqwestSnafu {
113+
uri: self.url.to_string(),
114+
method: "GET".to_string(),
115+
})?;
116+
117+
resp.text()
118+
.await
119+
.context(ReqwestSnafu {
120+
uri: self.url.to_string(),
121+
method: "GET".to_string(),
122+
})
123+
}
124+
}
125+

sources/clarify.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ license-files = [
2626
{ path = "LICENSE-MIT", hash = 0xfeb1e4a7 },
2727
]
2828

29+
[clarify.aws-sdk-s3]
30+
expression = "Apache-2.0"
31+
license-files = [
32+
{ path = "LICENSE", hash = 0x1b71bb24 }
33+
]
34+
skip-files = [ "tests/blns/LICENSE" ] # we do not vend this as it's just used in s3's tests
35+
2936
[clarify.backtrace-sys]
3037
# backtrace-sys is MIT/Apache-2.0, libbacktrace is BSD-3-Clause
3138
expression = "(MIT OR Apache-2.0) AND BSD-3-Clause"

0 commit comments

Comments
 (0)