1use crate::diff::{Change, FieldChange};
2use crate::model::{ExceptionItem, ExceptionList, Rule};
3use serde::Serialize;
4use serde_json::Value;
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct StackIdentity {
8 pub profile: String,
9 pub host: String,
10 pub space: String,
11}
12
13#[derive(Debug, Clone, PartialEq, Serialize)]
14pub struct PullReport {
15 pub pulled: usize,
16 pub exception_lists: usize,
17 pub exception_items: usize,
18 pub dir: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub selected: Option<usize>,
21 #[serde(skip_serializing_if = "is_zero")]
22 pub value_lists: usize,
23 #[serde(skip_serializing_if = "Vec::is_empty")]
24 pub value_list_warnings: Vec<crate::value_lists::Warning>,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize)]
28pub struct DiffReport {
29 pub clean: bool,
30 pub local: usize,
31 pub remote: usize,
32 pub changes: Vec<Change>,
33 pub exceptions: ExceptionDrift,
34 #[serde(skip_serializing_if = "Option::is_none")]
35 pub value_lists: Option<super::values::ValueListDrift>,
36 #[serde(skip_serializing_if = "is_zero")]
37 pub out_of_scope: usize,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub selected: Option<usize>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub local_total: Option<usize>,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize)]
45pub struct PushReport {
46 pub applied: bool,
47 pub created: usize,
48 pub updated: usize,
49 pub skipped_remote_only: usize,
50 pub failed: usize,
51 pub pending: usize,
52 pub lists_created: usize,
53 pub lists_updated: usize,
54 pub items_created: usize,
55 pub items_updated: usize,
56 pub items_removed: usize,
57 #[serde(skip_serializing_if = "is_zero")]
58 pub value_lists_created: usize,
59 #[serde(skip_serializing_if = "is_zero")]
60 pub value_lists_updated: usize,
61 #[serde(skip_serializing_if = "is_zero")]
62 pub values_added: usize,
63 #[serde(skip_serializing_if = "is_zero")]
64 pub values_removed: usize,
65 #[serde(skip_serializing_if = "is_zero")]
66 pub value_lists_unknown: usize,
67 #[serde(skip_serializing_if = "is_zero")]
68 pub value_lists_not_reached: usize,
69 #[serde(skip_serializing_if = "is_zero")]
70 pub out_of_scope: usize,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub selected: Option<usize>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub local_total: Option<usize>,
75}
76
77fn is_zero(n: &usize) -> bool {
78 *n == 0
79}
80
81#[derive(Debug)]
82pub struct Mirror {
83 pub rules: Vec<Rule>,
84 pub lists: Vec<ExceptionList>,
85 pub items: Vec<ExceptionItem>,
86 pub value_lists: Vec<crate::value_lists::LocalPair>,
87 pub has_value_lists: bool,
88}
89
90#[derive(Debug, Clone, PartialEq, Serialize)]
91pub struct ExceptionDrift {
92 pub local: usize,
93 pub remote: usize,
94 pub changes: Vec<ListChange>,
95 pub dangling: Vec<DanglingPointer>,
96}
97
98impl ExceptionDrift {
99 pub fn is_clean(&self) -> bool {
100 self.changes
101 .iter()
102 .all(|c| matches!(c, ListChange::Unchanged { .. }))
103 && self.dangling.is_empty()
104 }
105}
106
107#[derive(Debug, Clone, PartialEq, Serialize)]
108#[serde(tag = "change", rename_all = "snake_case")]
109pub enum ListChange {
110 Added {
111 list_id: String,
112 name: String,
113 },
114 Modified {
115 list_id: String,
116 name: String,
117 fields: Vec<FieldChange>,
118 },
119 Unchanged {
120 list_id: String,
121 },
122 RemoteOnly {
123 list_id: String,
124 name: String,
125 },
126 ItemAdded {
127 list_id: String,
128 item_id: String,
129 },
130 ItemModified {
131 list_id: String,
132 item_id: String,
133 fields: Vec<FieldChange>,
134 },
135 ItemRemoved {
136 list_id: String,
137 item_id: String,
138 },
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize)]
142pub struct DanglingPointer {
143 pub rule_id: String,
144 pub list_id: String,
145 pub stored_id: Value,
146 pub live_id: Option<String>,
147}