1use std::fmt;
3
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use time::OffsetDateTime;
6use time::format_description::well_known::Rfc3339;
7
8use crate::RedispatchXmlError;
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub struct DocumentId(String);
17
18impl DocumentId {
19 #[must_use = "this returns the new DocumentId, discarding it is likely a mistake"]
22 pub fn new(s: impl Into<String>) -> Result<Self, RedispatchXmlError> {
23 let s = s.into();
24 if s.is_empty() || s.len() > 35 {
25 return Err(RedispatchXmlError::InvalidDocumentId(s));
26 }
27 Ok(Self(s))
28 }
29
30 pub fn as_str(&self) -> &str {
32 &self.0
33 }
34}
35
36impl AsRef<str> for DocumentId {
37 fn as_ref(&self) -> &str {
38 &self.0
39 }
40}
41
42impl TryFrom<String> for DocumentId {
43 type Error = RedispatchXmlError;
44 fn try_from(s: String) -> Result<Self, Self::Error> {
45 Self::new(s)
46 }
47}
48
49impl TryFrom<&str> for DocumentId {
50 type Error = RedispatchXmlError;
51 fn try_from(s: &str) -> Result<Self, Self::Error> {
52 Self::new(s)
53 }
54}
55
56impl fmt::Display for DocumentId {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 self.0.fmt(f)
59 }
60}
61
62impl<'de> Deserialize<'de> for DocumentId {
63 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
64 let s = String::deserialize(d)?;
65 Self::new(s).map_err(serde::de::Error::custom)
66 }
67}
68
69impl Serialize for DocumentId {
70 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
71 self.0.serialize(s)
72 }
73}
74
75pub type Mrid = DocumentId;
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
86pub struct DocumentVersion(u16);
87
88impl DocumentVersion {
89 #[must_use = "this returns the new DocumentVersion, discarding it is likely a mistake"]
91 pub fn new(v: u32) -> Result<Self, RedispatchXmlError> {
92 if v == 0 || v > 999 {
93 return Err(RedispatchXmlError::InvalidDocumentVersion(v));
94 }
95 Ok(Self(v as u16))
96 }
97
98 pub fn get(self) -> u16 {
100 self.0
101 }
102}
103
104impl fmt::Display for DocumentVersion {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 self.0.fmt(f)
107 }
108}
109
110impl<'de> Deserialize<'de> for DocumentVersion {
111 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
112 let v: u32 = Deserialize::deserialize(d)?;
114 Self::new(v).map_err(serde::de::Error::custom)
115 }
116}
117
118impl Serialize for DocumentVersion {
119 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
120 self.0.serialize(s)
121 }
122}
123
124pub type RevisionNumber = DocumentVersion;
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
134pub struct UtcDateTime(OffsetDateTime);
135
136impl UtcDateTime {
137 #[must_use = "this returns the new UtcDateTime, discarding it is likely a mistake"]
140 pub fn new(dt: OffsetDateTime) -> Result<Self, RedispatchXmlError> {
141 if dt.offset() != time::UtcOffset::UTC {
142 return Err(RedispatchXmlError::InvalidTimestamp(dt.to_string()));
143 }
144 Ok(Self(dt))
145 }
146
147 pub fn inner(self) -> OffsetDateTime {
149 self.0
150 }
151}
152
153impl<'de> Deserialize<'de> for UtcDateTime {
154 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
155 let s = String::deserialize(d)?;
156 let dt = OffsetDateTime::parse(&s, &Rfc3339)
157 .map_err(|_| serde::de::Error::custom(format!("invalid UTC timestamp: {s:?}")))?;
158 if dt.offset() != time::UtcOffset::UTC {
159 return Err(serde::de::Error::custom(format!(
160 "timestamp must use UTC (Z suffix): {s:?}"
161 )));
162 }
163 Ok(UtcDateTime(dt))
164 }
165}
166
167impl Serialize for UtcDateTime {
168 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
169 self.0
171 .format(&Rfc3339)
172 .map_err(serde::ser::Error::custom)?
173 .serialize(s)
174 }
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
183pub struct UtcMinuteDateTime(OffsetDateTime);
184
185impl UtcMinuteDateTime {
186 #[must_use = "this returns the new UtcMinuteDateTime, discarding it is likely a mistake"]
188 pub fn new(dt: OffsetDateTime) -> Result<Self, RedispatchXmlError> {
189 if dt.offset() != time::UtcOffset::UTC {
190 return Err(RedispatchXmlError::InvalidTimestamp(dt.to_string()));
191 }
192 Ok(Self(dt))
193 }
194
195 pub fn inner(self) -> OffsetDateTime {
197 self.0
198 }
199}
200
201const MINUTE_FMT: &[time::format_description::BorrowedFormatItem<'static>] =
202 time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]Z");
203
204impl<'de> Deserialize<'de> for UtcMinuteDateTime {
205 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
206 let s = String::deserialize(d)?;
207 let naive = time::PrimitiveDateTime::parse(&s, MINUTE_FMT).map_err(|_| {
208 serde::de::Error::custom(format!("invalid UTC minute timestamp: {s:?}"))
209 })?;
210 Ok(UtcMinuteDateTime(naive.assume_offset(time::UtcOffset::UTC)))
211 }
212}
213
214impl Serialize for UtcMinuteDateTime {
215 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
216 self.0
217 .format(MINUTE_FMT)
218 .map_err(serde::ser::Error::custom)?
219 .serialize(s)
220 }
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
229pub struct TimeInterval {
230 pub start: OffsetDateTime,
232 pub end: OffsetDateTime,
234}
235
236impl TimeInterval {
237 #[must_use = "this returns the new TimeInterval, discarding it is likely a mistake"]
240 pub fn new(start: OffsetDateTime, end: OffsetDateTime) -> Result<Self, RedispatchXmlError> {
241 if start.offset() != time::UtcOffset::UTC || end.offset() != time::UtcOffset::UTC {
242 return Err(RedispatchXmlError::InvalidTimeInterval(
243 "timestamps must be UTC".into(),
244 ));
245 }
246 if start >= end {
247 return Err(RedispatchXmlError::InvalidTimeInterval(
248 "start must be before end".into(),
249 ));
250 }
251 Ok(Self { start, end })
252 }
253}
254
255impl<'de> Deserialize<'de> for TimeInterval {
256 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
257 let s = String::deserialize(d)?;
258 let (start_str, end_str) = s.split_once('/').ok_or_else(|| {
259 serde::de::Error::custom(format!("invalid time interval {s:?}: missing '/'"))
260 })?;
261 let start_naive = time::PrimitiveDateTime::parse(start_str, MINUTE_FMT).map_err(|_| {
262 serde::de::Error::custom(format!("invalid interval start {start_str:?}"))
263 })?;
264 let end_naive = time::PrimitiveDateTime::parse(end_str, MINUTE_FMT)
265 .map_err(|_| serde::de::Error::custom(format!("invalid interval end {end_str:?}")))?;
266 Ok(TimeInterval {
267 start: start_naive.assume_offset(time::UtcOffset::UTC),
268 end: end_naive.assume_offset(time::UtcOffset::UTC),
269 })
270 }
271}
272
273impl Serialize for TimeInterval {
274 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
275 let start = self
276 .start
277 .format(MINUTE_FMT)
278 .map_err(serde::ser::Error::custom)?;
279 let end = self
280 .end
281 .format(MINUTE_FMT)
282 .map_err(serde::ser::Error::custom)?;
283 format!("{start}/{end}").serialize(s)
284 }
285}
286
287impl fmt::Display for TimeInterval {
288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289 let start = self.start.format(MINUTE_FMT).map_err(|_| fmt::Error)?;
290 let end = self.end.format(MINUTE_FMT).map_err(|_| fmt::Error)?;
291 write!(f, "{start}/{end}")
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Hash)]
299pub struct MarketParticipantId(String);
300
301impl MarketParticipantId {
302 #[must_use = "this returns the new MarketParticipantId, discarding it is likely a mistake"]
304 pub fn new(s: impl Into<String>) -> Result<Self, RedispatchXmlError> {
305 let s = s.into();
306 if s.len() != 13 || !s.chars().all(|c| c.is_ascii_digit()) {
307 return Err(RedispatchXmlError::InvalidMarketParticipantId(s));
308 }
309 Ok(Self(s))
310 }
311
312 pub fn as_str(&self) -> &str {
314 &self.0
315 }
316}
317
318impl AsRef<str> for MarketParticipantId {
319 fn as_ref(&self) -> &str {
320 &self.0
321 }
322}
323
324impl TryFrom<String> for MarketParticipantId {
325 type Error = RedispatchXmlError;
326 fn try_from(s: String) -> Result<Self, Self::Error> {
327 Self::new(s)
328 }
329}
330
331impl TryFrom<&str> for MarketParticipantId {
332 type Error = RedispatchXmlError;
333 fn try_from(s: &str) -> Result<Self, Self::Error> {
334 Self::new(s)
335 }
336}
337
338impl fmt::Display for MarketParticipantId {
339 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 self.0.fmt(f)
341 }
342}
343
344impl<'de> Deserialize<'de> for MarketParticipantId {
345 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
346 let s = String::deserialize(d)?;
347 Self::new(s).map_err(serde::de::Error::custom)
348 }
349}
350
351impl Serialize for MarketParticipantId {
352 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
353 self.0.serialize(s)
354 }
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
362pub struct Decimal3(f64);
363
364impl Decimal3 {
365 #[must_use = "this returns the new Decimal3, discarding it is likely a mistake"]
372 pub fn new(v: f64) -> Result<Self, RedispatchXmlError> {
373 if v < 0.0 {
374 return Err(RedispatchXmlError::StructuralError(format!(
375 "Decimal3 value {v} must be ≥ 0"
376 )));
377 }
378 Ok(Self(v))
379 }
380
381 pub fn value(self) -> f64 {
383 self.0
384 }
385}
386
387impl<'de> Deserialize<'de> for Decimal3 {
388 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
389 let v: f64 = Deserialize::deserialize(d)?;
391 Self::new(v).map_err(serde::de::Error::custom)
392 }
393}
394
395impl Serialize for Decimal3 {
396 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
397 format!("{:.3}", self.0).serialize(s)
400 }
401}
402
403impl fmt::Display for Decimal3 {
404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 write!(f, "{:.3}", self.0)
406 }
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
413pub enum CodingScheme {
414 #[serde(rename = "A10")]
417 Gs1,
418 #[serde(rename = "NDE")]
420 Nde,
421 #[serde(rename = "A01")]
423 Eic,
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
430#[non_exhaustive]
431pub enum MeasureUnit {
432 #[serde(rename = "MAW")]
434 Megawatt,
435 #[serde(rename = "P1")]
437 Percent,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
444#[non_exhaustive]
445pub enum Direction {
446 #[serde(rename = "A01")]
448 Up,
449 #[serde(rename = "A02")]
451 Down,
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
458#[non_exhaustive]
459pub enum MarketRoleType {
460 #[serde(rename = "A08")]
462 BalanceResponsibleParty,
463 #[serde(rename = "A18")]
465 GridOperator,
466 #[serde(rename = "A21")]
468 Producer,
469 #[serde(rename = "A27")]
471 ResourceProvider,
472 #[serde(rename = "A39")]
474 DataProvider,
475 #[serde(rename = "Z01")]
477 Supplier,
478}
479
480#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
485#[non_exhaustive]
486pub enum ControlZone {
487 #[serde(rename = "10YDE-ENBW-----N")]
489 TransnetBw,
490 #[serde(rename = "10YDE-EON------1")]
492 TennetDe,
493 #[serde(rename = "10YDE-RWENET---I")]
495 Amprion,
496 #[serde(rename = "10YDE-VE-------2")]
498 FiftyHertz,
499 #[serde(rename = "10YFLENSBURG---3")]
501 Flensburg,
502 #[serde(rename = "11YRBAHNSTROM--P")]
504 Bahnstrom,
505}