1use crate::documents::{
15 self, AcknowledgementDocument, ActivationDocument, DocumentType, Kaskade, Kostenblatt,
16 NetworkConstraintDocument, PlannedResourceScheduleDocument, Stammdaten,
17 StatusRequestMarketDocument, UnavailabilityMarketDocument,
18};
19use crate::error::RedispatchXmlError;
20
21#[derive(Debug, Clone, PartialEq)]
25pub enum Document {
26 Activation(Box<ActivationDocument>),
28 PlannedResourceSchedule(Box<PlannedResourceScheduleDocument>),
30 Acknowledgement(Box<AcknowledgementDocument>),
32 Stammdaten(Box<Stammdaten>),
34 StatusRequest(Box<StatusRequestMarketDocument>),
36 Unavailability(Box<UnavailabilityMarketDocument>),
38 Kaskade(Box<Kaskade>),
40 NetworkConstraint(Box<NetworkConstraintDocument>),
42 Kostenblatt(Box<Kostenblatt>),
44}
45
46impl Document {
47 pub fn document_type(&self) -> DocumentType {
49 match self {
50 Self::Activation(_) => DocumentType::Activation,
51 Self::PlannedResourceSchedule(_) => DocumentType::PlannedResourceSchedule,
52 Self::Acknowledgement(_) => DocumentType::Acknowledgement,
53 Self::Stammdaten(_) => DocumentType::Stammdaten,
54 Self::StatusRequest(_) => DocumentType::StatusRequest,
55 Self::Unavailability(_) => DocumentType::Unavailability,
56 Self::Kaskade(_) => DocumentType::Kaskade,
57 Self::NetworkConstraint(_) => DocumentType::NetworkConstraint,
58 Self::Kostenblatt(_) => DocumentType::Kostenblatt,
59 }
60 }
61
62 pub fn mrid(&self) -> &str {
67 match self {
68 Self::Activation(d) => d.document_identification.v.as_str(),
69 Self::PlannedResourceSchedule(d) => d.document_identification.v.as_str(),
70 Self::Acknowledgement(d) => d.document_identification.v.as_str(),
71 Self::Stammdaten(d) => d.document_identification.as_str(),
72 Self::StatusRequest(d) => d.m_rid.as_str(),
73 Self::Unavailability(d) => d.m_rid.as_str(),
74 Self::Kaskade(d) => d.m_rid.as_str(),
75 Self::NetworkConstraint(d) => d.document_identification.v.as_str(),
76 Self::Kostenblatt(d) => d.document_identification.v.as_str(),
77 }
78 }
79
80 pub fn sender_id(&self) -> &str {
82 match self {
83 Self::Activation(d) => d.sender_identification.v.as_str(),
84 Self::PlannedResourceSchedule(d) => d.sender_identification.v.as_str(),
85 Self::Acknowledgement(d) => d.sender_identification.v.as_str(),
86 Self::Stammdaten(d) => d.sender.code.as_str(),
87 Self::StatusRequest(d) => d.sender_market_participant.m_rid.value.as_str(),
88 Self::Unavailability(d) => d.sender_market_participant.m_rid.value.as_str(),
89 Self::Kaskade(d) => d.sender_market_participant.m_rid.value.as_str(),
90 Self::NetworkConstraint(d) => d.sender_identification.v.as_str(),
91 Self::Kostenblatt(d) => d.sender_identification.v.as_str(),
92 }
93 }
94
95 pub fn receiver_id(&self) -> &str {
97 match self {
98 Self::Activation(d) => d.receiver_identification.v.as_str(),
99 Self::PlannedResourceSchedule(d) => d.receiver_identification.v.as_str(),
100 Self::Acknowledgement(d) => d.receiver_identification.v.as_str(),
101 Self::Stammdaten(d) => d.empfaenger.code.as_str(),
102 Self::StatusRequest(d) => d.receiver_market_participant.m_rid.value.as_str(),
103 Self::Unavailability(d) => d.receiver_market_participant.m_rid.value.as_str(),
104 Self::Kaskade(d) => d.receiver_market_participant.m_rid.value.as_str(),
105 Self::NetworkConstraint(d) => d.receiver_identification.v.as_str(),
106 Self::Kostenblatt(d) => d.receiver_identification.v.as_str(),
107 }
108 }
109}
110
111impl From<ActivationDocument> for Document {
114 fn from(d: ActivationDocument) -> Self {
115 Self::Activation(Box::new(d))
116 }
117}
118impl From<PlannedResourceScheduleDocument> for Document {
119 fn from(d: PlannedResourceScheduleDocument) -> Self {
120 Self::PlannedResourceSchedule(Box::new(d))
121 }
122}
123impl From<AcknowledgementDocument> for Document {
124 fn from(d: AcknowledgementDocument) -> Self {
125 Self::Acknowledgement(Box::new(d))
126 }
127}
128impl From<Stammdaten> for Document {
129 fn from(d: Stammdaten) -> Self {
130 Self::Stammdaten(Box::new(d))
131 }
132}
133impl From<StatusRequestMarketDocument> for Document {
134 fn from(d: StatusRequestMarketDocument) -> Self {
135 Self::StatusRequest(Box::new(d))
136 }
137}
138impl From<UnavailabilityMarketDocument> for Document {
139 fn from(d: UnavailabilityMarketDocument) -> Self {
140 Self::Unavailability(Box::new(d))
141 }
142}
143impl From<Kaskade> for Document {
144 fn from(d: Kaskade) -> Self {
145 Self::Kaskade(Box::new(d))
146 }
147}
148impl From<NetworkConstraintDocument> for Document {
149 fn from(d: NetworkConstraintDocument) -> Self {
150 Self::NetworkConstraint(Box::new(d))
151 }
152}
153impl From<documents::Kostenblatt> for Document {
154 fn from(d: documents::Kostenblatt) -> Self {
155 Self::Kostenblatt(Box::new(d))
156 }
157}
158
159fn detect_root(xml: &[u8]) -> (String, Option<String>) {
167 let xml = xml.strip_prefix(b"\xEF\xBB\xBF").unwrap_or(xml);
169
170 let window = &xml[..xml.len().min(4096)];
172 let text = String::from_utf8_lossy(window);
173
174 let mut root_name = String::new();
176 let mut namespace = None;
177
178 for i in 0..text.len() {
179 let ch = text.as_bytes()[i];
180 if ch != b'<' {
181 continue;
182 }
183 let rest = &text[i + 1..];
184 if rest.starts_with('?') || rest.starts_with('!') {
185 continue;
186 }
187 let name_end = rest
189 .find(|c: char| c.is_whitespace() || c == '>' || c == '/')
190 .unwrap_or(rest.len());
191 let raw_name = &rest[..name_end];
192 root_name = if let Some(pos) = raw_name.rfind(':') {
194 raw_name[pos + 1..].to_string()
195 } else {
196 raw_name.to_string()
197 };
198
199 let tag_end = rest.find('>').unwrap_or(rest.len());
201 let tag_slice = &rest[..tag_end];
202 namespace = extract_default_namespace(tag_slice);
203 break;
204 }
205
206 (root_name, namespace)
207}
208
209fn extract_default_namespace(tag: &str) -> Option<String> {
212 if let Some(pos) = tag.find("xmlns=\"") {
214 let after = &tag[pos + 7..];
215 if let Some(end) = after.find('"') {
216 return Some(after[..end].to_string());
217 }
218 }
219 if let Some(pos) = tag.find("xmlns:") {
221 let after = &tag[pos..];
222 if let Some(eq) = after.find("=\"") {
223 let ns_part = &after[eq + 2..];
224 if let Some(end) = ns_part.find('"') {
225 return Some(ns_part[..end].to_string());
226 }
227 }
228 }
229 None
230}
231
232pub fn detect(xml: &[u8]) -> Result<DocumentType, RedispatchXmlError> {
242 let (root_name, _) = detect_root(xml);
243 DocumentType::from_root_element(&root_name)
244 .ok_or(RedispatchXmlError::UnknownDocumentType(root_name))
245}
246
247pub fn parse(xml: &[u8]) -> Result<Document, RedispatchXmlError> {
258 let (root_name, detected_ns) = detect_root(xml);
259 let doc_type = DocumentType::from_root_element(&root_name)
260 .ok_or(RedispatchXmlError::UnknownDocumentType(root_name))?;
261
262 if let Some(expected_ns) = doc_type.expected_namespace() {
264 match detected_ns.as_deref() {
265 Some(found) if found == expected_ns => {}
266 Some(found) => {
267 return Err(RedispatchXmlError::NamespaceMismatch {
268 expected: expected_ns,
269 found: found.to_string(),
270 });
271 }
272 None => {
273 return Err(RedispatchXmlError::NamespaceMismatch {
274 expected: expected_ns,
275 found: String::new(),
276 });
277 }
278 }
279 }
280
281 let text =
282 std::str::from_utf8(xml).map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))?;
283
284 match doc_type {
285 DocumentType::Activation => {
286 let doc: ActivationDocument =
287 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
288 Ok(Document::Activation(Box::new(doc)))
289 }
290 DocumentType::PlannedResourceSchedule => {
291 let doc: PlannedResourceScheduleDocument =
292 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
293 Ok(Document::PlannedResourceSchedule(Box::new(doc)))
294 }
295 DocumentType::Acknowledgement => {
296 let doc: AcknowledgementDocument =
297 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
298 Ok(Document::Acknowledgement(Box::new(doc)))
299 }
300 DocumentType::Stammdaten => {
301 let doc: Stammdaten =
302 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
303 Ok(Document::Stammdaten(Box::new(doc)))
304 }
305 DocumentType::StatusRequest => {
306 let doc: StatusRequestMarketDocument =
307 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
308 Ok(Document::StatusRequest(Box::new(doc)))
309 }
310 DocumentType::Unavailability => {
311 let doc: UnavailabilityMarketDocument =
312 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
313 Ok(Document::Unavailability(Box::new(doc)))
314 }
315 DocumentType::Kaskade => {
316 let doc: Kaskade =
317 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
318 Ok(Document::Kaskade(Box::new(doc)))
319 }
320 DocumentType::NetworkConstraint => {
321 let doc: NetworkConstraintDocument =
322 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
323 Ok(Document::NetworkConstraint(Box::new(doc)))
324 }
325 DocumentType::Kostenblatt => {
326 let doc: documents::Kostenblatt =
327 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
328 Ok(Document::Kostenblatt(Box::new(doc)))
329 }
330 }
331}
332
333pub fn parse_as<T>(xml: &[u8]) -> Result<T, RedispatchXmlError>
341where
342 T: serde::de::DeserializeOwned,
343{
344 let text =
345 std::str::from_utf8(xml).map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))?;
346 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)
347}
348
349pub fn parse_and_validate(xml: &[u8]) -> Result<Document, RedispatchXmlError> {
361 let doc = parse(xml)?;
362 let result = crate::validation::validate(&doc);
363 result
364 .into_result()
365 .map(|_| doc)
366 .map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))
367}