Skip to main content

epcis_models/
types.rs

1//! Core shared types for the EPCIS 2.0 SDK.
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 std::borrow::Cow;
11use crate::error::EpcisModelError;
12
13/// Newtype representing an Electronic Product Code (EPC) URN.
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct Epc(pub Cow<'static, str>);
16
17impl From<String> for Epc {
18    fn from(s: String) -> Self {
19        Epc(Cow::Owned(s))
20    }
21}
22
23impl From<Cow<'static, str>> for Epc {
24    fn from(s: Cow<'static, str>) -> Self {
25        Epc(s)
26    }
27}
28
29impl TryFrom<&str> for Epc {
30    type Error = EpcisModelError;
31
32    fn try_from(s: &str) -> Result<Self, Self::Error> {
33        if s.is_empty() {
34            return Err(EpcisModelError::InvalidEpc("EPC URN cannot be empty".to_string()));
35        }
36        if !s.starts_with("urn:epc:") && !s.starts_with("http://") && !s.starts_with("https://") {
37            return Err(EpcisModelError::InvalidEpc(format!("EPC URN must start with URN or HTTP scheme: {s}")));
38        }
39        Ok(Epc(Cow::Owned(s.to_string())))
40    }
41}
42
43impl std::fmt::Display for Epc {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        write!(f, "{}", self.0)
46    }
47}
48
49/// The Action component of an EPCIS event, specifying the status of the objects
50/// identified in the event.
51#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
52#[serde(rename_all = "UPPERCASE")]
53pub enum Action {
54    /// Add action
55    Add,
56    /// Observe action
57    Observe,
58    /// Delete action
59    Delete,
60}
61
62/// Type-safe identifier wrapper for ReadPoint.
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
64pub struct ReadPointId(pub Cow<'static, str>);
65
66impl From<&'static str> for ReadPointId {
67    fn from(s: &'static str) -> Self {
68        ReadPointId(Cow::Borrowed(s))
69    }
70}
71
72impl From<String> for ReadPointId {
73    fn from(s: String) -> Self {
74        ReadPointId(Cow::Owned(s))
75    }
76}
77
78impl std::fmt::Display for ReadPointId {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "{}", self.0)
81    }
82}
83
84/// The physical location where the event took place.
85#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
86pub struct ReadPoint {
87    /// Unique identifier of the read point
88    pub id: ReadPointId,
89}
90
91impl From<&'static str> for ReadPoint {
92    fn from(id: &'static str) -> Self {
93        ReadPoint { id: ReadPointId::from(id) }
94    }
95}
96
97impl From<String> for ReadPoint {
98    fn from(id: String) -> Self {
99        ReadPoint { id: ReadPointId::from(id) }
100    }
101}
102
103/// Type-safe identifier wrapper for BizLocation.
104#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
105pub struct BizLocationId(pub Cow<'static, str>);
106
107impl From<&'static str> for BizLocationId {
108    fn from(s: &'static str) -> Self {
109        BizLocationId(Cow::Borrowed(s))
110    }
111}
112
113impl From<String> for BizLocationId {
114    fn from(s: String) -> Self {
115        BizLocationId(Cow::Owned(s))
116    }
117}
118
119impl std::fmt::Display for BizLocationId {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        write!(f, "{}", self.0)
122    }
123}
124
125/// The business location where the objects are expected to be after the event.
126#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
127pub struct BizLocation {
128    /// Unique identifier of the business location
129    pub id: BizLocationId,
130}
131
132impl From<&'static str> for BizLocation {
133    fn from(id: &'static str) -> Self {
134        BizLocation { id: BizLocationId::from(id) }
135    }
136}
137
138impl From<String> for BizLocation {
139    fn from(id: String) -> Self {
140        BizLocation { id: BizLocationId::from(id) }
141    }
142}
143
144/// A business transaction associated with the event.
145#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct BizTransaction {
148    /// Type of the business transaction (e.g. PO)
149    #[serde(rename = "type")]
150    pub r#type: String,
151    /// Transaction ID value (e.g. URI)
152    pub biz_transaction: String,
153}
154
155/// Source of the objects in the event (e.g. shipping location, possessing party).
156#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct Source {
159    /// Type of the source (e.g. possessing party)
160    #[serde(rename = "type")]
161    pub r#type: String,
162    /// Source value (e.g. SGLN URI)
163    pub source: String,
164}
165
166/// Destination of the objects in the event (e.g. receiving location, owning party).
167#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct Destination {
170    /// Type of the destination (e.g. owning party)
171    #[serde(rename = "type")]
172    pub r#type: String,
173    /// Destination value (e.g. SGLN URI)
174    pub destination: String,
175}
176
177/// An element representing a quantity of EPCs of a single class.
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct QuantityElement {
181    /// The EPC class (e.g. GTIN class)
182    pub epc_class: String,
183    /// Quantity of items in the class
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub quantity: Option<f64>,
186    /// Unit of measure (e.g. KGM)
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub uom: Option<String>,
189}
190
191/// Metadata about a sensor device or sensor data source.
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct SensorMetadata {
195    /// Time when the sensor metadata was generated
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub time: Option<DateTime<Utc>>,
198    /// Device identifier
199    #[serde(rename = "deviceID", skip_serializing_if = "Option::is_none")]
200    pub device_id: Option<String>,
201    /// URI pointing to device metadata details
202    #[serde(rename = "deviceMetadataURI", skip_serializing_if = "Option::is_none")]
203    pub device_metadata_uri: Option<String>,
204    /// URI pointing to raw sensor data
205    #[serde(rename = "rawDataURI", skip_serializing_if = "Option::is_none")]
206    pub raw_data_uri: Option<String>,
207    /// URI pointing to parsed data content
208    #[serde(rename = "dataContentURI", skip_serializing_if = "Option::is_none")]
209    pub data_content_uri: Option<String>,
210    /// URI pointing to business logic rules applied to the sensor
211    #[serde(rename = "bizRulesURI", skip_serializing_if = "Option::is_none")]
212    pub biz_rules_uri: Option<String>,
213}
214
215/// A specific sensor report (e.g., temperature reading).
216#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
217#[serde(rename_all = "camelCase")]
218pub struct SensorReport {
219    /// Type of measurement (e.g., temperature, relative humidity)
220    #[serde(rename = "type")]
221    pub r#type: String,
222    /// Numerical value of the sensor reading
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub value: Option<f64>,
225    /// Unit of measure (e.g. CEL)
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub uom: Option<String>,
228    /// Processor component responsible for the sensor reading
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub sensor_processor: Option<String>,
231    /// Time when the reading occurred
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub time: Option<DateTime<Utc>>,
234    /// Microsecond offset from event time
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub microsecond_offset: Option<i32>,
237    /// Chemical substance measured (if applicable)
238    #[serde(skip_serializing_if = "Option::is_none")]
239    pub chemical_substance: Option<String>,
240    /// Data value in string/URI format
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub data_value: Option<String>,
243    /// String representation value
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub string_value: Option<String>,
246    /// Boolean representation value
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub boolean_value: Option<bool>,
249    /// Hex-binary value
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub hex_binary_value: Option<String>,
252    /// URI value
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub uri_value: Option<String>,
255}
256
257/// A sensor element grouping sensor metadata and reports.
258#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259#[serde(rename_all = "camelCase")]
260pub struct SensorElement {
261    /// Sensor device metadata
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub sensor_metadata: Option<SensorMetadata>,
264    /// List of sensor reports
265    #[serde(skip_serializing_if = "Option::is_none")]
266    pub sensor_report: Option<Vec<SensorReport>>,
267}
268
269/// Declaration of error correction context.
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct ErrorDeclaration {
273    /// The timestamp when the error was declared
274    pub declaration_time: DateTime<Utc>,
275    /// The reason for error declaration (e.g. incorrect data)
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub reason: Option<String>,
278    /// List of event IDs that correct this event
279    #[serde(rename = "correctiveEventIDs", skip_serializing_if = "Option::is_none")]
280    pub corrective_event_ids: Option<Vec<String>>,
281}