-
Notifications
You must be signed in to change notification settings - Fork 499
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1836 from tjkirch/apiclient-get
Add 'apiclient get' for simple API retrieval
- Loading branch information
Showing
8 changed files
with
261 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use snafu::{OptionExt, ResultExt}; | ||
use std::path::Path; | ||
|
||
mod merge_json; | ||
use merge_json::merge_json; | ||
|
||
/// Fetches the given prefixes from the API and merges them into a single Value. (It's not | ||
/// expected that given prefixes would overlap, but if they do, later ones take precedence.) | ||
pub async fn get_prefixes<P>(socket_path: P, prefixes: Vec<String>) -> Result<serde_json::Value> | ||
where | ||
P: AsRef<Path>, | ||
{ | ||
let mut results: Vec<serde_json::Value> = Vec::with_capacity(prefixes.len()); | ||
|
||
// Fetch all given prefixes into separate Values. | ||
for prefix in prefixes { | ||
let uri = format!("/?prefix={}", prefix); | ||
let method = "GET"; | ||
let (_status, body) = crate::raw_request(&socket_path, &uri, method, None) | ||
.await | ||
.context(error::Request { uri, method })?; | ||
let value = serde_json::from_str(&body).context(error::ResponseJson { body })?; | ||
results.push(value); | ||
} | ||
|
||
// Merge results together. | ||
results | ||
.into_iter() | ||
.reduce(|mut merge_into, merge_from| { | ||
merge_json(&mut merge_into, merge_from); | ||
merge_into | ||
}) | ||
.context(error::NoPrefixes) | ||
} | ||
|
||
/// Fetches the given URI from the API and returns the result as an untyped Value. | ||
pub async fn get_uri<P>(socket_path: P, uri: String) -> Result<serde_json::Value> | ||
where | ||
P: AsRef<Path>, | ||
{ | ||
let method = "GET"; | ||
let (_status, body) = crate::raw_request(&socket_path, &uri, method, None) | ||
.await | ||
.context(error::Request { uri, method })?; | ||
serde_json::from_str(&body).context(error::ResponseJson { body }) | ||
} | ||
|
||
mod error { | ||
use snafu::Snafu; | ||
|
||
#[derive(Debug, Snafu)] | ||
#[snafu(visibility = "pub(super)")] | ||
pub enum Error { | ||
#[snafu(display("Must give prefixes to query"))] | ||
NoPrefixes, | ||
|
||
#[snafu(display("Failed {} request to '{}': {}", method, uri, source))] | ||
Request { | ||
method: String, | ||
uri: String, | ||
source: crate::Error, | ||
}, | ||
|
||
#[snafu(display("Response contained invalid JSON '{}' - {}", body, source))] | ||
ResponseJson { | ||
body: String, | ||
source: serde_json::Error, | ||
}, | ||
} | ||
} | ||
pub use error::Error; | ||
pub type Result<T> = std::result::Result<T, error::Error>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
use serde_json::{map::Entry, Value}; | ||
|
||
/// This modifies the first given JSON Value by inserting any values from the second Value. | ||
/// | ||
/// This is done recursively. Any time a scalar or array is seen, the left side is set to match | ||
/// the right side. Any time an object is seen, we iterate through the keys of the objects; if the | ||
/// left side does not have the key from the right side, it's inserted, otherwise we recursively | ||
/// merge the values in each object for that key. | ||
// Logic and tests taken from storewolf::merge-toml, modified for serde_json. | ||
pub(super) fn merge_json(merge_into: &mut Value, merge_from: Value) { | ||
match (merge_into, merge_from) { | ||
// If we see objects, we recursively merge each key. | ||
(Value::Object(merge_into), Value::Object(merge_from)) => { | ||
for (merge_from_key, merge_from_val) in merge_from.into_iter() { | ||
// Check if the left has the same key as the right. | ||
match merge_into.entry(merge_from_key) { | ||
// If not, we can just insert the value. | ||
Entry::Vacant(entry) => { | ||
entry.insert(merge_from_val); | ||
} | ||
// If so, we need to recursively merge; we don't want to replace an entire | ||
// table, for example, because the left may have some distinct inner keys. | ||
Entry::Occupied(ref mut entry) => { | ||
merge_json(entry.get_mut(), merge_from_val); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// If we see a scalar, we replace the left with the right. We treat arrays like scalars so | ||
// behavior is clear - no question about whether we're appending right onto left, etc. | ||
(merge_into, merge_from) => { | ||
*merge_into = merge_from; | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::merge_json; | ||
use serde_json::json; | ||
|
||
#[test] | ||
fn recursion() { | ||
let mut left = json! {{ | ||
"top1": "left top1", | ||
"top2": "left top2", | ||
"settings": { | ||
"inner": { | ||
"inner_setting1": "left inner_setting1", | ||
"inner_setting2": "left inner_setting2" | ||
} | ||
} | ||
}}; | ||
let right = json! {{ | ||
"top1": "right top1", | ||
"settings": { | ||
"setting": "right setting", | ||
"inner": { | ||
"inner_setting1": "right inner_setting1", | ||
"inner_setting3": "right inner_setting3" | ||
} | ||
} | ||
}}; | ||
let expected = json! {{ | ||
// "top1" is being overwritten from right. | ||
"top1": "right top1", | ||
// "top2" is only in the left and remains. | ||
"top2": "left top2", | ||
"settings": { | ||
// "setting" is only in the right side. | ||
"setting": "right setting", | ||
// "inner" tests that recursion works. | ||
"inner": { | ||
// inner_setting1 is replaced. | ||
"inner_setting1": "right inner_setting1", | ||
// 2 is untouched. | ||
"inner_setting2": "left inner_setting2", | ||
// 3 is new. | ||
"inner_setting3": "right inner_setting3" | ||
} | ||
} | ||
}}; | ||
merge_json(&mut left, right); | ||
assert_eq!(left, expected); | ||
} | ||
|
||
#[test] | ||
fn array() { | ||
let mut left = json!({"a": [1, 2, 3]}); | ||
let right = json!({"a": [4, 5]}); | ||
let expected = json!({"a": [4, 5]}); | ||
merge_json(&mut left, right); | ||
assert_eq!(left, expected); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.