mod semantics;
pub use semantics::{
Diagnostic, Layer, Layers, Resolved, Write, check_value, resolve, resolve_with, simplify,
simplify_key,
};
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::Value as Json;
use crate::extras::icon::Icon;
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettingsNamespace {
pub namespace: String,
pub title: String,
pub extension_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon: Option<Icon>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub order: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub defaults: Option<Json>,
#[serde(default)]
pub sections: Vec<SettingsSection>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettingsSection {
pub id: String,
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advanced: Option<bool>,
#[serde(default)]
pub fields: Vec<Field>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FieldScope {
#[default]
Both,
Global,
Project,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Dynamic {
pub service: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub op: Option<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub struct Cond {
pub key: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub eq: Option<Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ne: Option<Json>,
#[serde(rename = "in", default, skip_serializing_if = "Option::is_none")]
pub in_: Option<Vec<Json>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub truthy: Option<bool>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Restart {
Session,
Host,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StringFormat {
Path,
Url,
Glob,
Keybinding,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EnumDisplay {
#[default]
Select,
Segmented,
Radio,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Choice {
pub value: Json,
pub label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FieldType {
#[default]
Boolean,
Enum,
Multiselect,
String,
Number,
Secret,
Order,
Model,
Action,
Page,
List,
Dict,
Json,
#[serde(other)]
Unknown,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FieldProps {
#[serde(
default,
deserialize_with = "choices",
skip_serializing_if = "Vec::is_empty"
)]
pub options: Vec<Choice>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allow_custom: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display: Option<EnumDisplay>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min: Option<serde_json::Number>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max: Option<serde_json::Number>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step: Option<serde_json::Number>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub unit: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub integer: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multiline: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub placeholder: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_length: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<StringFormat>,
#[serde(
default,
deserialize_with = "strings",
skip_serializing_if = "Vec::is_empty"
)]
pub items: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub filter: Option<Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub confirm: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub sections: Vec<SettingsSection>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item: Option<Box<Field>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<Box<Field>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub key_label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub add_label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema: Option<Json>,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Field {
#[serde(default, skip_serializing_if = "String::is_empty")]
pub key: String,
#[serde(rename = "type")]
pub type_: FieldType,
pub label: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub badge: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default: Option<Json>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scope: Option<FieldScope>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub advanced: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dynamic: Option<Dynamic>,
#[serde(rename = "visibleIf", default, skip_serializing_if = "Option::is_none")]
pub visible_if: Option<Cond>,
#[serde(
rename = "disabledIf",
default,
skip_serializing_if = "Option::is_none"
)]
pub disabled_if: Option<Cond>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub restart: Option<Restart>,
#[serde(flatten)]
pub props: FieldProps,
}
impl Field {
#[must_use]
pub fn new(key: impl Into<String>, type_: FieldType, label: impl Into<String>) -> Self {
Self {
key: key.into(),
type_,
label: label.into(),
description: None,
hint: None,
badge: None,
default: None,
scope: None,
advanced: None,
dynamic: None,
visible_if: None,
disabled_if: None,
restart: None,
props: FieldProps::default(),
}
}
#[must_use]
pub fn boolean(key: impl Into<String>, label: impl Into<String>) -> Self {
Self::new(key, FieldType::Boolean, label)
}
#[must_use]
pub fn enumeration(
key: impl Into<String>,
label: impl Into<String>,
options: Vec<Choice>,
) -> Self {
let mut field = Self::new(key, FieldType::Enum, label);
field.props.options = options;
field
}
#[must_use]
pub fn string(key: impl Into<String>, label: impl Into<String>) -> Self {
Self::new(key, FieldType::String, label)
}
#[must_use]
pub fn number(key: impl Into<String>, label: impl Into<String>) -> Self {
Self::new(key, FieldType::Number, label)
}
}
impl Field {
#[must_use]
pub const fn scope(&self) -> FieldScope {
match self.scope {
Some(scope) => scope,
None => FieldScope::Both,
}
}
#[must_use]
pub const fn stores_value(&self) -> bool {
!matches!(
self.type_,
FieldType::Action | FieldType::Page | FieldType::Unknown
)
}
#[must_use]
pub fn value_at<'a>(&self, values: &'a Json) -> Option<&'a Json> {
semantics::lookup(values, &self.key)
}
}
fn choices<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<Choice>, D::Error> {
let raw = Vec::<Json>::deserialize(de)?;
Ok(raw
.into_iter()
.filter_map(|option| match option {
Json::String(value) => Some(Choice {
label: value.clone(),
value: Json::String(value),
description: None,
}),
object @ Json::Object(_) => serde_json::from_value(object).ok(),
_ => None,
})
.collect())
}
fn strings<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<String>, D::Error> {
let raw = Vec::<Json>::deserialize(de)?;
Ok(raw
.into_iter()
.filter_map(|item| match item {
Json::String(value) => Some(value),
Json::Object(object) => object
.get("value")
.and_then(Json::as_str)
.map(ToOwned::to_owned),
_ => None,
})
.collect())
}