use indexmap::IndexMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
fn is_false(b: &bool) -> bool {
!*b
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TypeDefinition {
pub name: String,
pub description: String,
pub when_to_use: String,
#[serde(default)]
pub boundaries: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exemplar: Option<Exemplar>,
#[serde(default, rename = "examples", skip_serializing)]
#[schemars(skip)]
pub legacy_examples: Option<serde_yaml_ng::Value>,
#[serde(default)]
pub system_message: Option<String>,
pub sections: Vec<SectionDef>,
pub metadata_fields: Vec<MetadataFieldDef>,
pub title_weight: f32,
pub text_fields: Vec<String>,
pub hierarchy_relationship: String,
#[serde(default)]
pub edge_weight_overrides: IndexMap<String, f32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub no_self_loop_relationships: Vec<String>,
#[serde(default, rename = "propagating_relationships", skip_serializing)]
#[schemars(skip)]
pub legacy_propagating_relationships: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "is_false")]
pub leaf: bool,
pub updatable_fields: Vec<String>,
pub health_required_fields: Vec<String>,
pub staleness_threshold_days: u32,
pub write_rules: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required_outgoing: Vec<RequiredOutgoing>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub constraints: Vec<ConstraintDef>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub due: Option<DueAxis>,
#[serde(skip)]
pub edge_weights: IndexMap<String, f32>,
#[serde(skip)]
pub declared_metadata_keys: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RequiredOutgoing {
pub relationships: Vec<String>,
pub cardinality: RequiredCardinality,
#[serde(default)]
pub severity: ConstraintSeverity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ConstraintSeverity {
#[default]
Warn,
Block,
}
impl ConstraintSeverity {
pub fn block() -> Self {
Self::Block
}
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
pub enum ConstraintDef {
RequiresWhen {
field: String,
when_field: String,
when_value: String,
#[serde(default)]
severity: ConstraintSeverity,
},
Unique {
fields: Vec<String>,
#[serde(default = "ConstraintSeverity::block")]
severity: ConstraintSeverity,
},
EnumFromNeighbour {
field: String,
rel_type: String,
section: String,
#[serde(default)]
severity: ConstraintSeverity,
},
StatusPropagation {
field: String,
value: String,
rel_type: String,
direction: PropagationDirection,
#[serde(default)]
severity: ConstraintSeverity,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PropagationDirection {
Incoming,
Outgoing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RequiredCardinality {
AtLeastOne,
}
impl std::fmt::Display for RequiredCardinality {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
RequiredCardinality::AtLeastOne => "at_least_one",
})
}
}
impl RequiredOutgoing {
pub fn admits(&self, outgoing_count: usize) -> bool {
match self.cardinality {
RequiredCardinality::AtLeastOne => outgoing_count >= 1,
}
}
}
impl TypeDefinition {
pub fn edge_weight(&self, rel: &str) -> f32 {
if let Some(&w) = self.edge_weights.get(rel) {
return w;
}
if let Some(&w) = self.edge_weights.get("_default") {
return w;
}
1.0
}
pub fn section(&self, key: &str) -> Option<&SectionDef> {
self.sections.iter().find(|s| s.key == key)
}
pub fn catch_all_section(&self) -> Option<&SectionDef> {
self.sections.iter().find(|s| s.catch_all)
}
pub fn metadata_field(&self, key: &str) -> Option<&MetadataFieldDef> {
self.metadata_fields.iter().find(|f| f.key == key)
}
pub fn suggest_metadata_field(&self, key: &str) -> Option<String> {
crate::schema::closest_match(key, self.metadata_fields.iter().map(|f| f.key.as_str()))
}
pub fn suggest_section(&self, key: &str) -> Option<String> {
crate::schema::closest_match(key, self.sections.iter().map(|s| s.key.as_str()))
}
pub fn required_sections(&self) -> impl Iterator<Item = &SectionDef> {
self.sections.iter().filter(|s| s.required)
}
pub fn optional_sections(&self) -> impl Iterator<Item = &SectionDef> {
self.sections.iter().filter(|s| !s.required)
}
pub fn system_message_str(&self) -> &str {
self.system_message.as_deref().unwrap_or("")
}
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Exemplar {
pub title: String,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub metadata: IndexMap<String, String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub sections: IndexMap<String, String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub relations: Vec<ExemplarRelation>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ExemplarRelation {
pub to: String,
#[serde(rename = "type")]
pub rel_type: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
pub fn derive_section_key(heading: &str) -> String {
heading.to_lowercase().replace(' ', "_")
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SectionDef {
pub key: String,
pub heading: String,
#[serde(default)]
pub required: bool,
pub search_weight: f32,
#[serde(default)]
pub catch_all: bool,
#[serde(default)]
pub write_rules: Vec<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub item_pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub table: Option<TableFormat>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub example: Option<String>,
#[serde(
default = "ConstraintSeverity::block",
skip_serializing_if = "severity_is_block"
)]
pub format_severity: ConstraintSeverity,
#[serde(skip)]
pub compiled_content: Option<crate::content_expr::ContentExpr>,
#[serde(skip)]
pub format_problems: Vec<String>,
}
fn severity_is_block(s: &ConstraintSeverity) -> bool {
*s == ConstraintSeverity::Block
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TableFormat {
pub columns: Vec<String>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub column_patterns: IndexMap<String, String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct DueAxis {
pub date_field: String,
pub status_field: String,
pub open_values: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lead_section: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct MetadataFieldDef {
pub key: String,
pub description: String,
pub field_type: FieldType,
#[serde(default)]
pub default_value: Option<String>,
#[serde(default)]
pub enum_values: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(default, rename = "optional", skip_serializing)]
#[schemars(skip)]
pub legacy_optional: Option<bool>,
#[serde(skip)]
#[schemars(skip)]
pub required_resolved: bool,
#[serde(default)]
pub init_timestamp: bool,
#[serde(default)]
pub auto_timestamp: bool,
#[serde(default)]
pub serialization: Serialization,
#[serde(default)]
pub filterable: Filterable,
}
impl MetadataFieldDef {
pub fn is_required(&self) -> bool {
self.required_resolved
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum FieldType {
String,
Number,
Date,
Boolean,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum Serialization {
#[default]
Default,
CsvArray,
OmitWhenFalsy,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, JsonSchema, Default)]
#[serde(rename_all = "snake_case")]
pub enum Filterable {
#[default]
None,
Equality,
Range,
}
impl Filterable {
pub fn as_wire_str(self) -> Option<&'static str> {
match self {
Filterable::None => None,
Filterable::Equality => Some("equality"),
Filterable::Range => Some("range"),
}
}
}