reactive_graph/client/
result.rs

1use crate::client::error::CommandError;
2use crate::client::error::CommandError::NotImplemented;
3use crate::shared::output_format::OutputFormatArgs;
4use reactive_graph_serde::error::SerializationError;
5use reactive_graph_table_model::container::DefaultTableContainer;
6use reactive_graph_table_model::container::TableContainer;
7use reactive_graph_table_model::container::TableInlineFormatSetter;
8use reactive_graph_table_model::container::TableOptions;
9use serde::Serialize;
10use serde_json::Value;
11use std::fmt::Display;
12use std::fmt::Formatter;
13use std::marker::PhantomData;
14use tabled::Tabled;
15use toml::map::Map;
16
17pub enum CommandResponse {
18    Message(String),
19    Value(Value),
20    #[cfg(feature = "toml")]
21    TomlValue(toml::Value),
22    Table(Box<dyn TableContainer>),
23}
24
25pub type CommandResult = Result<CommandResponse, CommandError>;
26
27impl From<String> for CommandResponse {
28    fn from(message: String) -> Self {
29        CommandResponse::Message(message)
30    }
31}
32
33impl From<&str> for CommandResponse {
34    fn from(message: &str) -> Self {
35        CommandResponse::Message(message.to_string())
36    }
37}
38
39impl From<Value> for CommandResponse {
40    fn from(value: Value) -> Self {
41        CommandResponse::Value(value)
42    }
43}
44
45#[cfg(feature = "toml")]
46impl From<toml::Value> for CommandResponse {
47    fn from(value: toml::Value) -> Self {
48        CommandResponse::TomlValue(value)
49    }
50}
51
52impl<S: 'static, T: Clone + Tabled + From<S> + TableInlineFormatSetter + 'static, O: TableOptions + 'static> From<DefaultTableContainer<S, T, O>>
53    for CommandResponse
54{
55    fn from(t: DefaultTableContainer<S, T, O>) -> Self {
56        CommandResponse::Table(t.into_boxed())
57    }
58}
59
60impl Display for CommandResponse {
61    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
62        match self {
63            CommandResponse::Message(message) => write!(f, "{message}"),
64            CommandResponse::Value(value) => write!(f, "{}", serde_json::to_string_pretty(&value).unwrap_or_default()),
65            #[cfg(feature = "toml")]
66            CommandResponse::TomlValue(value) => write!(f, "{}", toml::to_string_pretty(&value).unwrap_or_default()),
67            CommandResponse::Table(table) => write!(f, "{table}"),
68        }
69    }
70}
71
72pub(crate) enum CommandResultBuilderContent<S: Serialize> {
73    Single(S),
74    Collection(Vec<S>),
75}
76
77pub(crate) struct CommandResultBuilder<S: Serialize, T: Clone + Tabled + From<S>, O: TableOptions> {
78    object_or_collection: CommandResultBuilderContent<S>,
79    output_format: Option<OutputFormatArgs>,
80    table_type: PhantomData<T>,
81    table_options: PhantomData<O>,
82}
83
84impl<S: Serialize + 'static, T: Clone + Tabled + From<S> + TableInlineFormatSetter + 'static, O: TableOptions + 'static> CommandResultBuilder<S, T, O> {
85    pub(crate) fn single(single_object: S, output_format: Option<OutputFormatArgs>) -> Self {
86        Self {
87            object_or_collection: CommandResultBuilderContent::Single(single_object),
88            output_format,
89            table_type: Default::default(),
90            table_options: Default::default(),
91        }
92    }
93
94    pub(crate) fn collection(collection: Vec<S>, output_format: Option<OutputFormatArgs>) -> Self {
95        CommandResultBuilder {
96            object_or_collection: CommandResultBuilderContent::Collection(collection),
97            output_format,
98            table_type: Default::default(),
99            table_options: Default::default(),
100        }
101    }
102
103    pub(crate) fn into_command_result(self) -> CommandResult {
104        match self.output_format.clone().unwrap_or_default() {
105            OutputFormatArgs::Table => match self.object_or_collection {
106                CommandResultBuilderContent::Single(single_object) => Ok(DefaultTableContainer::<S, T, O>::from(single_object).into()),
107                CommandResultBuilderContent::Collection(collection) => Ok(DefaultTableContainer::<S, T, O>::from(collection).into()),
108            },
109            OutputFormatArgs::HtmlTable => match self.object_or_collection {
110                CommandResultBuilderContent::Single(single_object) => Ok(DefaultTableContainer::<S, T, O>::from(single_object).into_html_table().into()),
111                CommandResultBuilderContent::Collection(collection) => Ok(DefaultTableContainer::<S, T, O>::from(collection).into_html_table().into()),
112            },
113            OutputFormatArgs::MarkdownTable => match self.object_or_collection {
114                CommandResultBuilderContent::Single(single_object) => Ok(DefaultTableContainer::<S, T, O>::from(single_object).into_markdown_table().into()),
115                CommandResultBuilderContent::Collection(collection) => Ok(DefaultTableContainer::<S, T, O>::from(collection).into_markdown_table().into()),
116            },
117            OutputFormatArgs::Count => match self.object_or_collection {
118                CommandResultBuilderContent::Single(_) => Ok("1 result".into()),
119                CommandResultBuilderContent::Collection(collection) => Ok(format!("{} result(s)", collection.len()).into()),
120            },
121            OutputFormatArgs::Json => match self.object_or_collection {
122                CommandResultBuilderContent::Single(single_object) => Ok(serde_json::to_value(single_object)
123                    .map_err(|e| CommandError::SerializationError(SerializationError::Json(e)))?
124                    .into()),
125                CommandResultBuilderContent::Collection(collection) => Ok(serde_json::to_value(collection)
126                    .map_err(|e| CommandError::SerializationError(SerializationError::Json(e)))?
127                    .into()),
128            },
129            #[cfg(feature = "json5")]
130            OutputFormatArgs::Json5 => Err(NotImplemented),
131            #[cfg(feature = "toml")]
132            OutputFormatArgs::Toml => match self.object_or_collection {
133                CommandResultBuilderContent::Single(single_object) => Ok(toml::Value::try_from(single_object)
134                    .map_err(|e| CommandError::SerializationError(SerializationError::Toml(e)))?
135                    .into()),
136                CommandResultBuilderContent::Collection(collection) => {
137                    let inner = toml::Value::try_from(collection).map_err(|e| CommandError::SerializationError(SerializationError::Toml(e)))?;
138                    let mut map: Map<String, toml::Value> = Map::new();
139                    let type_name = std::any::type_name::<S>().rsplit_once("::").unwrap_or_default().1.to_string();
140                    map.insert(type_name, inner);
141                    let table = toml::Value::Table(map);
142                    Ok(table.into())
143                }
144            },
145        }
146    }
147}