Skip to main content

gaman_core/drift/
contract.rs

1//! User-facing documentation derived from registered semantic drift properties.
2
3use crate::states::EntityKind;
4
5/// Documentation for one dialect property comparator used by `verify`.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct DriftPropertyDoc {
8    /// Entity kind whose modeled form owns this property.
9    pub entity_kind: EntityKind,
10    /// Stable property identifier used in drift findings.
11    pub property: &'static str,
12    /// Semantic value compared between replay and inspection.
13    pub compared: &'static str,
14    /// Related information intentionally excluded from this comparison.
15    pub ignored: &'static str,
16    /// Canonicalization applied before comparison.
17    pub normalization: &'static str,
18    /// Additional limitation users should consider when interpreting drift.
19    pub note: Option<&'static str>,
20}
21
22/// Builds the public contract entry for one registered comparator.
23pub(super) fn property_doc(entity_kind: EntityKind, property: &'static str) -> DriftPropertyDoc {
24    let (compared, normalization) = property_semantics(property);
25    DriftPropertyDoc {
26        entity_kind,
27        property,
28        compared,
29        ignored: ignored_semantics(entity_kind),
30        normalization,
31        note: opaque_note(entity_kind),
32    }
33}
34
35fn property_semantics(property: &str) -> (&'static str, &'static str) {
36    match property {
37        "name" => ("stable entity identity", "exact identifier comparison"),
38        "schema" => (
39            "canonical schema identity",
40            "dialect schema canonicalization",
41        ),
42        "type" => ("canonical column type", "dialect type alias normalization"),
43        "nullable" | "primary_key" | "unique" | "security_definer" => {
44            ("modeled boolean state", "exact boolean comparison")
45        }
46        "default" => (
47            "modeled default expression",
48            "dialect-safe literal canonicalization",
49        ),
50        "columns" | "to_columns" | "values" | "events" => (
51            "ordered modeled values",
52            "order-preserving exact comparison",
53        ),
54        "on_delete" | "on_update" => (
55            "canonical foreign-key action",
56            "canonical action keyword comparison",
57        ),
58        "definition" | "predicate" | "check" | "generated" => (
59            "stable inspected expression text",
60            "dialect comparator normalization",
61        ),
62        _ => (
63            "registered modeled metadata",
64            "comparator-specific canonicalization",
65        ),
66    }
67}
68
69fn ignored_semantics(entity_kind: EntityKind) -> &'static str {
70    match entity_kind {
71        EntityKind::Function => "function body and opaque raw SQL",
72        EntityKind::View => "view body when it is not a registered stable property",
73        EntityKind::Trigger => "trigger body, WHEN source, and opaque raw SQL",
74        EntityKind::Index => {
75            "access method, operator classes, collation, sort metadata, and opaque raw SQL"
76        }
77        EntityKind::Table => "unmanaged table options and unregistered catalog metadata",
78        _ => "unregistered properties and opaque raw SQL",
79    }
80}
81
82fn opaque_note(entity_kind: EntityKind) -> Option<&'static str> {
83    matches!(
84        entity_kind,
85        EntityKind::Index
86            | EntityKind::Constraint
87            | EntityKind::Trigger
88            | EntityKind::Function
89            | EntityKind::View
90            | EntityKind::Extension
91    )
92    .then_some("opaque forms are verified by owned presence only")
93}