1use zerodds_web::bridge::BackendError;
26use zerodds_xml::{EntityQos, XmlElement};
27
28use crate::qos_update::EntityKind;
29
30use crate::metadata::MetadataStore;
31
32#[derive(Debug, Clone)]
35pub struct ParsedDataWriter {
36 pub name: String,
38 pub topic_name: String,
41 pub qos: Option<EntityQos>,
43}
44
45#[derive(Debug, Clone)]
47pub struct ParsedDataReader {
48 pub name: String,
50 pub topic_name: String,
53 pub qos: Option<EntityQos>,
55}
56
57#[derive(Debug, Clone)]
59pub struct ParsedPublisher {
60 pub name: String,
62 pub qos: Option<EntityQos>,
64 pub data_writers: Vec<ParsedDataWriter>,
66}
67
68#[derive(Debug, Clone)]
70pub struct ParsedSubscriber {
71 pub name: String,
73 pub qos: Option<EntityQos>,
75 pub data_readers: Vec<ParsedDataReader>,
77}
78
79fn required_name(el: &XmlElement, what: &str) -> Result<String, BackendError> {
80 el.child("name")
81 .map(|e| e.text.clone())
82 .filter(|s| !s.is_empty())
83 .ok_or_else(|| BackendError::BadRequest(format!("a contained {what} needs a <name>")))
84}
85
86pub fn parse_publishers(
95 root: &XmlElement,
96 metadata: &MetadataStore,
97) -> Result<Vec<ParsedPublisher>, BackendError> {
98 let Some(container) = root.child("publishers") else {
99 return Ok(Vec::new());
100 };
101 container
102 .sequence_elements()
103 .map(|el| parse_publisher(el, metadata))
104 .collect()
105}
106
107fn parse_publisher(
108 el: &XmlElement,
109 metadata: &MetadataStore,
110) -> Result<ParsedPublisher, BackendError> {
111 let name = required_name(el, "publisher")?;
112 let qos =
113 crate::qos_update::resolve_entity_qos_from_element(el, metadata, EntityKind::Publisher)?;
114 let data_writers = parse_data_writers(el, metadata)?;
115 Ok(ParsedPublisher {
116 name,
117 qos,
118 data_writers,
119 })
120}
121
122pub fn parse_data_writers(
127 root: &XmlElement,
128 metadata: &MetadataStore,
129) -> Result<Vec<ParsedDataWriter>, BackendError> {
130 let Some(container) = root.child("data_writers") else {
131 return Ok(Vec::new());
132 };
133 container
134 .sequence_elements()
135 .map(|el| {
136 let name = required_name(el, "data_writer")?;
137 let topic_name = el
138 .child("topic_name")
139 .map(|e| e.text.clone())
140 .unwrap_or_default();
141 let qos = crate::qos_update::resolve_entity_qos_from_element_for_topic(
144 el,
145 metadata,
146 EntityKind::DataWriter,
147 &topic_name,
148 )?;
149 Ok(ParsedDataWriter {
150 name,
151 topic_name,
152 qos,
153 })
154 })
155 .collect()
156}
157
158pub fn parse_subscribers(
163 root: &XmlElement,
164 metadata: &MetadataStore,
165) -> Result<Vec<ParsedSubscriber>, BackendError> {
166 let Some(container) = root.child("subscribers") else {
167 return Ok(Vec::new());
168 };
169 container
170 .sequence_elements()
171 .map(|el| parse_subscriber(el, metadata))
172 .collect()
173}
174
175fn parse_subscriber(
176 el: &XmlElement,
177 metadata: &MetadataStore,
178) -> Result<ParsedSubscriber, BackendError> {
179 let name = required_name(el, "subscriber")?;
180 let qos =
181 crate::qos_update::resolve_entity_qos_from_element(el, metadata, EntityKind::Subscriber)?;
182 let data_readers = parse_data_readers(el, metadata)?;
183 Ok(ParsedSubscriber {
184 name,
185 qos,
186 data_readers,
187 })
188}
189
190pub fn parse_data_readers(
195 root: &XmlElement,
196 metadata: &MetadataStore,
197) -> Result<Vec<ParsedDataReader>, BackendError> {
198 let Some(container) = root.child("data_readers") else {
199 return Ok(Vec::new());
200 };
201 container
202 .sequence_elements()
203 .map(|el| {
204 let name = required_name(el, "data_reader")?;
205 let topic_name = el
206 .child("topic_name")
207 .map(|e| e.text.clone())
208 .unwrap_or_default();
209 let qos = crate::qos_update::resolve_entity_qos_from_element_for_topic(
212 el,
213 metadata,
214 EntityKind::DataReader,
215 &topic_name,
216 )?;
217 Ok(ParsedDataReader {
218 name,
219 topic_name,
220 qos,
221 })
222 })
223 .collect()
224}
225
226pub fn parse_body(body: &[u8]) -> Result<Option<zerodds_xml::DdsXmlDocument>, BackendError> {
235 if body.is_empty() {
236 return Ok(None);
237 }
238 let text = core::str::from_utf8(body)
239 .map_err(|e| BackendError::BadRequest(format!("body is not UTF-8: {e}")))?;
240 if text.trim().is_empty() {
241 return Ok(None);
242 }
243 zerodds_xml::parse_xml_tree(text)
244 .map(Some)
245 .map_err(|e| BackendError::BadRequest(format!("malformed object body: {e}")))
246}
247
248#[cfg(test)]
249#[allow(clippy::unwrap_used, clippy::expect_used)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn a_body_with_no_publishers_element_has_none() {
255 let doc = parse_body(b"<domain_participant><domain_id>0</domain_id></domain_participant>")
256 .unwrap()
257 .unwrap();
258 assert!(
259 parse_publishers(&doc.root, &MetadataStore::new())
260 .unwrap()
261 .is_empty()
262 );
263 }
264
265 #[test]
266 fn nested_publishers_and_data_writers_parse() {
267 let body = b"<domain_participant><publishers><element><name>pub1</name><data_writers><element><name>dw1</name><topic_name>Sensor</topic_name></element></data_writers></element></publishers></domain_participant>";
268 let doc = parse_body(body).unwrap().unwrap();
269 let pubs = parse_publishers(&doc.root, &MetadataStore::new()).unwrap();
270 assert_eq!(pubs.len(), 1);
271 assert_eq!(pubs[0].name, "pub1");
272 assert_eq!(pubs[0].data_writers.len(), 1);
273 assert_eq!(pubs[0].data_writers[0].name, "dw1");
274 assert_eq!(pubs[0].data_writers[0].topic_name, "Sensor");
275 }
276
277 #[test]
278 fn a_contained_entity_without_a_name_is_a_bad_request() {
279 let body =
280 b"<domain_participant><publishers><element><data_writers/></element></publishers></domain_participant>";
281 let doc = parse_body(body).unwrap().unwrap();
282 let err = parse_publishers(&doc.root, &MetadataStore::new()).unwrap_err();
283 assert!(matches!(err, BackendError::BadRequest(_)));
284 }
285
286 #[test]
287 fn a_publishers_qos_profile_reference_resolves() {
288 let metadata = MetadataStore::new();
289 metadata
290 .qos_libraries
291 .create(
292 "Lib",
293 crate::metadata::StoredDocument {
294 content_type: "application/dds-web+xml".to_string(),
295 body: b"<qos_library><name>Lib</name></qos_library>".to_vec(),
296 },
297 )
298 .unwrap();
299 metadata
300 .qos_profiles
301 .create(
302 &MetadataStore::profile_key("Lib", "P"),
303 crate::metadata::StoredDocument {
304 content_type: "application/dds-web+xml".to_string(),
305 body: b"<qos_profile><name>P</name><publisher_qos><partition><name><element>g1</element></name></partition></publisher_qos></qos_profile>".to_vec(),
306 },
307 )
308 .unwrap();
309 let body = b"<domain_participant><publishers><element><name>pub1</name><qos_profile>Lib::P</qos_profile></element></publishers></domain_participant>";
310 let doc = parse_body(body).unwrap().unwrap();
311 let pubs = parse_publishers(&doc.root, &metadata).unwrap();
312 assert_eq!(
313 pubs[0]
314 .qos
315 .as_ref()
316 .unwrap()
317 .partition
318 .as_ref()
319 .unwrap()
320 .names,
321 vec!["g1".to_string()]
322 );
323 }
324}