Files
onx/src/output.rs
T

404 lines
9.3 KiB
Rust
Raw Normal View History

2026-09-04 18:09:25 -05:00
use std::io::Write;
2026-09-14 20:40:21 -05:00
use chrono::{DateTime, Utc};
2026-09-11 22:13:22 -05:00
use onspring::{
2026-09-14 20:40:21 -05:00
App, CollectionResponse, CreatedWithIdResponse, Field, FileInfo, FormulaOutputType,
ListFieldValue, Multiplicity, PagedResponse, Record, RecordFieldValue, ReportData, ReportInfo,
ReportRow, SaveListItemResponse, SaveRecordResponse, ValueType,
2026-09-11 22:13:22 -05:00
};
use serde::Serialize;
2026-09-11 22:13:22 -05:00
use uuid::Uuid;
use crate::{CliError, CliResult};
#[derive(Debug, Serialize)]
struct ErrorBody<'a> {
pub code: i32,
pub message: &'a str,
}
#[derive(Debug, Serialize)]
struct ErrorEnvelope<'a> {
pub error: ErrorBody<'a>,
}
2026-09-04 18:09:25 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SuccessResponse {
pub ok: bool,
}
2026-09-04 18:58:22 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PagedOutput<T> {
pub page_number: Option<i32>,
pub page_size: Option<i32>,
pub total_pages: Option<i32>,
pub total_records: Option<i32>,
pub items: Option<Vec<T>>,
}
impl<T, U> From<PagedResponse<U>> for PagedOutput<T>
where
T: From<U>,
{
fn from(value: PagedResponse<U>) -> Self {
Self {
page_number: value.page_number,
page_size: value.page_size,
total_pages: value.total_pages,
total_records: value.total_records,
items: value
.items
.map(|items| items.into_iter().map(T::from).collect()),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CollectionOutput<T> {
pub count: Option<i32>,
pub items: Option<Vec<T>>,
}
impl<T, U> From<CollectionResponse<U>> for CollectionOutput<T>
where
T: From<U>,
{
fn from(value: CollectionResponse<U>) -> Self {
Self {
count: value.count,
items: value
.items
.map(|items| items.into_iter().map(T::from).collect()),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AppOutput {
pub href: Option<String>,
pub id: i32,
pub name: Option<String>,
}
impl From<App> for AppOutput {
fn from(value: App) -> Self {
Self {
href: value.href,
id: value.id,
name: value.name,
}
}
}
2026-09-11 22:13:22 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListFieldValueOutput {
pub id: Uuid,
pub name: String,
pub sort_order: i32,
pub numeric_value: Option<f64>,
pub color: Option<String>,
}
impl From<ListFieldValue> for ListFieldValueOutput {
fn from(value: ListFieldValue) -> Self {
Self {
id: value.id,
name: value.name,
sort_order: value.sort_order,
numeric_value: value.numeric_value,
color: value.color,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FieldOutput {
pub id: i32,
pub app_id: i32,
pub name: Option<String>,
#[serde(rename = "type")]
pub field_type: Option<String>,
pub status: Option<String>,
pub is_required: bool,
pub is_unique: bool,
pub multiplicity: Option<Multiplicity>,
pub list_id: Option<i32>,
pub values: Option<Vec<ListFieldValueOutput>>,
pub output_type: Option<FormulaOutputType>,
pub referenced_app_id: Option<i32>,
}
impl From<Field> for FieldOutput {
fn from(value: Field) -> Self {
Self {
id: value.id,
app_id: value.app_id,
name: value.name,
field_type: value.field_type,
status: value.status,
is_required: value.is_required,
is_unique: value.is_unique,
multiplicity: value.multiplicity,
list_id: value.list_id,
values: value
.values
.map(|values| values.into_iter().map(ListFieldValueOutput::from).collect()),
output_type: value.output_type,
referenced_app_id: value.referenced_app_id,
}
}
}
2026-09-14 19:53:56 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RecordFieldValueOutput {
#[serde(rename = "type")]
pub value_type: ValueType,
pub field_id: i32,
pub value: serde_json::Value,
}
impl From<RecordFieldValue> for RecordFieldValueOutput {
fn from(value: RecordFieldValue) -> Self {
Self {
value_type: value.value_type,
field_id: value.field_id,
value: value.value,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RecordOutput {
pub app_id: i32,
pub record_id: i32,
pub field_data: Option<Vec<RecordFieldValueOutput>>,
}
impl From<Record> for RecordOutput {
fn from(value: Record) -> Self {
Self {
app_id: value.app_id,
record_id: value.record_id,
field_data: value
.field_data
.map(|fields| fields.into_iter().map(RecordFieldValueOutput::from).collect()),
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveRecordResponseOutput {
pub id: i32,
pub warnings: Option<Vec<String>>,
}
impl From<SaveRecordResponse> for SaveRecordResponseOutput {
fn from(value: SaveRecordResponse) -> Self {
Self {
id: value.id,
warnings: value.warnings,
}
}
}
2026-09-14 20:40:21 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FileInfoOutput {
#[serde(rename = "type")]
pub file_type: Option<String>,
pub content_type: Option<String>,
pub name: Option<String>,
pub created_date: Option<DateTime<Utc>>,
pub modified_date: Option<DateTime<Utc>>,
pub owner: Option<String>,
pub notes: Option<String>,
pub file_href: Option<String>,
}
impl From<FileInfo> for FileInfoOutput {
fn from(value: FileInfo) -> Self {
Self {
file_type: value.file_type,
content_type: value.content_type,
name: value.name,
created_date: value.created_date,
modified_date: value.modified_date,
owner: value.owner,
notes: value.notes,
file_href: value.file_href,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatedWithIdResponseOutput {
pub id: i32,
}
impl From<CreatedWithIdResponse> for CreatedWithIdResponseOutput {
fn from(value: CreatedWithIdResponse) -> Self {
Self { id: value.id }
}
}
2026-10-01 09:53:20 -05:00
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SaveListItemResponseOutput {
pub id: Uuid,
}
impl From<SaveListItemResponse> for SaveListItemResponseOutput {
fn from(value: SaveListItemResponse) -> Self {
Self { id: value.id }
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReportInfoOutput {
pub app_id: i32,
pub id: i32,
pub name: Option<String>,
pub description: Option<String>,
}
impl From<ReportInfo> for ReportInfoOutput {
fn from(value: ReportInfo) -> Self {
Self {
app_id: value.app_id,
id: value.id,
name: value.name,
description: value.description,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReportRowOutput {
pub record_id: Option<i32>,
pub cells: Option<Vec<serde_json::Value>>,
}
impl From<ReportRow> for ReportRowOutput {
fn from(value: ReportRow) -> Self {
Self {
record_id: value.record_id,
cells: value.cells,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ReportDataOutput {
pub columns: Option<Vec<String>>,
pub rows: Option<Vec<ReportRowOutput>>,
}
impl From<ReportData> for ReportDataOutput {
fn from(value: ReportData) -> Self {
Self {
columns: value.columns,
rows: value
.rows
.map(|rows| rows.into_iter().map(ReportRowOutput::from).collect()),
}
}
}
pub fn render_json<T: Serialize>(value: &T, pretty: bool) -> CliResult<String> {
if pretty {
serde_json::to_string_pretty(value).map_err(|_| CliError::runtime("Failed to serialize output"))
} else {
serde_json::to_string(value).map_err(|_| CliError::runtime("Failed to serialize output"))
}
}
pub fn render_error(err: &CliError, pretty: bool) -> CliResult<String> {
let envelope = ErrorEnvelope {
error: ErrorBody {
code: err.code,
message: &err.message,
},
};
render_json(&envelope, pretty)
}
2026-09-04 18:09:25 -05:00
pub fn write_json<W: Write, T: Serialize>(
writer: &mut W,
value: &T,
pretty: bool,
) -> CliResult<()> {
let rendered = render_json(value, pretty)?;
writeln!(writer, "{rendered}").map_err(|_| CliError::runtime("Failed to write output"))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn render_json_when_called_without_pretty_it_should_print_value() {
let data = json!({
"name": "Stevan",
});
let result = render_json(&data, false);
let expected = Ok(String::from(r#"{"name":"Stevan"}"#));
assert_eq!(result, expected);
}
#[test]
fn render_json_when_called_with_pretty_it_should_pretty_print_value() {
let data = json!({
"name": "Stevan",
});
let result = render_json(&data, true);
let expected = Ok(String::from("{\n \"name\": \"Stevan\"\n}"));
assert_eq!(result, expected);
}
#[test]
fn render_error_when_called_with_error_it_should_render_properly() {
let error = CliError {
code: 1,
message: String::from("This is a test"),
};
let result = render_error(&error, false);
let expected = Ok(String::from(
r#"{"error":{"code":1,"message":"This is a test"}}"#,
));
assert_eq!(result, expected);
}
2026-09-04 18:09:25 -05:00
#[test]
fn write_json_when_called_with_writer_it_should_write_formatted_string() {
let data = json!({"ok": true});
let mut buffer = Vec::new();
let result = write_json(&mut buffer, &data, false);
assert_eq!(result, Ok(()));
let output_str = String::from_utf8(buffer).unwrap();
assert_eq!(output_str, "{\"ok\":true}\n");
}
}