Skip to main content

epcis_models/
events.rs

1//! EPCIS 2.0 event model definitions.
2
3#![deny(missing_docs)]
4#![deny(unsafe_code)]
5#![warn(clippy::pedantic)]
6#![allow(clippy::module_name_repetitions)]
7
8use serde::{Deserialize, Serialize};
9use chrono::{DateTime, Utc};
10use crate::types::{
11    Action, BizLocation, BizTransaction, Destination, Epc,
12    ErrorDeclaration, QuantityElement, ReadPoint, SensorElement, Source,
13};
14use crate::cbv::{BizStep, Disposition};
15
16/// Persistent disposition settings for tracking updates in an `ObjectEvent`.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct PersistentDisposition {
20    /// List of dispositions to unset
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub unset: Option<Vec<Disposition>>,
23    /// List of dispositions to set
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub set: Option<Vec<Disposition>>,
26}
27
28/// An `ObjectEvent` captures an observation, addition, or deletion of specific items.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct ObjectEvent {
32    /// Date and time when the event occurred
33    pub event_time: DateTime<Utc>,
34    /// Timezone offset (e.g. "+01:00")
35    pub event_time_zone_offset: String,
36    /// Record timestamp (optional, set by repository)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub record_time: Option<DateTime<Utc>>,
39    /// Event identifier (often URN like urn:uuid)
40    #[serde(rename = "eventID", skip_serializing_if = "Option::is_none")]
41    pub event_id: Option<String>,
42    /// Error declaration for corrective events
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub error_declaration: Option<ErrorDeclaration>,
45    
46    /// Action specifying lifecycle state change
47    pub action: Action,
48    /// List of item EPCs
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub epc_list: Option<Vec<Epc>>,
51    /// List of class/quantity items
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub quantity_list: Option<Vec<QuantityElement>>,
54    
55    /// Business step (e.g. receiving)
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub biz_step: Option<BizStep>,
58    /// Item disposition (e.g. `in_transit`)
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub disposition: Option<Disposition>,
61    /// Read point identifier
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub read_point: Option<ReadPoint>,
64    /// Business location identifier
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub biz_location: Option<BizLocation>,
67    /// List of business transactions linked to the event
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub biz_transaction_list: Option<Vec<BizTransaction>>,
70    /// List of source identifiers
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub source_list: Option<Vec<Source>>,
73    /// List of destination identifiers
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub destination_list: Option<Vec<Destination>>,
76    /// List of sensor records
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub sensor_element_list: Option<Vec<SensorElement>>,
79    /// Persistent state changes of disposition
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub persistent_disposition: Option<PersistentDisposition>,
82
83    /// Extra custom JSON elements
84    #[serde(flatten)]
85    pub extensions: serde_json::Map<String, serde_json::Value>,
86}
87
88impl ObjectEvent {
89    /// Creates a new `ObjectEvent` with required properties.
90    #[must_use]
91    pub fn new(event_time: DateTime<Utc>, tz_offset: String, action: Action) -> Self {
92        Self {
93            event_time,
94            event_time_zone_offset: tz_offset,
95            record_time: None,
96            event_id: None,
97            error_declaration: None,
98            action,
99            epc_list: None,
100            quantity_list: None,
101            biz_step: None,
102            disposition: None,
103            read_point: None,
104            biz_location: None,
105            biz_transaction_list: None,
106            source_list: None,
107            destination_list: None,
108            sensor_element_list: None,
109            persistent_disposition: None,
110            extensions: serde_json::Map::new(),
111        }
112    }
113}
114
115/// An `AggregationEvent` captures an event where objects are aggregated into a parent container.
116#[derive(Debug, Clone, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase")]
118pub struct AggregationEvent {
119    /// Date and time when the event occurred
120    pub event_time: DateTime<Utc>,
121    /// Timezone offset
122    pub event_time_zone_offset: String,
123    /// Record timestamp (optional)
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub record_time: Option<DateTime<Utc>>,
126    /// Event identifier
127    #[serde(rename = "eventID", skip_serializing_if = "Option::is_none")]
128    pub event_id: Option<String>,
129    /// Error declaration details
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub error_declaration: Option<ErrorDeclaration>,
132    
133    /// Action specifying parent-child state change (ADD, DELETE, OBSERVE)
134    pub action: Action,
135    /// Parent container EPC
136    #[serde(rename = "parentID", skip_serializing_if = "Option::is_none")]
137    pub parent_id: Option<Epc>,
138    /// List of child EPCs
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub child_e_p_cs: Option<Vec<Epc>>,
141    /// List of child quantities
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub child_quantity_list: Option<Vec<QuantityElement>>,
144    
145    /// Business step (e.g. packing)
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub biz_step: Option<BizStep>,
148    /// Disposition
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub disposition: Option<Disposition>,
151    /// Read point
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub read_point: Option<ReadPoint>,
154    /// Business location
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub biz_location: Option<BizLocation>,
157    /// Business transactions
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub biz_transaction_list: Option<Vec<BizTransaction>>,
160    /// Source list
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub source_list: Option<Vec<Source>>,
163    /// Destination list
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub destination_list: Option<Vec<Destination>>,
166    /// Sensor element list
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub sensor_element_list: Option<Vec<SensorElement>>,
169
170    /// Extra custom fields
171    #[serde(flatten)]
172    pub extensions: serde_json::Map<String, serde_json::Value>,
173}
174
175impl AggregationEvent {
176    /// Creates a new `AggregationEvent` with required properties.
177    #[must_use]
178    pub fn new(event_time: DateTime<Utc>, tz_offset: String, action: Action) -> Self {
179        Self {
180            event_time,
181            event_time_zone_offset: tz_offset,
182            record_time: None,
183            event_id: None,
184            error_declaration: None,
185            action,
186            parent_id: None,
187            child_e_p_cs: None,
188            child_quantity_list: None,
189            biz_step: None,
190            disposition: None,
191            read_point: None,
192            biz_location: None,
193            biz_transaction_list: None,
194            source_list: None,
195            destination_list: None,
196            sensor_element_list: None,
197            extensions: serde_json::Map::new(),
198        }
199    }
200}
201
202/// A `TransformationEvent` captures the consumption of input items and the creation of output items.
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct TransformationEvent {
206    /// Date and time when the event occurred
207    pub event_time: DateTime<Utc>,
208    /// Timezone offset
209    pub event_time_zone_offset: String,
210    /// Record timestamp
211    #[serde(skip_serializing_if = "Option::is_none")]
212    pub record_time: Option<DateTime<Utc>>,
213    /// Event identifier
214    #[serde(rename = "eventID", skip_serializing_if = "Option::is_none")]
215    pub event_id: Option<String>,
216    /// Error declaration details
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub error_declaration: Option<ErrorDeclaration>,
219    
220    /// Input item EPCs
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub input_e_p_c_list: Option<Vec<Epc>>,
223    /// Input item quantities
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub input_quantity_list: Option<Vec<QuantityElement>>,
226    /// Output item EPCs
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub output_e_p_c_list: Option<Vec<Epc>>,
229    /// Output item quantities
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub output_quantity_list: Option<Vec<QuantityElement>>,
232    /// Unique transformation logic identifier
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub transformation_id: Option<String>,
235    
236    /// Business step
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub biz_step: Option<BizStep>,
239    /// Disposition
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub disposition: Option<Disposition>,
242    /// Read point
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub read_point: Option<ReadPoint>,
245    /// Business location
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub biz_location: Option<BizLocation>,
248    /// Business transactions
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub biz_transaction_list: Option<Vec<BizTransaction>>,
251    /// Source list
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub source_list: Option<Vec<Source>>,
254    /// Destination list
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub destination_list: Option<Vec<Destination>>,
257    /// Sensor element list
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub sensor_element_list: Option<Vec<SensorElement>>,
260
261    /// Extra custom fields
262    #[serde(flatten)]
263    pub extensions: serde_json::Map<String, serde_json::Value>,
264}
265
266impl TransformationEvent {
267    /// Creates a new `TransformationEvent` with required properties.
268    #[must_use]
269    pub fn new(event_time: DateTime<Utc>, tz_offset: String) -> Self {
270        Self {
271            event_time,
272            event_time_zone_offset: tz_offset,
273            record_time: None,
274            event_id: None,
275            error_declaration: None,
276            input_e_p_c_list: None,
277            input_quantity_list: None,
278            output_e_p_c_list: None,
279            output_quantity_list: None,
280            transformation_id: None,
281            biz_step: None,
282            disposition: None,
283            read_point: None,
284            biz_location: None,
285            biz_transaction_list: None,
286            source_list: None,
287            destination_list: None,
288            sensor_element_list: None,
289            extensions: serde_json::Map::new(),
290        }
291    }
292}
293
294/// An `AssociationEvent` captures the assembly or disassembly of reusable assets.
295#[derive(Debug, Clone, Serialize, Deserialize)]
296#[serde(rename_all = "camelCase")]
297pub struct AssociationEvent {
298    /// Date and time when the event occurred
299    pub event_time: DateTime<Utc>,
300    /// Timezone offset
301    pub event_time_zone_offset: String,
302    /// Record timestamp
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub record_time: Option<DateTime<Utc>>,
305    /// Event identifier
306    #[serde(rename = "eventID", skip_serializing_if = "Option::is_none")]
307    pub event_id: Option<String>,
308    /// Error declaration details
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub error_declaration: Option<ErrorDeclaration>,
311    
312    /// Action specifying parent-child state change (ADD, DELETE, OBSERVE)
313    pub action: Action,
314    /// Parent container EPC
315    #[serde(rename = "parentID", skip_serializing_if = "Option::is_none")]
316    pub parent_id: Option<Epc>,
317    /// List of child EPCs
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub child_e_p_cs: Option<Vec<Epc>>,
320    /// List of child quantities
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub child_quantity_list: Option<Vec<QuantityElement>>,
323    
324    /// Business step
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub biz_step: Option<BizStep>,
327    /// Disposition
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub disposition: Option<Disposition>,
330    /// Read point
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub read_point: Option<ReadPoint>,
333    /// Business location
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub biz_location: Option<BizLocation>,
336    /// Business transactions
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub biz_transaction_list: Option<Vec<BizTransaction>>,
339    /// Source list
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub source_list: Option<Vec<Source>>,
342    /// Destination list
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub destination_list: Option<Vec<Destination>>,
345    /// Sensor element list
346    #[serde(skip_serializing_if = "Option::is_none")]
347    pub sensor_element_list: Option<Vec<SensorElement>>,
348
349    /// Extra custom fields
350    #[serde(flatten)]
351    pub extensions: serde_json::Map<String, serde_json::Value>,
352}
353
354impl AssociationEvent {
355    /// Creates a new `AssociationEvent` with required properties.
356    #[must_use]
357    pub fn new(event_time: DateTime<Utc>, tz_offset: String, action: Action) -> Self {
358        Self {
359            event_time,
360            event_time_zone_offset: tz_offset,
361            record_time: None,
362            event_id: None,
363            error_declaration: None,
364            action,
365            parent_id: None,
366            child_e_p_cs: None,
367            child_quantity_list: None,
368            biz_step: None,
369            disposition: None,
370            read_point: None,
371            biz_location: None,
372            biz_transaction_list: None,
373            source_list: None,
374            destination_list: None,
375            sensor_element_list: None,
376            extensions: serde_json::Map::new(),
377        }
378    }
379}
380
381/// A `TransactionEvent` captures items associated with a business transaction.
382#[derive(Debug, Clone, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct TransactionEvent {
385    /// Date and time when the event occurred
386    pub event_time: DateTime<Utc>,
387    /// Timezone offset
388    pub event_time_zone_offset: String,
389    /// Record timestamp
390    #[serde(skip_serializing_if = "Option::is_none")]
391    pub record_time: Option<DateTime<Utc>>,
392    /// Event identifier
393    #[serde(rename = "eventID", skip_serializing_if = "Option::is_none")]
394    pub event_id: Option<String>,
395    /// Error declaration details
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub error_declaration: Option<ErrorDeclaration>,
398    
399    /// Action specifying parent-child state change
400    pub action: Action,
401    /// Parent identifier
402    #[serde(rename = "parentID", skip_serializing_if = "Option::is_none")]
403    pub parent_id: Option<Epc>,
404    /// List of item EPCs
405    #[serde(skip_serializing_if = "Option::is_none")]
406    pub epc_list: Option<Vec<Epc>>,
407    /// List of quantities
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub quantity_list: Option<Vec<QuantityElement>>,
410    
411    /// Business step
412    #[serde(skip_serializing_if = "Option::is_none")]
413    pub biz_step: Option<BizStep>,
414    /// Disposition
415    #[serde(skip_serializing_if = "Option::is_none")]
416    pub disposition: Option<Disposition>,
417    /// Read point
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub read_point: Option<ReadPoint>,
420    /// Business location
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub biz_location: Option<BizLocation>,
423    /// Business transactions
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub biz_transaction_list: Option<Vec<BizTransaction>>,
426    /// Source list
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub source_list: Option<Vec<Source>>,
429    /// Destination list
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub destination_list: Option<Vec<Destination>>,
432    /// Sensor element list
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub sensor_element_list: Option<Vec<SensorElement>>,
435
436    /// Extra custom fields
437    #[serde(flatten)]
438    pub extensions: serde_json::Map<String, serde_json::Value>,
439}
440
441impl TransactionEvent {
442    /// Creates a new `TransactionEvent` with required properties.
443    #[must_use]
444    pub fn new(event_time: DateTime<Utc>, tz_offset: String, action: Action) -> Self {
445        Self {
446            event_time,
447            event_time_zone_offset: tz_offset,
448            record_time: None,
449            event_id: None,
450            error_declaration: None,
451            action,
452            parent_id: None,
453            epc_list: None,
454            quantity_list: None,
455            biz_step: None,
456            disposition: None,
457            read_point: None,
458            biz_location: None,
459            biz_transaction_list: None,
460            source_list: None,
461            destination_list: None,
462            sensor_element_list: None,
463            extensions: serde_json::Map::new(),
464        }
465    }
466}
467
468/// Sum-type representing any EPCIS event.
469#[derive(Debug, Clone, Serialize, Deserialize)]
470#[serde(tag = "type")]
471pub enum EPCISEvent {
472    /// An `ObjectEvent` variant.
473    ObjectEvent(ObjectEvent),
474    /// An `AggregationEvent` variant.
475    AggregationEvent(AggregationEvent),
476    /// A `TransformationEvent` variant.
477    TransformationEvent(TransformationEvent),
478    /// An `AssociationEvent` variant.
479    AssociationEvent(AssociationEvent),
480    /// A `TransactionEvent` variant.
481    TransactionEvent(TransactionEvent),
482}