Skip to main content

matomo/models/
visit.rs

1use crate::de::empty_or_vec;
2use serde::Deserialize;
3use serde_with::{serde_as, DisplayFromStr, PickFirst};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum VisitorType {
8    New,
9    Returning,
10    #[serde(other)]
11    Unknown,
12}
13
14/// A single visit from `Live.getLastVisitsDetails`.
15///
16/// Only data-bearing fields are modeled; Matomo's presentation fields
17/// (`*Pretty`, `*Icon`, `*IconSVG`, flag images, etc.) are silently dropped by
18/// not declaring them. `deny_unknown_fields` is deliberately NOT used.
19#[serde_as]
20#[derive(Debug, Clone, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct Visit {
23    // ids
24    pub id_visit: i64,
25    #[serde(default)]
26    pub visitor_id: Option<String>,
27    #[serde(default)]
28    pub fingerprint: Option<String>,
29    #[serde(default)]
30    pub user_id: Option<String>,
31
32    // timestamps (unix seconds)
33    #[serde(default)]
34    pub server_timestamp: Option<i64>,
35    #[serde(default)]
36    pub first_action_timestamp: Option<i64>,
37    #[serde(default)]
38    pub last_action_timestamp: Option<i64>,
39
40    // visit metrics
41    #[serde(default)]
42    pub visit_count: i64,
43    #[serde(default)]
44    pub visit_duration: i64,
45    #[serde(default)]
46    pub actions: i64,
47    #[serde(default)]
48    pub searches: i64,
49    #[serde(default)]
50    pub interactions: i64,
51    #[serde(default)]
52    pub visitor_type: Option<VisitorType>,
53    #[serde_as(as = "PickFirst<(_, DisplayFromStr)>")]
54    #[serde(default)]
55    pub visit_converted: i64,
56
57    // referrer
58    #[serde(default)]
59    pub referrer_type: Option<String>,
60    #[serde(default)]
61    pub referrer_name: Option<String>,
62    #[serde(default)]
63    pub referrer_keyword: Option<String>,
64    #[serde(default)]
65    pub referrer_url: Option<String>,
66
67    // geo
68    #[serde(default)]
69    pub continent: Option<String>,
70    #[serde(default)]
71    pub country: Option<String>,
72    #[serde(default)]
73    pub country_code: Option<String>,
74    #[serde(default)]
75    pub region: Option<String>,
76    #[serde(default)]
77    pub city: Option<String>,
78    /// Matomo sends these as strings.
79    #[serde_as(as = "Option<PickFirst<(DisplayFromStr, _)>>")]
80    #[serde(default)]
81    pub latitude: Option<f64>,
82    #[serde_as(as = "Option<PickFirst<(DisplayFromStr, _)>>")]
83    #[serde(default)]
84    pub longitude: Option<f64>,
85
86    // device / os / browser
87    #[serde(default)]
88    pub device_type: Option<String>,
89    #[serde(default)]
90    pub device_brand: Option<String>,
91    #[serde(default)]
92    pub device_model: Option<String>,
93    #[serde(default)]
94    pub operating_system_name: Option<String>,
95    #[serde(default)]
96    pub browser_name: Option<String>,
97    #[serde(default)]
98    pub browser_version: Option<String>,
99    #[serde(default)]
100    pub resolution: Option<String>,
101
102    #[serde(default)]
103    pub action_details: Vec<ActionDetail>,
104
105    /// Integer `0` when there are no conversions, an array otherwise.
106    #[serde(default, deserialize_with = "empty_or_vec")]
107    pub goal_conversions: Vec<GoalConversion>,
108}
109
110/// A goal conversion attached to a visit.
111#[derive(Debug, Clone, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct GoalConversion {
114    #[serde(default)]
115    pub goal_id: Option<i64>,
116    #[serde(default)]
117    pub revenue: Option<f64>,
118    #[serde(default)]
119    pub url: Option<String>,
120    #[serde(default)]
121    pub timestamp: Option<i64>,
122}
123
124/// An entry in `actionDetails`. Internally tagged on Matomo's `type` field.
125/// `action` and `download` are typed precisely from harvested data; the others
126/// are modeled minimally; anything else falls through to `Unknown` for
127/// forward-compat. Never `#[serde(untagged)]`.
128#[derive(Debug, Clone, Deserialize)]
129#[serde(tag = "type", rename_all = "lowercase")]
130pub enum ActionDetail {
131    Action(ActionPageview),
132    Download(ActionLink),
133    Outlink(ActionLink),
134    Event(ActionEvent),
135    Search(ActionSearch),
136    Goal(ActionGoal),
137    #[serde(other)]
138    Unknown,
139}
140
141#[derive(Debug, Clone, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct ActionPageview {
144    #[serde(default)]
145    pub url: Option<String>,
146    #[serde(default)]
147    pub page_title: Option<String>,
148    #[serde(default)]
149    pub page_id_action: Option<i64>,
150    #[serde(default)]
151    pub page_id: Option<i64>,
152    #[serde(default)]
153    pub idpageview: Option<String>,
154    #[serde(default)]
155    pub time_spent: Option<i64>,
156    #[serde(default)]
157    pub pageview_position: Option<i64>,
158    #[serde(default)]
159    pub timestamp: Option<i64>,
160}
161
162#[derive(Debug, Clone, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct ActionLink {
165    #[serde(default)]
166    pub url: Option<String>,
167    #[serde(default)]
168    pub page_title: Option<String>,
169    #[serde(default)]
170    pub page_id_action: Option<i64>,
171    #[serde(default)]
172    pub page_id: Option<i64>,
173    #[serde(default)]
174    pub idpageview: Option<String>,
175    #[serde(default)]
176    pub timestamp: Option<i64>,
177}
178
179#[derive(Debug, Clone, Deserialize)]
180#[serde(rename_all = "camelCase")]
181pub struct ActionEvent {
182    #[serde(default)]
183    pub event_category: Option<String>,
184    #[serde(default)]
185    pub event_action: Option<String>,
186    #[serde(default)]
187    pub event_name: Option<String>,
188    #[serde(default)]
189    pub timestamp: Option<i64>,
190}
191
192#[derive(Debug, Clone, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct ActionSearch {
195    #[serde(default)]
196    pub keyword: Option<String>,
197    #[serde(default)]
198    pub timestamp: Option<i64>,
199}
200
201#[derive(Debug, Clone, Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct ActionGoal {
204    #[serde(default)]
205    pub goal_id: Option<i64>,
206    #[serde(default)]
207    pub revenue: Option<f64>,
208    #[serde(default)]
209    pub timestamp: Option<i64>,
210}