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>),
27 PlannedResourceSchedule(Box<PlannedResourceScheduleDocument>),
28 Acknowledgement(Box<AcknowledgementDocument>),
29 Stammdaten(Box<Stammdaten>),
30 StatusRequest(Box<StatusRequestMarketDocument>),
31 Unavailability(Box<UnavailabilityMarketDocument>),
32 Kaskade(Box<Kaskade>),
33 NetworkConstraint(Box<NetworkConstraintDocument>),
34 Kostenblatt(Box<Kostenblatt>),
35}
36
37impl Document {
38 pub fn document_type(&self) -> DocumentType {
40 match self {
41 Self::Activation(_) => DocumentType::Activation,
42 Self::PlannedResourceSchedule(_) => DocumentType::PlannedResourceSchedule,
43 Self::Acknowledgement(_) => DocumentType::Acknowledgement,
44 Self::Stammdaten(_) => DocumentType::Stammdaten,
45 Self::StatusRequest(_) => DocumentType::StatusRequest,
46 Self::Unavailability(_) => DocumentType::Unavailability,
47 Self::Kaskade(_) => DocumentType::Kaskade,
48 Self::NetworkConstraint(_) => DocumentType::NetworkConstraint,
49 Self::Kostenblatt(_) => DocumentType::Kostenblatt,
50 }
51 }
52}
53
54impl From<ActivationDocument> for Document {
57 fn from(d: ActivationDocument) -> Self {
58 Self::Activation(Box::new(d))
59 }
60}
61impl From<PlannedResourceScheduleDocument> for Document {
62 fn from(d: PlannedResourceScheduleDocument) -> Self {
63 Self::PlannedResourceSchedule(Box::new(d))
64 }
65}
66impl From<AcknowledgementDocument> for Document {
67 fn from(d: AcknowledgementDocument) -> Self {
68 Self::Acknowledgement(Box::new(d))
69 }
70}
71impl From<Stammdaten> for Document {
72 fn from(d: Stammdaten) -> Self {
73 Self::Stammdaten(Box::new(d))
74 }
75}
76impl From<StatusRequestMarketDocument> for Document {
77 fn from(d: StatusRequestMarketDocument) -> Self {
78 Self::StatusRequest(Box::new(d))
79 }
80}
81impl From<UnavailabilityMarketDocument> for Document {
82 fn from(d: UnavailabilityMarketDocument) -> Self {
83 Self::Unavailability(Box::new(d))
84 }
85}
86impl From<Kaskade> for Document {
87 fn from(d: Kaskade) -> Self {
88 Self::Kaskade(Box::new(d))
89 }
90}
91impl From<NetworkConstraintDocument> for Document {
92 fn from(d: NetworkConstraintDocument) -> Self {
93 Self::NetworkConstraint(Box::new(d))
94 }
95}
96impl From<documents::Kostenblatt> for Document {
97 fn from(d: documents::Kostenblatt) -> Self {
98 Self::Kostenblatt(Box::new(d))
99 }
100}
101
102fn detect_root(xml: &[u8]) -> (String, Option<String>) {
110 let window = &xml[..xml.len().min(4096)];
112 let text = String::from_utf8_lossy(window);
113
114 let mut root_name = String::new();
116 let mut namespace = None;
117
118 for i in 0..text.len() {
119 let ch = text.as_bytes()[i];
120 if ch != b'<' {
121 continue;
122 }
123 let rest = &text[i + 1..];
124 if rest.starts_with('?') || rest.starts_with('!') {
125 continue;
126 }
127 let name_end = rest
129 .find(|c: char| c.is_whitespace() || c == '>' || c == '/')
130 .unwrap_or(rest.len());
131 let raw_name = &rest[..name_end];
132 root_name = if let Some(pos) = raw_name.rfind(':') {
134 raw_name[pos + 1..].to_string()
135 } else {
136 raw_name.to_string()
137 };
138
139 let tag_end = rest.find('>').unwrap_or(rest.len());
141 let tag_slice = &rest[..tag_end];
142 namespace = extract_default_namespace(tag_slice);
143 break;
144 }
145
146 (root_name, namespace)
147}
148
149fn extract_default_namespace(tag: &str) -> Option<String> {
152 if let Some(pos) = tag.find("xmlns=\"") {
154 let after = &tag[pos + 7..];
155 if let Some(end) = after.find('"') {
156 return Some(after[..end].to_string());
157 }
158 }
159 if let Some(pos) = tag.find("xmlns:") {
161 let after = &tag[pos..];
162 if let Some(eq) = after.find("=\"") {
163 let ns_part = &after[eq + 2..];
164 if let Some(end) = ns_part.find('"') {
165 return Some(ns_part[..end].to_string());
166 }
167 }
168 }
169 None
170}
171
172pub fn detect(xml: &[u8]) -> Result<DocumentType, RedispatchXmlError> {
182 let (root_name, _) = detect_root(xml);
183 DocumentType::from_root_element(&root_name)
184 .ok_or(RedispatchXmlError::UnknownDocumentType(root_name))
185}
186
187pub fn parse(xml: &[u8]) -> Result<Document, RedispatchXmlError> {
198 let (root_name, detected_ns) = detect_root(xml);
199 let doc_type = DocumentType::from_root_element(&root_name)
200 .ok_or(RedispatchXmlError::UnknownDocumentType(root_name))?;
201
202 if let Some(expected_ns) = doc_type.expected_namespace() {
204 match detected_ns.as_deref() {
205 Some(found) if found == expected_ns => {}
206 Some(found) => {
207 return Err(RedispatchXmlError::NamespaceMismatch {
208 expected: expected_ns,
209 found: found.to_string(),
210 });
211 }
212 None => {
213 return Err(RedispatchXmlError::NamespaceMismatch {
214 expected: expected_ns,
215 found: String::new(),
216 });
217 }
218 }
219 }
220
221 let text =
222 std::str::from_utf8(xml).map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))?;
223
224 match doc_type {
225 DocumentType::Activation => {
226 let doc: ActivationDocument =
227 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
228 Ok(Document::Activation(Box::new(doc)))
229 }
230 DocumentType::PlannedResourceSchedule => {
231 let doc: PlannedResourceScheduleDocument =
232 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
233 Ok(Document::PlannedResourceSchedule(Box::new(doc)))
234 }
235 DocumentType::Acknowledgement => {
236 let doc: AcknowledgementDocument =
237 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
238 Ok(Document::Acknowledgement(Box::new(doc)))
239 }
240 DocumentType::Stammdaten => {
241 let doc: Stammdaten =
242 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
243 Ok(Document::Stammdaten(Box::new(doc)))
244 }
245 DocumentType::StatusRequest => {
246 let doc: StatusRequestMarketDocument =
247 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
248 Ok(Document::StatusRequest(Box::new(doc)))
249 }
250 DocumentType::Unavailability => {
251 let doc: UnavailabilityMarketDocument =
252 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
253 Ok(Document::Unavailability(Box::new(doc)))
254 }
255 DocumentType::Kaskade => {
256 let doc: Kaskade =
257 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
258 Ok(Document::Kaskade(Box::new(doc)))
259 }
260 DocumentType::NetworkConstraint => {
261 let doc: NetworkConstraintDocument =
262 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
263 Ok(Document::NetworkConstraint(Box::new(doc)))
264 }
265 DocumentType::Kostenblatt => {
266 let doc: documents::Kostenblatt =
267 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)?;
268 Ok(Document::Kostenblatt(Box::new(doc)))
269 }
270 }
271}
272
273pub fn parse_as<T>(xml: &[u8]) -> Result<T, RedispatchXmlError>
281where
282 T: serde::de::DeserializeOwned,
283{
284 let text =
285 std::str::from_utf8(xml).map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))?;
286 quick_xml::de::from_str(text).map_err(RedispatchXmlError::Deserialize)
287}
288
289pub fn parse_and_validate(xml: &[u8]) -> Result<Document, RedispatchXmlError> {
301 let doc = parse(xml)?;
302 let result = crate::validation::validate(&doc);
303 result
304 .into_result()
305 .map(|_| doc)
306 .map_err(|e| RedispatchXmlError::StructuralError(e.to_string()))
307}