feat: implement record commands
This commit is contained in:
1 parent
54346ff20f
commit
56b5abc1d1
6 files changed
+2101
-15
No files matched your search
+178
-2
@@ -1,6 +1,9 @@
|
||||
use onspring::PagingRequest;
|
||||
use std::io::Read;
|
||||
|
||||
use crate::cli::PagingArgs;
|
||||
use onspring::PagingRequest;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::cli::{BodySource, PagingArgs};
|
||||
use crate::error::{CliError, CliResult};
|
||||
|
||||
pub fn paging_request(args: &PagingArgs) -> CliResult<Option<PagingRequest>> {
|
||||
@@ -54,6 +57,62 @@ pub fn validate_ids(ids: &[i32], name: &str, max_len: Option<usize>) -> CliResul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read_body<R: Read>(source: &BodySource, mut reader: R) -> CliResult<String> {
|
||||
let count = [
|
||||
source.json.is_some(),
|
||||
source.file.is_some(),
|
||||
source.stdin,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|&b| b)
|
||||
.count();
|
||||
|
||||
if count == 0 {
|
||||
return Err(CliError::usage(
|
||||
"A request body is required. Provide --json, --file, or --stdin.",
|
||||
));
|
||||
}
|
||||
|
||||
if count > 1 {
|
||||
return Err(CliError::usage(
|
||||
"Only one request body source may be specified (--json, --file, or --stdin).",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref json) = source.json {
|
||||
return Ok(json.clone());
|
||||
}
|
||||
|
||||
if let Some(ref path) = source.file {
|
||||
return std::fs::read_to_string(path).map_err(|e| {
|
||||
CliError::usage(format!(
|
||||
"Failed to read file '{}': {}",
|
||||
path.display(),
|
||||
e
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
if source.stdin {
|
||||
let mut buffer = String::new();
|
||||
reader
|
||||
.read_to_string(&mut buffer)
|
||||
.map_err(|e| CliError::runtime(format!("Failed to read from stdin: {e}")))?;
|
||||
return Ok(buffer);
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn parse_body<T: DeserializeOwned, R: Read>(
|
||||
source: &BodySource,
|
||||
reader: R,
|
||||
) -> CliResult<T> {
|
||||
let content = read_body(source, reader)?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| CliError::usage(format!("Invalid JSON request body: {e}")))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -207,4 +266,121 @@ mod tests {
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_no_source_provided_it_should_return_usage_error() {
|
||||
let source = BodySource::default();
|
||||
let result = read_body(&source, std::io::empty());
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::usage(
|
||||
"A request body is required. Provide --json, --file, or --stdin."
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_multiple_sources_provided_it_should_return_usage_error() {
|
||||
let source = BodySource {
|
||||
json: Some("{}".to_string()),
|
||||
stdin: true,
|
||||
file: None,
|
||||
};
|
||||
let result = read_body(&source, std::io::empty());
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(CliError::usage(
|
||||
"Only one request body source may be specified (--json, --file, or --stdin)."
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_json_provided_it_should_return_json_string() {
|
||||
let source = BodySource {
|
||||
json: Some(r#"{"appId":1}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
};
|
||||
let result = read_body(&source, std::io::empty());
|
||||
assert_eq!(result, Ok(r#"{"appId":1}"#.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_stdin_provided_it_should_read_from_reader() {
|
||||
let source = BodySource {
|
||||
json: None,
|
||||
file: None,
|
||||
stdin: true,
|
||||
};
|
||||
let input = r#"{"appId":2}"#;
|
||||
let result = read_body(&source, input.as_bytes());
|
||||
assert_eq!(result, Ok(input.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_file_provided_it_should_read_from_file() {
|
||||
use std::io::Write;
|
||||
let mut temp = tempfile::NamedTempFile::new().unwrap();
|
||||
write!(temp, r#"{{"appId":3}}"#).unwrap();
|
||||
|
||||
let source = BodySource {
|
||||
json: None,
|
||||
file: Some(temp.path().to_path_buf()),
|
||||
stdin: false,
|
||||
};
|
||||
let result = read_body(&source, std::io::empty());
|
||||
assert_eq!(result, Ok(r#"{"appId":3}"#.to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_body_when_file_not_found_it_should_return_usage_error() {
|
||||
let source = BodySource {
|
||||
json: None,
|
||||
file: Some(std::path::PathBuf::from("nonexistent_file_path_12345.json")),
|
||||
stdin: false,
|
||||
};
|
||||
let result = read_body(&source, std::io::empty());
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(err.code, 2);
|
||||
assert!(err.message.contains("Failed to read file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_body_when_valid_it_should_deserialize() {
|
||||
#[derive(Debug, PartialEq, serde::Deserialize)]
|
||||
struct TestData {
|
||||
app_id: i32,
|
||||
}
|
||||
|
||||
let source = BodySource {
|
||||
json: Some(r#"{"app_id":42}"#.to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
};
|
||||
|
||||
let result: Result<TestData, _> = parse_body(&source, std::io::empty());
|
||||
assert_eq!(result, Ok(TestData { app_id: 42 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_body_when_invalid_json_it_should_return_usage_error() {
|
||||
#[derive(Debug, PartialEq, serde::Deserialize)]
|
||||
struct TestData {
|
||||
app_id: i32,
|
||||
}
|
||||
|
||||
let source = BodySource {
|
||||
json: Some("invalid json".to_string()),
|
||||
file: None,
|
||||
stdin: false,
|
||||
};
|
||||
|
||||
let result: Result<TestData, _> = parse_body(&source, std::io::empty());
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert_eq!(err.code, 2);
|
||||
assert!(err.message.contains("Invalid JSON request body"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user