use std::collections::HashMap;
use std::ops::Deref;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use ts_rs::TS;
use super::{
ColumnConfigUpdate, OptionalUpdate, PluginConfigUpdate, PluginUpdate, SettingsUpdate, TableRef,
TableUpdate, ThemeUpdate, TitleUpdate, UnknownKeys, VersionUpdate,
};
use crate::config::{ViewConfig, ViewConfigUpdate};
pub type ColumnConfigMap = HashMap<String, serde_json::Map<String, Value>>;
pub const CREATE_REQUIRES_TABLE: &str = "Cannot create a panel without a `table` — `load()` a \
`Client` and include `table` in the config, or use \
`addPanel()`";
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
pub enum ViewerConfigError {
#[error("{}", CREATE_REQUIRES_TABLE)]
CreateRequiresTable,
#[error("Unresolved inline derived table")]
UnresolvedTableRef,
}
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, TS)]
pub struct ViewerConfig<V: TS = String, T: TS = TableRef> {
pub settings: bool,
#[serde(flatten)]
pub panel: PanelViewerConfig<V, T>,
}
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, TS)]
pub struct PanelViewerConfig<V: TS = String, T: TS = TableRef> {
pub version: V,
pub columns_config: ColumnConfigMap,
pub plugin: String,
pub plugin_config: serde_json::Map<String, Value>,
pub table: T,
pub theme: Option<String>,
pub title: Option<String>,
#[serde(flatten)]
pub view_config: ViewConfig,
#[serde(flatten, skip_serializing)]
#[ts(skip)]
pub unknown: UnknownKeys,
}
impl<V: TS, T: TS> Deref for ViewerConfig<V, T> {
type Target = PanelViewerConfig<V, T>;
fn deref(&self) -> &Self::Target {
&self.panel
}
}
impl<V: TS, T: TS> PanelViewerConfig<V, T> {
pub fn map_table<U: TS>(self, f: impl FnOnce(T) -> U) -> PanelViewerConfig<V, U> {
PanelViewerConfig {
version: self.version,
columns_config: self.columns_config,
plugin: self.plugin,
plugin_config: self.plugin_config,
table: f(self.table),
theme: self.theme,
title: self.title,
view_config: self.view_config,
unknown: self.unknown,
}
}
}
impl<V: TS, T: TS> ViewerConfig<V, T> {
pub fn map_table<U: TS>(self, f: impl FnOnce(T) -> U) -> ViewerConfig<V, U> {
ViewerConfig {
settings: self.settings,
panel: self.panel.map_table(f),
}
}
}
#[derive(Clone, Debug, TS, Deserialize, PartialEq, Serialize)]
#[serde(transparent)]
pub struct PluginConfig(serde_json::Value);
impl Deref for PluginConfig {
type Target = Value;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
pub struct ViewerConfigUpdate {
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub version: VersionUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub plugin: PluginUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub title: TitleUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub table: TableUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub theme: ThemeUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub settings: SettingsUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub plugin_config: PluginConfigUpdate,
#[serde(default)]
#[ts(as = "Option<_>")]
#[ts(optional)]
pub columns_config: ColumnConfigUpdate,
#[serde(flatten)]
pub view_config: ViewConfigUpdate,
#[serde(flatten, skip_serializing)]
#[ts(skip)]
pub unknown: UnknownKeys,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, TS)]
pub struct ViewerConfigInitial {
pub table: String,
#[ts(optional)]
pub version: Option<String>,
#[ts(optional)]
pub plugin: Option<String>,
#[ts(optional = nullable)]
pub title: Option<String>,
#[ts(optional = nullable)]
pub theme: Option<String>,
#[ts(optional)]
pub plugin_config: Option<serde_json::Map<String, Value>>,
#[ts(optional)]
pub columns_config: Option<ColumnConfigMap>,
#[serde(flatten)]
pub view_config: ViewConfigUpdate,
#[serde(flatten, skip_serializing)]
#[ts(skip)]
pub unknown: UnknownKeys,
}
impl ViewerConfigInitial {
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
version: None,
plugin: None,
title: None,
theme: None,
plugin_config: None,
columns_config: None,
view_config: ViewConfigUpdate::default(),
unknown: UnknownKeys,
}
}
}
fn up<T: Clone>(value: Option<T>) -> OptionalUpdate<T> {
match value {
Some(value) => OptionalUpdate::Update(value),
None => OptionalUpdate::Missing,
}
}
impl From<ViewerConfigInitial> for ViewerConfigUpdate {
fn from(value: ViewerConfigInitial) -> Self {
ViewerConfigUpdate {
version: up(value.version),
plugin: up(value.plugin),
title: up(value.title),
table: OptionalUpdate::Update(value.table.into()),
theme: up(value.theme),
settings: OptionalUpdate::Missing,
plugin_config: up(value.plugin_config),
columns_config: up(value.columns_config),
view_config: value.view_config,
unknown: value.unknown,
}
}
}
fn down<T: Clone>(value: OptionalUpdate<T>) -> Option<T> {
match value {
OptionalUpdate::Update(value) => Some(value),
OptionalUpdate::Missing | OptionalUpdate::SetDefault => None,
}
}
impl TryFrom<ViewerConfigUpdate> for ViewerConfigInitial {
type Error = ViewerConfigError;
fn try_from(value: ViewerConfigUpdate) -> Result<Self, Self::Error> {
let ViewerConfigUpdate {
version,
plugin,
title,
table,
theme,
settings: _settings,
plugin_config,
columns_config,
view_config,
unknown,
} = value;
let OptionalUpdate::Update(table) = table else {
return Err(ViewerConfigError::CreateRequiresTable);
};
let table = table.name()?.to_owned();
Ok(Self {
table,
version: down(version),
plugin: down(plugin),
title: down(title),
theme: down(theme),
plugin_config: down(plugin_config),
columns_config: down(columns_config),
view_config,
unknown,
})
}
}
impl std::fmt::Display for ViewerConfigUpdate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
serde_json::to_string(self).map_err(|_| std::fmt::Error)?
)
}
}