mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-22 06:19:47 +08:00
* fix(deps): update noyalib and preserve policy null rejection Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(providers): preserve quoted YAML duration exports Signed-off-by: Drew Newberry <anewberry@nvidia.com> * test(providers): assert YAML duration semantics instead of quoting Signed-off-by: Drew Newberry <anewberry@nvidia.com> * fix(yaml): preserve authored input and cross-reader string semantics Signed-off-by: Drew Newberry <anewberry@nvidia.com> * refactor(yaml): validate schema objects before standard decoding Signed-off-by: Drew Newberry <anewberry@nvidia.com> * docs(policy): remove YAML compatibility documentation changes Signed-off-by: Drew Newberry <anewberry@nvidia.com> --------- Signed-off-by: Drew Newberry <anewberry@nvidia.com>
694 lines
20 KiB
Rust
694 lines
20 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! Generic output formatting helpers for CLI commands.
|
|
//!
|
|
//! This module provides helper functions to eliminate duplication across CLI commands
|
|
//! that support `--output` flags (json, yaml, table).
|
|
//!
|
|
//! # Early-return Pattern
|
|
//!
|
|
//! All helper functions return `Result<bool>`:
|
|
//! - `Ok(true)` — Format was handled (json/yaml), caller should return immediately
|
|
//! - `Ok(false)` — Format is "table", caller should continue to table rendering
|
|
//! - `Err(e)` — Unsupported format or serialization error
|
|
//!
|
|
//! # Example
|
|
//!
|
|
//! ```ignore
|
|
//! use crate::output::print_output_collection;
|
|
//!
|
|
//! pub fn sandbox_list(output: &str) -> Result<()> {
|
|
//! let sandboxes = fetch_sandboxes()?;
|
|
//!
|
|
//! if print_output_collection(output, &sandboxes, sandbox_to_json)? {
|
|
//! return Ok(());
|
|
//! }
|
|
//!
|
|
//! // Fall through to table rendering
|
|
//! render_sandbox_table(&sandboxes);
|
|
//! Ok(())
|
|
//! }
|
|
//! ```
|
|
|
|
use miette::{IntoDiagnostic, Result};
|
|
use std::io::Write;
|
|
|
|
/// Report a continuation token for table output.
|
|
pub fn print_next_page_token(next_page_token: &str) {
|
|
if !next_page_token.is_empty() {
|
|
eprintln!("Next page token: {next_page_token}");
|
|
}
|
|
}
|
|
|
|
fn paginated_collection_value<T, F>(
|
|
collection_name: &str,
|
|
items: &[T],
|
|
next_page_token: &str,
|
|
to_json: F,
|
|
) -> serde_json::Value
|
|
where
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
let mut envelope = serde_json::Map::new();
|
|
envelope.insert(
|
|
collection_name.to_string(),
|
|
serde_json::Value::Array(items.iter().map(to_json).collect()),
|
|
);
|
|
envelope.insert(
|
|
"next_page_token".to_string(),
|
|
serde_json::Value::String(next_page_token.to_string()),
|
|
);
|
|
serde_json::Value::Object(envelope)
|
|
}
|
|
|
|
/// Print a single page as a structured response envelope.
|
|
///
|
|
/// JSON and YAML output include both the named collection and
|
|
/// `next_page_token`, so callers can continue without scraping stderr. Table
|
|
/// output is not handled; callers should render the table and may report the
|
|
/// token with [`print_next_page_token`].
|
|
pub fn print_paginated_output_collection<T, F>(
|
|
format: impl AsRef<str>,
|
|
collection_name: &str,
|
|
items: &[T],
|
|
next_page_token: &str,
|
|
to_json: F,
|
|
) -> Result<bool>
|
|
where
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let envelope =
|
|
paginated_collection_value(collection_name, items, next_page_token, to_json);
|
|
println!(
|
|
"{}",
|
|
serde_json::to_string_pretty(&envelope).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let envelope =
|
|
paginated_collection_value(collection_name, items, next_page_token, to_json);
|
|
print!(
|
|
"{}",
|
|
openshell_core::yaml::to_string(&envelope).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print collection output in specified format (json/yaml/table).
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats or serialization errors
|
|
///
|
|
/// # Behavior
|
|
/// - JSON: uses `println!()` (includes trailing newline)
|
|
/// - YAML: uses `print!()` (no trailing newline, `serde_yml` includes one)
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// if print_output_collection(output, &sandboxes, sandbox_to_json)? {
|
|
/// return Ok(());
|
|
/// }
|
|
/// ```
|
|
pub fn print_output_collection<T, F>(
|
|
format: impl AsRef<str>,
|
|
items: &[T],
|
|
to_json: F,
|
|
) -> Result<bool>
|
|
where
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let values: Vec<_> = items.iter().map(to_json).collect();
|
|
println!(
|
|
"{}",
|
|
serde_json::to_string_pretty(&values).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let values: Vec<_> = items.iter().map(to_json).collect();
|
|
print!(
|
|
"{}",
|
|
openshell_core::yaml::to_string(&values).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print single item output in specified format (json/yaml/table).
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats or serialization errors
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// if print_output_single(output, &sandbox, sandbox_to_json)? {
|
|
/// return Ok(());
|
|
/// }
|
|
/// ```
|
|
pub fn print_output_single<T, F>(format: impl AsRef<str>, item: &T, to_json: F) -> Result<bool>
|
|
where
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let value = to_json(item);
|
|
println!(
|
|
"{}",
|
|
serde_json::to_string_pretty(&value).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let value = to_json(item);
|
|
print!(
|
|
"{}",
|
|
openshell_core::yaml::to_string(&value).into_diagnostic()?
|
|
);
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print pre-formatted output in specified format (json/yaml/table).
|
|
///
|
|
/// Use this when converter functions return `Result<String>` instead of `serde_json::Value`.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats or conversion errors
|
|
///
|
|
/// # Example
|
|
/// ```ignore
|
|
/// if print_output_direct(
|
|
/// output,
|
|
/// || profiles_to_json(&profiles).into_diagnostic(),
|
|
/// || profiles_to_yaml(&profiles).into_diagnostic(),
|
|
/// )? {
|
|
/// return Ok(());
|
|
/// }
|
|
/// ```
|
|
pub fn print_output_direct<J, Y>(format: impl AsRef<str>, json_fn: J, yaml_fn: Y) -> Result<bool>
|
|
where
|
|
J: FnOnce() -> Result<String>,
|
|
Y: FnOnce() -> Result<String>,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let json = json_fn()?;
|
|
println!("{json}");
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let yaml = yaml_fn()?;
|
|
print!("{yaml}");
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print collection output to a custom writer in specified format (json/yaml/table).
|
|
///
|
|
/// Writer variant for commands that need custom output destinations.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats, serialization errors, or write errors
|
|
pub fn print_output_collection_to_writer<W, T, F>(
|
|
format: impl AsRef<str>,
|
|
writer: &mut W,
|
|
items: &[T],
|
|
to_json: F,
|
|
) -> Result<bool>
|
|
where
|
|
W: Write,
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let values: Vec<_> = items.iter().map(to_json).collect();
|
|
writeln!(
|
|
writer,
|
|
"{}",
|
|
serde_json::to_string_pretty(&values).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let values: Vec<_> = items.iter().map(to_json).collect();
|
|
write!(
|
|
writer,
|
|
"{}",
|
|
openshell_core::yaml::to_string(&values).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Writer variant of [`print_paginated_output_collection`].
|
|
pub fn print_paginated_output_collection_to_writer<W, T, F>(
|
|
format: impl AsRef<str>,
|
|
writer: &mut W,
|
|
collection_name: &str,
|
|
items: &[T],
|
|
next_page_token: &str,
|
|
to_json: F,
|
|
) -> Result<bool>
|
|
where
|
|
W: Write,
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let envelope =
|
|
paginated_collection_value(collection_name, items, next_page_token, to_json);
|
|
writeln!(
|
|
writer,
|
|
"{}",
|
|
serde_json::to_string_pretty(&envelope).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let envelope =
|
|
paginated_collection_value(collection_name, items, next_page_token, to_json);
|
|
write!(
|
|
writer,
|
|
"{}",
|
|
openshell_core::yaml::to_string(&envelope).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print single item output to a custom writer in specified format (json/yaml/table).
|
|
///
|
|
/// Writer variant for commands that need custom output destinations.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats, serialization errors, or write errors
|
|
pub fn print_output_single_to_writer<W, T, F>(
|
|
format: impl AsRef<str>,
|
|
writer: &mut W,
|
|
item: &T,
|
|
to_json: F,
|
|
) -> Result<bool>
|
|
where
|
|
W: Write,
|
|
F: Fn(&T) -> serde_json::Value,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let value = to_json(item);
|
|
writeln!(
|
|
writer,
|
|
"{}",
|
|
serde_json::to_string_pretty(&value).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let value = to_json(item);
|
|
write!(
|
|
writer,
|
|
"{}",
|
|
openshell_core::yaml::to_string(&value).into_diagnostic()?
|
|
)
|
|
.into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
/// Print pre-formatted output to a custom writer in specified format (json/yaml/table).
|
|
///
|
|
/// Writer variant for commands that need custom output destinations and use
|
|
/// pre-formatted converters returning `Result<String>`.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(true)` if format was handled (json/yaml), caller should return
|
|
/// - `Ok(false)` if format is "table", caller should continue to table rendering
|
|
/// - `Err` for unsupported formats, conversion errors, or write errors
|
|
pub fn print_output_direct_to_writer<W, J, Y>(
|
|
format: impl AsRef<str>,
|
|
writer: &mut W,
|
|
json_fn: J,
|
|
yaml_fn: Y,
|
|
) -> Result<bool>
|
|
where
|
|
W: Write,
|
|
J: FnOnce() -> Result<String>,
|
|
Y: FnOnce() -> Result<String>,
|
|
{
|
|
match format.as_ref() {
|
|
"json" => {
|
|
let json = json_fn()?;
|
|
writeln!(writer, "{json}").into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"yaml" => {
|
|
let yaml = yaml_fn()?;
|
|
write!(writer, "{yaml}").into_diagnostic()?;
|
|
Ok(true)
|
|
}
|
|
"table" => Ok(false),
|
|
_ => Err(miette::miette!(
|
|
"unsupported output format: {}",
|
|
format.as_ref()
|
|
)),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct TestItem {
|
|
id: u32,
|
|
name: String,
|
|
}
|
|
|
|
fn test_item_to_json(item: &TestItem) -> serde_json::Value {
|
|
serde_json::json!({
|
|
"id": item.id,
|
|
"name": item.name,
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_json() {
|
|
let items = vec![
|
|
TestItem {
|
|
id: 1,
|
|
name: "first".to_string(),
|
|
},
|
|
TestItem {
|
|
id: 2,
|
|
name: "second".to_string(),
|
|
},
|
|
];
|
|
|
|
// Note: This test doesn't capture stdout, but verifies the function returns Ok(true)
|
|
let result = print_output_collection("json", &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_yaml() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
|
|
let result = print_output_collection("yaml", &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_table() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
|
|
let result = print_output_collection("table", &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(!result.unwrap()); // Should return false for table
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_unsupported() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
|
|
let result = print_output_collection("csv", &items, test_item_to_json);
|
|
assert!(result.is_err());
|
|
assert!(
|
|
result
|
|
.unwrap_err()
|
|
.to_string()
|
|
.contains("unsupported output format")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_empty() {
|
|
let items: Vec<TestItem> = vec![];
|
|
|
|
let result = print_output_collection("json", &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_single_json() {
|
|
let item = TestItem {
|
|
id: 42,
|
|
name: "single".to_string(),
|
|
};
|
|
|
|
let result = print_output_single("json", &item, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_single_yaml() {
|
|
let item = TestItem {
|
|
id: 42,
|
|
name: "single".to_string(),
|
|
};
|
|
|
|
let result = print_output_single("yaml", &item, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_direct_json() {
|
|
let json_fn = || Ok(r#"{"test": true}"#.to_string());
|
|
let yaml_fn = || Ok("test: true".to_string());
|
|
|
|
let result = print_output_direct("json", json_fn, yaml_fn);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_direct_yaml() {
|
|
let json_fn = || Ok(r#"{"test": true}"#.to_string());
|
|
let yaml_fn = || Ok("test: true".to_string());
|
|
|
|
let result = print_output_direct("yaml", json_fn, yaml_fn);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_direct_error_propagation() {
|
|
let json_fn = || Err(miette::miette!("json conversion failed"));
|
|
let yaml_fn = || Ok("test: true".to_string());
|
|
|
|
let result = print_output_direct("json", json_fn, yaml_fn);
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().to_string().contains("json conversion"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_to_writer_json() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
let mut buffer = Vec::new();
|
|
|
|
let result =
|
|
print_output_collection_to_writer("json", &mut buffer, &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
|
|
let output = String::from_utf8(buffer).unwrap();
|
|
assert!(output.contains("\"id\": 1"));
|
|
assert!(output.contains("\"name\": \"test\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_collection_to_writer_yaml() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
let mut buffer = Vec::new();
|
|
|
|
let result =
|
|
print_output_collection_to_writer("yaml", &mut buffer, &items, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
|
|
let output = String::from_utf8(buffer).unwrap();
|
|
assert!(output.contains("id: 1"));
|
|
assert!(output.contains("name: test"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_paginated_output_collection_to_writer_json() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
let mut buffer = Vec::new();
|
|
|
|
let handled = print_paginated_output_collection_to_writer(
|
|
"json",
|
|
&mut buffer,
|
|
"items",
|
|
&items,
|
|
"next-token",
|
|
test_item_to_json,
|
|
)
|
|
.unwrap();
|
|
|
|
assert!(handled);
|
|
let output: serde_json::Value = serde_json::from_slice(&buffer).unwrap();
|
|
assert_eq!(output["items"][0]["id"], 1);
|
|
assert_eq!(output["next_page_token"], "next-token");
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_paginated_output_collection_to_writer_yaml() {
|
|
let items = vec![TestItem {
|
|
id: 1,
|
|
name: "test".to_string(),
|
|
}];
|
|
let mut buffer = Vec::new();
|
|
|
|
let handled = print_paginated_output_collection_to_writer(
|
|
"yaml",
|
|
&mut buffer,
|
|
"items",
|
|
&items,
|
|
"",
|
|
test_item_to_json,
|
|
)
|
|
.unwrap();
|
|
|
|
assert!(handled);
|
|
let output: serde_json::Value = serde_yml::from_slice(&buffer).unwrap();
|
|
assert_eq!(output["items"][0]["name"], "test");
|
|
assert_eq!(output["next_page_token"], "");
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_paginated_output_collection_table_is_not_handled() {
|
|
let items = Vec::<TestItem>::new();
|
|
let mut buffer = Vec::new();
|
|
|
|
let handled = print_paginated_output_collection_to_writer(
|
|
"table",
|
|
&mut buffer,
|
|
"items",
|
|
&items,
|
|
"next-token",
|
|
test_item_to_json,
|
|
)
|
|
.unwrap();
|
|
|
|
assert!(!handled);
|
|
assert!(buffer.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_single_to_writer_json() {
|
|
let item = TestItem {
|
|
id: 42,
|
|
name: "single".to_string(),
|
|
};
|
|
let mut buffer = Vec::new();
|
|
|
|
let result = print_output_single_to_writer("json", &mut buffer, &item, test_item_to_json);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
|
|
let output = String::from_utf8(buffer).unwrap();
|
|
assert!(output.contains("\"id\": 42"));
|
|
assert!(output.contains("\"name\": \"single\""));
|
|
}
|
|
|
|
#[test]
|
|
fn test_print_output_direct_to_writer_json() {
|
|
let mut buffer = Vec::new();
|
|
let json_fn = || Ok(r#"{"test": true}"#.to_string());
|
|
let yaml_fn = || Ok("test: true".to_string());
|
|
|
|
let result = print_output_direct_to_writer("json", &mut buffer, json_fn, yaml_fn);
|
|
assert!(result.is_ok());
|
|
assert!(result.unwrap());
|
|
|
|
let output = String::from_utf8(buffer).unwrap();
|
|
assert!(output.contains(r#"{"test": true}"#));
|
|
}
|
|
}
|