xposedornot/models.rs
1//! Request and response types for the XposedOrNot API.
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Deserializes `null` JSON values as an empty Vec.
6fn deserialize_null_as_empty_vec<'de, D, T>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
7where
8 D: Deserializer<'de>,
9 T: Deserialize<'de>,
10{
11 let opt: Option<Vec<T>> = Option::deserialize(deserializer)?;
12 Ok(opt.unwrap_or_default())
13}
14
15// ---------------------------------------------------------------------------
16// Email check responses
17// ---------------------------------------------------------------------------
18
19/// Response from the free email check endpoint (`/v1/check-email/{email}`).
20///
21/// The `breaches` field contains a nested array of breach names.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct FreeEmailCheckResponse {
24 /// Nested list of breach names associated with the email.
25 #[serde(default)]
26 pub breaches: Vec<Vec<String>>,
27}
28
29/// A single breach record returned by the Plus API.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct PlusBreachDetail {
32 /// Unique breach identifier.
33 pub breach_id: String,
34 /// Date the breach occurred.
35 pub breached_date: String,
36 /// Logo URL for the breached service.
37 pub logo: String,
38 /// Password risk level.
39 pub password_risk: String,
40 /// Whether the breach is searchable.
41 pub searchable: String,
42 /// Types of data exposed.
43 pub xposed_data: String,
44 /// Number of records exposed.
45 pub xposed_records: u64,
46 /// Description of the exposure.
47 pub xposure_desc: String,
48 /// Domain of the breached service.
49 pub domain: String,
50}
51
52/// Response from the Plus API email check endpoint (`/v3/check-email/{email}`).
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct PlusEmailCheckResponse {
55 /// Status of the request (e.g., "success").
56 pub status: String,
57 /// The email that was checked.
58 pub email: String,
59 /// Detailed breach records.
60 pub breaches: Vec<PlusBreachDetail>,
61}
62
63/// Unified email check result that covers both free and Plus API responses.
64#[derive(Debug, Clone)]
65pub enum EmailCheckResult {
66 /// Result from the free API.
67 Free(FreeEmailCheckResponse),
68 /// Result from the Plus API.
69 Plus(PlusEmailCheckResponse),
70}
71
72// ---------------------------------------------------------------------------
73// Breach listing
74// ---------------------------------------------------------------------------
75
76/// A single breach entry from the breach listing endpoint.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(rename_all = "camelCase")]
79pub struct BreachRecord {
80 /// Unique breach identifier.
81 #[serde(alias = "breachID")]
82 pub breach_id: String,
83 /// Date the breach occurred.
84 pub breached_date: String,
85 /// Domain of the breached service.
86 pub domain: String,
87 /// Industry category.
88 pub industry: String,
89 /// Types of data exposed (array of strings from the API).
90 pub exposed_data: Vec<String>,
91 /// Number of records exposed.
92 pub exposed_records: u64,
93 /// Whether the breach has been verified.
94 pub verified: bool,
95}
96
97/// Response from the breach listing endpoint (`/v1/breaches`).
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct BreachListResponse {
100 /// List of exposed breaches.
101 #[serde(rename = "exposedBreaches")]
102 pub exposed_breaches: Vec<BreachRecord>,
103}
104
105// ---------------------------------------------------------------------------
106// Breach analytics
107// ---------------------------------------------------------------------------
108
109/// Detailed information about a single breach in the analytics response.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct BreachAnalyticsDetail {
112 /// The breach name or identifier.
113 #[serde(default)]
114 pub breach: Option<String>,
115 /// Additional fields are captured dynamically.
116 #[serde(flatten)]
117 pub extra: serde_json::Value,
118}
119
120/// Top-level container for exposed breaches in the analytics response.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct ExposedBreaches {
123 /// List of breach detail objects.
124 #[serde(default)]
125 pub breaches_details: Vec<serde_json::Value>,
126}
127
128/// Summary of breaches in the analytics response.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct BreachesSummary {
131 /// Dynamic summary fields.
132 #[serde(flatten)]
133 pub data: serde_json::Value,
134}
135
136/// Breach metrics in the analytics response.
137#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct BreachMetrics {
139 /// Dynamic metrics fields.
140 #[serde(flatten)]
141 pub data: serde_json::Value,
142}
143
144/// Pastes summary in the analytics response.
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct PastesSummary {
147 /// Dynamic summary fields.
148 #[serde(flatten)]
149 pub data: serde_json::Value,
150}
151
152/// Response from the breach analytics endpoint (`/v1/breach-analytics`).
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct BreachAnalyticsResponse {
155 /// Exposed breaches with details.
156 #[serde(rename = "ExposedBreaches")]
157 pub exposed_breaches: ExposedBreaches,
158 /// Summary of breaches.
159 #[serde(rename = "BreachesSummary")]
160 pub breaches_summary: BreachesSummary,
161 /// Breach metrics.
162 #[serde(rename = "BreachMetrics")]
163 pub breach_metrics: BreachMetrics,
164 /// Pastes summary.
165 #[serde(rename = "PastesSummary")]
166 pub pastes_summary: PastesSummary,
167 /// List of exposed pastes (null from API when no pastes found).
168 #[serde(rename = "ExposedPastes", default, deserialize_with = "deserialize_null_as_empty_vec")]
169 pub exposed_pastes: Vec<serde_json::Value>,
170}
171
172// ---------------------------------------------------------------------------
173// Password check
174// ---------------------------------------------------------------------------
175
176/// Inner result of the anonymous password search.
177#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct PasswordAnonResult {
179 /// The anonymous hash portion returned by the API.
180 pub anon: String,
181 /// Character composition breakdown (e.g., `"D:3;A:8;S:0;L:11"`).
182 pub char: String,
183 /// Number of times this password has been seen in breaches.
184 pub count: String,
185}
186
187/// Wrapper for the password search response.
188#[derive(Debug, Clone, Serialize, Deserialize)]
189pub struct PasswordSearchAnon {
190 /// The anonymous search result.
191 pub anon: String,
192 /// Character composition breakdown.
193 pub char: String,
194 /// Exposure count.
195 pub count: String,
196}
197
198/// Response from the password check endpoint (`/v1/pass/anon/{hash_prefix}`).
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PasswordCheckResponse {
201 /// The anonymous password search result.
202 #[serde(rename = "SearchPassAnon")]
203 pub search_pass_anon: PasswordSearchAnon,
204}