1use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use uuid::Uuid;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
7#[serde(rename_all = "kebab-case")]
8pub enum LintRuleId {
9 ConceptNoParent,
10 DanglingEdge,
11 DirectionViolation,
12 DuplicateName,
13 LowWeightEdge,
14 MinEdgeDensity,
15 MissingEntityType,
16 MissingImplements,
17 MissingIntroducedBy,
18 NoteNoAnnotates,
19 OrphanEntity,
20 PaperMissingMetadata,
21 SupersededNotMarked,
22}
23impl LintRuleId {
24 pub const ALL: [Self; 13] = [
25 Self::ConceptNoParent,
26 Self::DanglingEdge,
27 Self::DirectionViolation,
28 Self::DuplicateName,
29 Self::LowWeightEdge,
30 Self::MinEdgeDensity,
31 Self::MissingEntityType,
32 Self::MissingImplements,
33 Self::MissingIntroducedBy,
34 Self::NoteNoAnnotates,
35 Self::OrphanEntity,
36 Self::PaperMissingMetadata,
37 Self::SupersededNotMarked,
38 ];
39 pub fn as_str(self) -> &'static str {
40 match self {
41 Self::ConceptNoParent => "concept-no-parent",
42 Self::DanglingEdge => "dangling-edge",
43 Self::DirectionViolation => "direction-violation",
44 Self::DuplicateName => "duplicate-name",
45 Self::LowWeightEdge => "low-weight-edge",
46 Self::MinEdgeDensity => "min-edge-density",
47 Self::MissingEntityType => "missing-entity-type",
48 Self::MissingImplements => "missing-implements",
49 Self::MissingIntroducedBy => "missing-introduced-by",
50 Self::NoteNoAnnotates => "note-no-annotates",
51 Self::OrphanEntity => "orphan-entity",
52 Self::PaperMissingMetadata => "paper-missing-metadata",
53 Self::SupersededNotMarked => "superseded-not-marked",
54 }
55 }
56}
57#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
58#[serde(rename_all = "lowercase")]
59pub enum LintSeverity {
60 Info,
61 Warn,
62 Error,
63}
64#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
65#[serde(tag = "substrate", rename_all = "lowercase")]
66pub enum LintSubject {
67 Entity {
68 namespace: String,
69 entity_id: Uuid,
70 entity_name: String,
71 entity_kind: String,
72 },
73 Edge {
74 namespace: String,
75 edge_id: Uuid,
76 },
77 Note {
78 namespace: String,
79 note_id: Uuid,
80 note_kind: String,
81 },
82}
83impl LintSubject {
84 pub fn namespace(&self) -> &str {
85 match self {
86 Self::Entity { namespace, .. }
87 | Self::Edge { namespace, .. }
88 | Self::Note { namespace, .. } => namespace,
89 }
90 }
91 pub fn id(&self) -> Uuid {
92 match self {
93 Self::Entity { entity_id, .. } => *entity_id,
94 Self::Edge { edge_id, .. } => *edge_id,
95 Self::Note { note_id, .. } => *note_id,
96 }
97 }
98 pub fn substrate(&self) -> &'static str {
99 match self {
100 Self::Entity { .. } => "entity",
101 Self::Edge { .. } => "edge",
102 Self::Note { .. } => "note",
103 }
104 }
105}
106#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
107pub struct LintViolation {
108 #[serde(flatten)]
109 pub subject: LintSubject,
110 pub rule: LintRuleId,
111 pub severity: LintSeverity,
112 pub message: String,
113 pub suggestion: String,
114 pub auto_fixable: bool,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 pub member_ids: Option<Vec<Uuid>>,
117}
118#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
119pub struct LintSummary {
120 pub total: u64,
121 pub errors: u64,
122 pub warnings: u64,
123 pub infos: u64,
124 pub by_rule: BTreeMap<LintRuleId, u64>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub fixed: Option<u64>,
127}
128#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
129pub struct PropertyCoverageEntry {
130 pub present: u64,
131 pub total: u64,
132}
133#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
134pub struct PropertyCoverage {
135 pub entities: BTreeMap<String, PropertyCoverageEntry>,
136 pub notes: BTreeMap<String, PropertyCoverageEntry>,
137}
138#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
139pub struct LintStats {
140 pub total_entities: u64,
141 pub total_notes: u64,
142 pub total_edges: u64,
143 pub entity_kinds: BTreeMap<String, u64>,
144 pub note_kinds: BTreeMap<String, u64>,
145 pub edge_relations: BTreeMap<String, u64>,
146 pub avg_density: f64,
147 pub density_histogram: BTreeMap<String, u64>,
148 pub property_coverage: PropertyCoverage,
149}
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
151pub struct LintReport {
152 pub violations: Vec<LintViolation>,
153 pub summary: LintSummary,
154 pub stats: LintStats,
155}
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum FixAction {
159 SoftDeleteEdge,
160 ProposeDuplicateMerge,
161 MarkDeprecated,
162}
163#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
164pub struct ActionIdentity {
165 pub violation_index: u64,
166 pub action: FixAction,
167}
168#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(tag = "status", rename_all = "snake_case")]
170pub enum FixReceipt {
171 Committed {
172 #[serde(flatten)]
173 action: CommittedFixAction,
174 },
175 Conflict {
176 #[serde(flatten)]
177 identity: ActionIdentity,
178 reason: String,
179 },
180 Unsupported {
181 #[serde(flatten)]
182 identity: ActionIdentity,
183 reason: String,
184 },
185}
186#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
187#[serde(tag = "action", rename_all = "snake_case")]
188pub enum CommittedFixAction {
189 SoftDeleteEdge {
190 violation_index: u64,
191 },
192 MarkDeprecated {
193 violation_index: u64,
194 },
195 ProposeDuplicateMerge {
196 violation_index: u64,
197 question_note_id: Uuid,
198 },
199}
200impl FixReceipt {
201 pub fn is_committed(&self) -> bool {
202 matches!(self, Self::Committed { .. })
203 }
204}
205#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "lowercase")]
207pub enum LintFixOutcome {
208 Unknown,
209 Failed,
210}
211#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
212pub struct LintFixFailureDetail {
213 pub outcome: LintFixOutcome,
214 pub failed_action: ActionIdentity,
215 pub confirmed_receipts: Vec<FixReceipt>,
216 pub report: LintReport,
217}