1use crate::diff::{Change, FieldChange};
8use crate::model::{ExceptionItem, ExceptionList, Rule};
9use serde::Serialize;
10use serde_json::Value;
11
12#[derive(Debug, Clone, PartialEq)]
17pub struct StackIdentity {
18 pub profile: String,
19 pub host: String,
20 pub space: String,
21}
22
23#[derive(Debug, Clone, PartialEq, Serialize)]
25pub struct PullReport {
26 pub pulled: usize,
27 pub exception_lists: usize,
28 pub exception_items: usize,
29 pub dir: String,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub selected: Option<usize>,
32}
33
34#[derive(Debug, Clone, PartialEq, Serialize)]
40pub struct DiffReport {
41 pub clean: bool,
42 pub local: usize,
43 pub remote: usize,
44 pub changes: Vec<Change>,
45 pub exceptions: ExceptionDrift,
46 #[serde(skip_serializing_if = "is_zero")]
51 pub out_of_scope: usize,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 pub selected: Option<usize>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub local_total: Option<usize>,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize)]
67pub struct PushReport {
68 pub applied: bool,
69 pub created: usize,
70 pub updated: usize,
71 pub skipped_remote_only: usize,
72 pub failed: usize,
73 pub pending: usize,
74 pub lists_created: usize,
75 pub lists_updated: usize,
76 pub items_created: usize,
77 pub items_updated: usize,
78 pub items_removed: usize,
79 #[serde(skip_serializing_if = "is_zero")]
82 pub out_of_scope: usize,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 pub selected: Option<usize>,
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub local_total: Option<usize>,
87}
88
89fn is_zero(n: &usize) -> bool {
90 *n == 0
91}
92
93#[derive(Debug)]
99pub struct Mirror {
100 pub rules: Vec<Rule>,
101 pub lists: Vec<ExceptionList>,
102 pub items: Vec<ExceptionItem>,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize)]
107pub struct ExceptionDrift {
108 pub local: usize,
109 pub remote: usize,
110 pub changes: Vec<ListChange>,
111 pub dangling: Vec<DanglingPointer>,
112}
113
114impl ExceptionDrift {
115 pub fn is_clean(&self) -> bool {
117 self.changes
118 .iter()
119 .all(|c| matches!(c, ListChange::Unchanged { .. }))
120 && self.dangling.is_empty()
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Serialize)]
125#[serde(tag = "change", rename_all = "snake_case")]
126pub enum ListChange {
127 Added {
128 list_id: String,
129 name: String,
130 },
131 Modified {
132 list_id: String,
133 name: String,
134 fields: Vec<FieldChange>,
135 },
136 Unchanged {
137 list_id: String,
138 },
139 RemoteOnly {
140 list_id: String,
141 name: String,
142 },
143 ItemAdded {
144 list_id: String,
145 item_id: String,
146 },
147 ItemModified {
148 list_id: String,
149 item_id: String,
150 fields: Vec<FieldChange>,
151 },
152 ItemRemoved {
153 list_id: String,
154 item_id: String,
155 },
156}
157
158#[derive(Debug, Clone, PartialEq, Serialize)]
163pub struct DanglingPointer {
164 pub rule_id: String,
165 pub list_id: String,
166 pub stored_id: Value,
167 pub live_id: Option<String>,
168}