Skip to content

Commit 8c1ff6d

Browse files
author
Anish Cheraku
committed
Refactoring get logic
1 parent 9af2f7f commit 8c1ff6d

File tree

6 files changed

+180
-41
lines changed

6 files changed

+180
-41
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 = {features = ["blocking", "json"], 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 & 40 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};
6+
use crate::apply::error::{UriSnafu, NoResolverSnafu};
57
use crate::rando;
68
use futures::future::{join, ready, TryFutureExt};
79
use futures::stream::{self, StreamExt};
810
use reqwest::Url;
911
use serde::de::{Deserialize, IntoDeserializer};
1012
use snafu::{futures::try_future::TryFutureExt as SnafuTryFutureExt, OptionExt, ResultExt};
11-
use std::path::Path;
13+
use std::{convert::TryFrom, 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,35 @@ 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(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+
NoResolverSnafu {
99+
input_source: input.to_string(),
100+
}
101+
.fail()
110102
}
111103

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

145-
mod error {
137+
pub(crate) mod error {
146138
use snafu::Snafu;
147139

148140
#[derive(Debug, Snafu)]
149-
#[snafu(visibility(pub(super)))]
141+
#[snafu(visibility(pub(crate)))]
150142
pub enum Error {
151143
#[snafu(display("Failed to commit combined settings to '{}': {}", uri, source))]
152144
CommitApply {
@@ -164,6 +156,9 @@ mod error {
164156
#[snafu(display("Given invalid file URI '{}'", input_source))]
165157
FileUri { input_source: String },
166158

159+
#[snafu(display("No URI resolver for '{}'", input_source))]
160+
NoResolver { input_source: String },
161+
167162
#[snafu(display(
168163
"Input '{}' is not valid TOML or JSON. (TOML error: {}) (JSON error: {})",
169164
input_source,
@@ -218,6 +213,12 @@ mod error {
218213
source: reqwest::Error,
219214
},
220215

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

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

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)