feat: implement record commands

This commit is contained in:
Stevan Freeborn committed 2026-09-14 19:53:56 -05:00
1 parent 54346ff20f
commit 56b5abc1d1
6 files changed
+2101 -15

No files matched your search

+178 -2
View File
@@ -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"));
}
}