Skip to main content

zerodds_soap/
addressing.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3
4//! WS-Addressing 1.0 — W3C 2006 Recommendation.
5//!
6//! Spec: `http://www.w3.org/TR/ws-addr-core/`.
7//! Namespace: `http://www.w3.org/2005/08/addressing` (`wsa:`).
8
9use alloc::format;
10use alloc::string::String;
11
12/// WS-Addressing-Namespace 1.0.
13pub const WSA_NS: &str = "http://www.w3.org/2005/08/addressing";
14
15/// WS-Addressing-Header. Spec §3.2.
16#[derive(Debug, Clone, PartialEq, Eq, Default)]
17pub struct AddressingHeaders {
18    /// `wsa:To` — Endpoint-Reference des Empfaengers.
19    pub to: Option<String>,
20    /// `wsa:Action` — Operation-IRI.
21    pub action: Option<String>,
22    /// `wsa:MessageID` — eindeutige Message-Id.
23    pub message_id: Option<String>,
24    /// `wsa:RelatesTo` — bezieht sich auf eine vorherige Message-Id
25    /// (typisch fuer Reply).
26    pub relates_to: Option<String>,
27    /// `wsa:From` — Source-Endpoint.
28    pub from: Option<String>,
29    /// `wsa:ReplyTo` — Endpoint, an den die Reply geschickt werden
30    /// soll.
31    pub reply_to: Option<String>,
32    /// `wsa:FaultTo` — Endpoint fuer Faults.
33    pub fault_to: Option<String>,
34}
35
36/// Erzeugt einen `<wsa:...>`-Header-Block fuer den SOAP-Envelope.
37/// Spec §3.2.
38#[must_use]
39pub fn build_addressing_header(h: &AddressingHeaders) -> String {
40    let mut out = String::new();
41    if let Some(to) = &h.to {
42        out.push_str(&format!("<wsa:To xmlns:wsa=\"{WSA_NS}\">{to}</wsa:To>"));
43    }
44    if let Some(action) = &h.action {
45        out.push_str(&format!(
46            "<wsa:Action xmlns:wsa=\"{WSA_NS}\">{action}</wsa:Action>"
47        ));
48    }
49    if let Some(mid) = &h.message_id {
50        out.push_str(&format!(
51            "<wsa:MessageID xmlns:wsa=\"{WSA_NS}\">{mid}</wsa:MessageID>"
52        ));
53    }
54    if let Some(r) = &h.relates_to {
55        out.push_str(&format!(
56            "<wsa:RelatesTo xmlns:wsa=\"{WSA_NS}\">{r}</wsa:RelatesTo>"
57        ));
58    }
59    if let Some(f) = &h.from {
60        out.push_str(&format!(
61            "<wsa:From xmlns:wsa=\"{WSA_NS}\"><wsa:Address>{f}</wsa:Address></wsa:From>"
62        ));
63    }
64    if let Some(r) = &h.reply_to {
65        out.push_str(&format!(
66            "<wsa:ReplyTo xmlns:wsa=\"{WSA_NS}\"><wsa:Address>{r}</wsa:Address></wsa:ReplyTo>"
67        ));
68    }
69    if let Some(f) = &h.fault_to {
70        out.push_str(&format!(
71            "<wsa:FaultTo xmlns:wsa=\"{WSA_NS}\"><wsa:Address>{f}</wsa:Address></wsa:FaultTo>"
72        ));
73    }
74    out
75}
76
77#[cfg(test)]
78#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn empty_headers_produce_empty_string() {
84        let h = AddressingHeaders::default();
85        assert!(build_addressing_header(&h).is_empty());
86    }
87
88    #[test]
89    fn to_and_action_are_emitted() {
90        let h = AddressingHeaders {
91            to: Some("http://server/ep".into()),
92            action: Some("http://demo/Action/Echo".into()),
93            ..AddressingHeaders::default()
94        };
95        let out = build_addressing_header(&h);
96        assert!(out.contains("<wsa:To"));
97        assert!(out.contains("http://server/ep"));
98        assert!(out.contains("<wsa:Action"));
99        assert!(out.contains("http://demo/Action/Echo"));
100    }
101
102    #[test]
103    fn message_id_and_relates_to_round_trip() {
104        let h = AddressingHeaders {
105            message_id: Some("uuid:1".into()),
106            relates_to: Some("uuid:0".into()),
107            ..AddressingHeaders::default()
108        };
109        let out = build_addressing_header(&h);
110        assert!(out.contains("uuid:1"));
111        assert!(out.contains("uuid:0"));
112    }
113
114    #[test]
115    fn reply_to_and_fault_to_emit_address_subelement() {
116        let h = AddressingHeaders {
117            reply_to: Some("http://client/reply".into()),
118            fault_to: Some("http://client/fault".into()),
119            ..AddressingHeaders::default()
120        };
121        let out = build_addressing_header(&h);
122        assert!(out.contains("<wsa:ReplyTo"));
123        assert!(out.contains("<wsa:Address>http://client/reply</wsa:Address>"));
124        assert!(out.contains("<wsa:FaultTo"));
125        assert!(out.contains("<wsa:Address>http://client/fault</wsa:Address>"));
126    }
127
128    #[test]
129    fn ns_constant_is_w3c_2005_08() {
130        assert_eq!(WSA_NS, "http://www.w3.org/2005/08/addressing");
131    }
132}