1use crate::fault::SoapFault;
5use crate::handler::SoapHandler;
6use crate::qname::QName;
7use crate::wsdl::definitions::BindingStyle;
8use crate::wsdl::resolver::ResolvedWsdl;
9use crate::xsd::types::{ComplexContent, TypeRegistry};
10use std::collections::{HashMap, HashSet};
11use std::sync::Arc;
12
13pub struct DispatchEntry {
15 pub handler: Arc<dyn SoapHandler>,
16 pub auth_required: bool,
18 pub input_type: Option<QName>,
21 pub validation_type: Option<QName>,
28}
29
30impl std::fmt::Debug for DispatchEntry {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 f.debug_struct("DispatchEntry")
33 .field("auth_required", &self.auth_required)
34 .field("input_type", &self.input_type)
35 .field("validation_type", &self.validation_type)
36 .finish_non_exhaustive()
37 }
38}
39
40pub struct DispatchTable {
43 by_element: HashMap<QName, DispatchEntry>,
44 by_action: HashMap<String, DispatchEntry>,
45 pub default_handler: Option<Arc<dyn SoapHandler>>,
47}
48
49impl DispatchTable {
50 pub fn empty() -> Self {
53 DispatchTable {
54 by_element: HashMap::new(),
55 by_action: HashMap::new(),
56 default_handler: None,
57 }
58 }
59}
60
61#[derive(Debug, thiserror::Error)]
63pub enum DispatchError {
64 #[error("WSDL operation '{0}' has no registered handler")]
65 UnregisteredOperation(String),
66 #[error("Registered handler '{0}' has no matching operation in WSDL")]
67 UnknownOperation(String),
68 #[error("Operation '{op}' input element '{element}' references type '{type_ref}' which is not in the type registry")]
72 UnresolvableInputType {
73 op: String,
74 element: String,
75 type_ref: String,
76 },
77}
78
79pub fn build_dispatch_table(
90 resolved: &ResolvedWsdl,
91 handlers: HashMap<String, Arc<dyn SoapHandler>>,
92 auth_bypass: &HashSet<String>,
93 default_handler: Option<Arc<dyn SoapHandler>>,
94) -> Result<DispatchTable, DispatchError> {
95 let unique_ops = collect_ops_for_service(None, resolved);
96 build_dispatch_table_from_ops(unique_ops, resolved, handlers, auth_bypass, default_handler)
97}
98
99pub fn build_dispatch_table_for_service(
106 service_name: &str,
107 resolved: &ResolvedWsdl,
108 handlers: HashMap<String, Arc<dyn SoapHandler>>,
109 auth_bypass: &HashSet<String>,
110 default_handler: Option<Arc<dyn SoapHandler>>,
111) -> Result<DispatchTable, DispatchError> {
112 let unique_ops = collect_ops_for_service(Some(service_name), resolved);
113 build_dispatch_table_from_ops(unique_ops, resolved, handlers, auth_bypass, default_handler)
114}
115
116fn collect_ops_for_service(
119 service_name: Option<&str>,
120 resolved: &ResolvedWsdl,
121) -> Vec<(String, String, BindingStyle, Option<String>)> {
122 let mut all_binding_ops: Vec<(String, String, BindingStyle, Option<String>)> = Vec::new();
123
124 let service_iter: Vec<&crate::wsdl::definitions::Service> = match service_name {
125 Some(name) => resolved.definition.services.get(name).into_iter().collect(),
126 None => resolved.definition.services.values().collect(),
127 };
128
129 for service in service_iter {
130 for port in &service.ports {
131 let binding_local = &port.binding.local_name;
132 if let Some(binding) = resolved.definition.bindings.get(binding_local) {
133 let style = binding.soap_binding.style.clone();
134 for binding_op in &binding.operations {
135 let rpc_ns = if style == BindingStyle::Rpc {
136 binding_op
137 .input
138 .body
139 .namespace
140 .clone()
141 .or_else(|| Some(resolved.definition.target_namespace.clone()))
142 } else {
143 None
144 };
145 all_binding_ops.push((
146 binding_op.name.clone(),
147 binding_op.soap_action.clone(),
148 style.clone(),
149 rpc_ns,
150 ));
151 }
152 }
153 }
154 }
155
156 if all_binding_ops.is_empty() && service_name.is_none() {
158 for binding in resolved.definition.bindings.values() {
159 let style = binding.soap_binding.style.clone();
160 for binding_op in &binding.operations {
161 let rpc_ns = if style == BindingStyle::Rpc {
162 binding_op
163 .input
164 .body
165 .namespace
166 .clone()
167 .or_else(|| Some(resolved.definition.target_namespace.clone()))
168 } else {
169 None
170 };
171 all_binding_ops.push((
172 binding_op.name.clone(),
173 binding_op.soap_action.clone(),
174 style.clone(),
175 rpc_ns,
176 ));
177 }
178 }
179 }
180
181 let mut seen_ops: HashSet<String> = HashSet::new();
183 let mut unique_ops: Vec<(String, String, BindingStyle, Option<String>)> = Vec::new();
184 for (op_name, soap_action, style, rpc_ns) in all_binding_ops {
185 if seen_ops.insert(op_name.clone()) {
186 unique_ops.push((op_name, soap_action, style, rpc_ns));
187 }
188 }
189
190 unique_ops
191}
192
193fn build_dispatch_table_from_ops(
195 unique_ops: Vec<(String, String, BindingStyle, Option<String>)>,
196 resolved: &ResolvedWsdl,
197 handlers: HashMap<String, Arc<dyn SoapHandler>>,
198 auth_bypass: &HashSet<String>,
199 default_handler: Option<Arc<dyn SoapHandler>>,
200) -> Result<DispatchTable, DispatchError> {
201 let mut by_element: HashMap<QName, DispatchEntry> = HashMap::new();
202 let mut by_action: HashMap<String, DispatchEntry> = HashMap::new();
203
204 let mut consumed_handlers: HashSet<String> = HashSet::new();
206
207 for (op_name, soap_action, style, rpc_ns) in unique_ops {
208 let handler = match handlers.get(&op_name) {
209 Some(h) => h.clone(),
210 None => match &default_handler {
211 Some(d) => d.clone(),
212 None => return Err(DispatchError::UnregisteredOperation(op_name.clone())),
213 },
214 };
215
216 consumed_handlers.insert(op_name.clone());
217
218 let auth_required = !auth_bypass.contains(&op_name);
219
220 let (input_type, validation_type) = if style == BindingStyle::Rpc {
233 let ns = rpc_ns
234 .as_deref()
235 .unwrap_or(&resolved.definition.target_namespace);
236 let qn = Some(QName::new(ns, &op_name));
237 (qn.clone(), qn)
238 } else {
239 let elem_qname = resolve_input_element(resolved, &op_name);
240 let val_type = match &elem_qname {
241 None => None, Some(elem_qn) => {
243 match resolved.element_type_map.get(elem_qn) {
244 None => {
245 None
249 }
250 Some(type_qn) => {
251 if resolved.type_registry.lookup(type_qn).is_none() {
254 return Err(DispatchError::UnresolvableInputType {
255 op: op_name.clone(),
256 element: elem_qn.to_string(),
257 type_ref: type_qn.to_string(),
258 });
259 }
260 Some(type_qn.clone())
261 }
262 }
263 }
264 };
265 (elem_qname, val_type)
266 };
267
268 let entry_for_element = DispatchEntry {
269 handler: handler.clone(),
270 auth_required,
271 input_type: input_type.clone(),
272 validation_type: validation_type.clone(),
273 };
274 let entry_for_action = DispatchEntry {
275 handler,
276 auth_required,
277 input_type,
278 validation_type,
279 };
280
281 if let Some(ref qn) = entry_for_element.input_type {
283 by_element.insert(qn.clone(), entry_for_element);
284 } else {
285 drop(entry_for_element);
287 }
288
289 if !soap_action.is_empty() {
291 by_action.insert(soap_action, entry_for_action);
292 }
293 }
294
295 for handler_name in handlers.keys() {
297 if !consumed_handlers.contains(handler_name) {
298 return Err(DispatchError::UnknownOperation(handler_name.clone()));
299 }
300 }
301
302 Ok(DispatchTable {
303 by_element,
304 by_action,
305 default_handler,
306 })
307}
308
309fn resolve_input_element(resolved: &ResolvedWsdl, op_name: &str) -> Option<QName> {
313 for port_type in resolved.definition.port_types.values() {
315 for op in &port_type.operations {
316 if op.name != op_name {
317 continue;
318 }
319 let input_msg_ref = op.input.as_ref()?;
320 let msg_local = &input_msg_ref.message.local_name;
322 let message = resolved.definition.messages.get(msg_local)?;
323 let first_part = message.parts.first()?;
325 return first_part.element.clone();
326 }
327 }
328 None
329}
330
331pub fn route<'a>(
337 table: &'a DispatchTable,
338 body_first_child_qname: &QName,
339 soap_action: Option<&str>,
340) -> Result<&'a DispatchEntry, SoapFault> {
341 if let Some(entry) = table.by_element.get(body_first_child_qname) {
343 return Ok(entry);
344 }
345
346 if let Some(action) = soap_action {
348 if let Some(entry) = table.by_action.get(action) {
349 return Ok(entry);
350 }
351 }
352
353 Err(SoapFault::action_not_supported(
354 &body_first_child_qname.to_string(),
355 ))
356 }
359
360pub fn validate_request(
372 body_bytes: &[u8],
373 type_registry: &TypeRegistry,
374 input_type: Option<&QName>,
375) -> Result<(), SoapFault> {
376 let Some(qname) = input_type else {
377 return Ok(());
378 };
379
380 let Some(xsd_type) = type_registry.lookup(qname) else {
381 return Ok(()); };
383
384 let required_names = collect_required_element_names(xsd_type);
386 if required_names.is_empty() {
387 return Ok(());
388 }
389
390 let present_children = parse_child_element_names(body_bytes)
392 .map_err(|e| SoapFault::sender(format!("Schema validation failed: malformed XML: {e}")))?;
393
394 for required in &required_names {
395 if !present_children.contains(required) {
396 return Err(SoapFault::sender(format!(
397 "Schema validation failed: required element '{required}' is missing"
398 )));
399 }
400 }
401
402 Ok(())
403}
404
405fn collect_required_element_names(xsd_type: &crate::xsd::types::XsdType) -> Vec<String> {
407 use crate::xsd::types::XsdType;
408 match xsd_type {
409 XsdType::Complex(ct) => collect_required_from_content(&ct.content),
410 XsdType::Simple(_) => vec![],
411 }
412}
413
414fn collect_required_from_content(content: &ComplexContent) -> Vec<String> {
415 let elements = match content {
416 ComplexContent::Sequence(els) | ComplexContent::All(els) => els,
417 ComplexContent::Choice(_) => return vec![],
422 ComplexContent::ComplexExtension { content, .. } => {
423 return collect_required_from_content(content)
424 }
425 ComplexContent::ComplexRestriction { content, .. } => {
426 return collect_required_from_content(content)
427 }
428 ComplexContent::Empty | ComplexContent::SimpleContent(_) => return vec![],
429 };
430
431 elements
432 .iter()
433 .filter_map(|el| {
434 if el.min_occurs > 0 {
435 el.name.clone()
436 } else {
437 None
438 }
439 })
440 .collect()
441}
442
443fn parse_child_element_names(body_bytes: &[u8]) -> Result<HashSet<String>, String> {
446 use quick_xml::events::Event;
447 use quick_xml::Reader;
448
449 let mut reader = Reader::from_reader(body_bytes);
450 reader.config_mut().trim_text(true);
451
452 let mut depth: u32 = 0;
453 let mut children: HashSet<String> = HashSet::new();
454
455 loop {
456 match reader.read_event() {
457 Ok(Event::Start(e)) => {
458 depth += 1;
459 if depth == 2 {
460 let local_name = e.local_name();
462 let name = std::str::from_utf8(local_name.as_ref())
463 .map_err(|e| e.to_string())?
464 .to_string();
465 children.insert(name);
466 }
467 }
468 Ok(Event::Empty(e)) => {
469 if depth == 1 {
470 let local_name = e.local_name();
472 let name = std::str::from_utf8(local_name.as_ref())
473 .map_err(|e| e.to_string())?
474 .to_string();
475 children.insert(name);
476 }
477 }
478 Ok(Event::End(_)) => {
479 if depth == 0 {
480 break;
481 }
482 depth -= 1;
483 }
484 Ok(Event::Eof) => break,
485 Err(e) => return Err(e.to_string()),
486 _ => {}
487 }
488 }
489
490 Ok(children)
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use crate::fault::{FaultCode, SoapFault};
497 use crate::handler::SoapHandler;
498 use crate::qname::QName;
499 use crate::wsdl::definitions::*;
500 use crate::wsdl::resolver::ResolvedWsdl;
501 use crate::xsd::elements::XsdElement;
502 use crate::xsd::types::{ComplexContent, ComplexType, TypeRegistry, XsdType};
503 use async_trait::async_trait;
504 use bytes::Bytes;
505 use std::sync::Arc;
506
507 struct MockHandler {
510 name: &'static str,
511 }
512
513 #[async_trait]
514 impl SoapHandler for MockHandler {
515 async fn handle(&self, _body: Bytes) -> Result<Bytes, SoapFault> {
516 Ok(Bytes::from(format!("<response>{}</response>", self.name)))
517 }
518 }
519
520 fn mock_handler(name: &'static str) -> Arc<dyn SoapHandler> {
521 Arc::new(MockHandler { name })
522 }
523
524 fn make_resolved_wsdl(
528 op_name: &str,
529 soap_action: &str,
530 input_element_qname: Option<QName>,
531 ) -> ResolvedWsdl {
532 let target_ns = "http://example.com/service".to_string();
533 let msg_name = format!("{op_name}Request");
534
535 let part = MessagePart {
537 name: "parameters".to_string(),
538 element: input_element_qname,
539 type_ref: None,
540 };
541 let message = Message {
542 name: msg_name.clone(),
543 parts: vec![part],
544 };
545
546 let pt_op = Operation {
548 name: op_name.to_string(),
549 input: Some(OperationMessage {
550 name: None,
551 message: QName::local(&msg_name),
552 }),
553 output: None,
554 faults: vec![],
555 style: OperationStyle::RequestResponse,
556 };
557 let port_type = PortType {
558 name: "TestPortType".to_string(),
559 operations: vec![pt_op],
560 };
561
562 let binding_op = BindingOperation {
564 name: op_name.to_string(),
565 soap_action: soap_action.to_string(),
566 input: BindingMessage {
567 body: SoapBody {
568 use_attr: UseStyle::Literal,
569 namespace: None,
570 encoding_style: None,
571 },
572 headers: vec![],
573 },
574 output: BindingMessage {
575 body: SoapBody {
576 use_attr: UseStyle::Literal,
577 namespace: None,
578 encoding_style: None,
579 },
580 headers: vec![],
581 },
582 };
583 let binding = Binding {
584 name: "TestBinding".to_string(),
585 port_type: QName::local("TestPortType"),
586 soap_binding: SoapBinding {
587 style: BindingStyle::Document,
588 transport: "http://schemas.xmlsoap.org/soap/http".to_string(),
589 soap_version: SoapVersion::Soap12,
590 },
591 operations: vec![binding_op],
592 };
593
594 let port = Port {
596 name: "TestPort".to_string(),
597 binding: QName::local("TestBinding"),
598 address: "http://localhost/service".to_string(),
599 };
600 let service = Service {
601 name: "TestService".to_string(),
602 ports: vec![port],
603 };
604
605 let mut messages = HashMap::new();
606 messages.insert(msg_name, message);
607 let mut port_types = HashMap::new();
608 port_types.insert("TestPortType".to_string(), port_type);
609 let mut bindings = HashMap::new();
610 bindings.insert("TestBinding".to_string(), binding);
611 let mut services = HashMap::new();
612 services.insert("TestService".to_string(), service);
613
614 let definition = WsdlDefinition {
615 target_namespace: target_ns,
616 imports: vec![],
617 types: TypesSection::default(),
618 messages,
619 port_types,
620 bindings,
621 services,
622 };
623
624 ResolvedWsdl {
625 definition,
626 type_registry: TypeRegistry::new(),
627 element_type_map: HashMap::new(),
628 raw_bytes: vec![],
629 }
630 }
631
632 #[test]
635 fn dispatch_table_routes_by_element_qname() {
636 let elem_qname = QName::new("http://example.com", "GetProfiles");
637 let resolved =
638 make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname.clone()));
639
640 let mut handlers = HashMap::new();
641 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
642
643 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
644 let entry = route(&table, &elem_qname, None).unwrap();
645
646 assert!(entry.auth_required); }
649
650 #[test]
651 fn dispatch_table_unregistered_operation_fails_at_build() {
652 let elem_qname = QName::new("http://example.com", "GetProfiles");
653 let resolved = make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname));
654
655 let handlers: HashMap<String, Arc<dyn SoapHandler>> = HashMap::new();
657 let result = build_dispatch_table(&resolved, handlers, &HashSet::new(), None);
658
659 assert!(
660 matches!(result, Err(DispatchError::UnregisteredOperation(ref name)) if name == "GetProfiles")
661 );
662 }
663
664 #[test]
665 fn dispatch_table_unknown_handler_name_fails_at_build() {
666 let elem_qname = QName::new("http://example.com", "GetProfiles");
667 let resolved = make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname));
668
669 let mut handlers = HashMap::new();
670 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
671 handlers.insert("NonExistentOp".to_string(), mock_handler("ghost")); let result = build_dispatch_table(&resolved, handlers, &HashSet::new(), None);
674 assert!(
675 matches!(result, Err(DispatchError::UnknownOperation(ref name)) if name == "NonExistentOp")
676 );
677 }
678
679 #[test]
680 fn route_unknown_qname_no_soap_action_returns_sender_fault() {
681 let elem_qname = QName::new("http://example.com", "GetProfiles");
682 let resolved =
683 make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname.clone()));
684
685 let mut handlers = HashMap::new();
686 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
687 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
688
689 let unknown = QName::new("http://example.com", "UnknownOperation");
690 let result = route(&table, &unknown, None);
691
692 assert!(result.is_err());
693 let fault = result.unwrap_err();
694 assert_eq!(fault.code, FaultCode::Sender);
695 assert!(
696 fault.reason.to_lowercase().contains("action")
697 || fault.reason.to_lowercase().contains("supported")
698 );
699 }
700
701 #[test]
702 fn route_falls_back_to_soap_action_on_unknown_qname() {
703 let elem_qname = QName::new("http://example.com", "GetProfiles");
704 let resolved =
705 make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname.clone()));
706
707 let mut handlers = HashMap::new();
708 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
709 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
710
711 let unknown = QName::local("SomethingElse");
713 let result = route(&table, &unknown, Some("urn:GetProfiles"));
714 assert!(result.is_ok(), "Expected fallback to SOAPAction to succeed");
715 }
716
717 #[test]
718 fn route_unknown_qname_unknown_soap_action_returns_fault() {
719 let elem_qname = QName::new("http://example.com", "GetProfiles");
720 let resolved =
721 make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname.clone()));
722
723 let mut handlers = HashMap::new();
724 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
725 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
726
727 let unknown = QName::local("SomethingElse");
728 let result = route(&table, &unknown, Some("urn:UnknownAction"));
729 assert!(result.is_err());
730 assert_eq!(result.unwrap_err().code, FaultCode::Sender);
731 }
732
733 #[test]
734 fn auth_bypass_marks_entry_auth_not_required() {
735 let elem_qname = QName::new("http://example.com", "GetProfiles");
736 let resolved =
737 make_resolved_wsdl("GetProfiles", "urn:GetProfiles", Some(elem_qname.clone()));
738
739 let mut handlers = HashMap::new();
740 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
741
742 let mut bypass = HashSet::new();
743 bypass.insert("GetProfiles".to_string());
744
745 let table = build_dispatch_table(&resolved, handlers, &bypass, None).unwrap();
746 let entry = route(&table, &elem_qname, None).unwrap();
747 assert!(!entry.auth_required); }
749
750 #[test]
751 fn auth_required_by_default_for_non_bypass_op() {
752 let elem_qname = QName::new("http://example.com", "SetSomething");
753 let resolved =
754 make_resolved_wsdl("SetSomething", "urn:SetSomething", Some(elem_qname.clone()));
755
756 let mut handlers = HashMap::new();
757 handlers.insert("SetSomething".to_string(), mock_handler("SetSomething"));
758
759 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
760 let entry = route(&table, &elem_qname, None).unwrap();
761 assert!(entry.auth_required);
762 }
763
764 fn make_type_registry_with_required_field(
767 type_qname: &QName,
768 required_field: &str,
769 ) -> TypeRegistry {
770 let element = XsdElement {
771 name: Some(required_field.to_string()),
772 min_occurs: 1,
773 ..Default::default()
774 };
775 let ct = ComplexType {
776 name: Some(type_qname.local_name.clone()),
777 content: ComplexContent::Sequence(vec![element]),
778 attributes: vec![],
779 };
780 let mut reg = TypeRegistry::new();
781 reg.insert(type_qname.clone(), XsdType::Complex(Box::new(ct)));
782 reg
783 }
784
785 #[test]
786 fn validate_request_no_input_type_returns_ok() {
787 let reg = TypeRegistry::new();
788 let result = validate_request(b"<GetProfiles/>", ®, None);
789 assert!(result.is_ok());
790 }
791
792 #[test]
793 fn validate_request_unknown_type_in_registry_returns_ok() {
794 let reg = TypeRegistry::new(); let qname = QName::new("http://example.com", "GetProfilesRequest");
796 let result = validate_request(b"<GetProfiles/>", ®, Some(&qname));
797 assert!(result.is_ok());
798 }
799
800 #[test]
801 fn validate_request_valid_xml_with_required_element_returns_ok() {
802 let qname = QName::new("http://example.com", "GetProfilesRequest");
803 let reg = make_type_registry_with_required_field(&qname, "Token");
804
805 let body = b"<GetProfiles xmlns=\"http://example.com\"><Token>1234</Token></GetProfiles>";
806 let result = validate_request(body, ®, Some(&qname));
807 assert!(result.is_ok(), "Expected Ok, got: {result:?}");
808 }
809
810 #[test]
811 fn validate_request_missing_required_element_returns_sender_fault() {
812 let qname = QName::new("http://example.com", "GetProfilesRequest");
813 let reg = make_type_registry_with_required_field(&qname, "Token");
814
815 let body = b"<GetProfiles xmlns=\"http://example.com\"></GetProfiles>";
817 let result = validate_request(body, ®, Some(&qname));
818 assert!(result.is_err());
819 let fault = result.unwrap_err();
820 assert_eq!(fault.code, FaultCode::Sender);
821 assert!(fault.reason.contains("Schema validation failed"));
822 assert!(fault.reason.contains("Token"));
823 }
824
825 #[test]
826 fn validate_request_optional_element_missing_returns_ok() {
827 let qname = QName::new("http://example.com", "GetProfilesRequest");
828 let optional_el = XsdElement {
829 name: Some("OptionalField".to_string()),
830 min_occurs: 0,
831 ..Default::default()
832 };
833 let ct = ComplexType {
834 name: Some("GetProfilesRequest".to_string()),
835 content: ComplexContent::Sequence(vec![optional_el]),
836 attributes: vec![],
837 };
838 let mut reg = TypeRegistry::new();
839 reg.insert(qname.clone(), XsdType::Complex(Box::new(ct)));
840
841 let body = b"<GetProfiles xmlns=\"http://example.com\"></GetProfiles>";
842 let result = validate_request(body, ®, Some(&qname));
843 assert!(result.is_ok());
844 }
845
846 #[test]
847 fn handler_name_matches_wsdl_operation_name_get_profiles() {
848 let elem_qname = QName::new("http://onvif.org/ver10/media/wsdl", "GetProfiles");
850 let resolved = make_resolved_wsdl(
851 "GetProfiles",
852 "http://www.onvif.org/ver10/media/wsdl/GetProfiles",
853 Some(elem_qname.clone()),
854 );
855
856 let mut handlers = HashMap::new();
857 handlers.insert("GetProfiles".to_string(), mock_handler("GetProfiles"));
858
859 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
860 let entry = route(&table, &elem_qname, None).unwrap();
861 assert!(entry.auth_required);
862 }
863
864 fn make_resolved_wsdl_rpc(
868 op_name: &str,
869 soap_action: &str,
870 rpc_namespace: Option<&str>,
871 ) -> ResolvedWsdl {
872 let target_ns = "http://example.com/service".to_string();
873 let msg_name = format!("{op_name}Request");
874
875 let part = MessagePart {
877 name: "parameters".to_string(),
878 element: None,
879 type_ref: Some(QName::new("http://www.w3.org/2001/XMLSchema", "string")),
880 };
881 let message = Message {
882 name: msg_name.clone(),
883 parts: vec![part],
884 };
885
886 let pt_op = Operation {
888 name: op_name.to_string(),
889 input: Some(OperationMessage {
890 name: None,
891 message: QName::local(&msg_name),
892 }),
893 output: None,
894 faults: vec![],
895 style: OperationStyle::RequestResponse,
896 };
897 let port_type = PortType {
898 name: "TestPortType".to_string(),
899 operations: vec![pt_op],
900 };
901
902 let binding_op = BindingOperation {
904 name: op_name.to_string(),
905 soap_action: soap_action.to_string(),
906 input: BindingMessage {
907 body: SoapBody {
908 use_attr: UseStyle::Encoded,
909 namespace: rpc_namespace.map(|s| s.to_string()),
910 encoding_style: None,
911 },
912 headers: vec![],
913 },
914 output: BindingMessage {
915 body: SoapBody {
916 use_attr: UseStyle::Encoded,
917 namespace: rpc_namespace.map(|s| s.to_string()),
918 encoding_style: None,
919 },
920 headers: vec![],
921 },
922 };
923 let binding = Binding {
924 name: "TestBinding".to_string(),
925 port_type: QName::local("TestPortType"),
926 soap_binding: SoapBinding {
927 style: BindingStyle::Rpc,
928 transport: "http://schemas.xmlsoap.org/soap/http".to_string(),
929 soap_version: SoapVersion::Soap12,
930 },
931 operations: vec![binding_op],
932 };
933
934 let port = Port {
936 name: "TestPort".to_string(),
937 binding: QName::local("TestBinding"),
938 address: "http://localhost/service".to_string(),
939 };
940 let service = Service {
941 name: "TestService".to_string(),
942 ports: vec![port],
943 };
944
945 let mut messages = HashMap::new();
946 messages.insert(msg_name, message);
947 let mut port_types = HashMap::new();
948 port_types.insert("TestPortType".to_string(), port_type);
949 let mut bindings = HashMap::new();
950 bindings.insert("TestBinding".to_string(), binding);
951 let mut services = HashMap::new();
952 services.insert("TestService".to_string(), service);
953
954 let definition = WsdlDefinition {
955 target_namespace: target_ns,
956 imports: vec![],
957 types: TypesSection::default(),
958 messages,
959 port_types,
960 bindings,
961 services,
962 };
963
964 ResolvedWsdl {
965 definition,
966 type_registry: crate::xsd::types::TypeRegistry::new(),
967 element_type_map: HashMap::new(),
968 raw_bytes: vec![],
969 }
970 }
971
972 fn make_resolved_wsdl_two_services() -> ResolvedWsdl {
975 let target_ns = "http://example.com/multi".to_string();
976
977 let msg_a = Message {
979 name: "OpARequest".to_string(),
980 parts: vec![MessagePart {
981 name: "parameters".to_string(),
982 element: Some(QName::new("http://example.com/multi", "OpA")),
983 type_ref: None,
984 }],
985 };
986 let msg_b = Message {
987 name: "OpBRequest".to_string(),
988 parts: vec![MessagePart {
989 name: "parameters".to_string(),
990 element: Some(QName::new("http://example.com/multi", "OpB")),
991 type_ref: None,
992 }],
993 };
994
995 let pt_a = PortType {
997 name: "PortTypeA".to_string(),
998 operations: vec![Operation {
999 name: "OpA".to_string(),
1000 input: Some(OperationMessage {
1001 name: None,
1002 message: QName::local("OpARequest"),
1003 }),
1004 output: None,
1005 faults: vec![],
1006 style: OperationStyle::RequestResponse,
1007 }],
1008 };
1009 let pt_b = PortType {
1010 name: "PortTypeB".to_string(),
1011 operations: vec![Operation {
1012 name: "OpB".to_string(),
1013 input: Some(OperationMessage {
1014 name: None,
1015 message: QName::local("OpBRequest"),
1016 }),
1017 output: None,
1018 faults: vec![],
1019 style: OperationStyle::RequestResponse,
1020 }],
1021 };
1022
1023 let binding_a = Binding {
1025 name: "BindingA".to_string(),
1026 port_type: QName::local("PortTypeA"),
1027 soap_binding: SoapBinding {
1028 style: BindingStyle::Document,
1029 transport: "http://schemas.xmlsoap.org/soap/http".to_string(),
1030 soap_version: SoapVersion::Soap12,
1031 },
1032 operations: vec![BindingOperation {
1033 name: "OpA".to_string(),
1034 soap_action: "urn:OpA".to_string(),
1035 input: BindingMessage {
1036 body: SoapBody {
1037 use_attr: UseStyle::Literal,
1038 namespace: None,
1039 encoding_style: None,
1040 },
1041 headers: vec![],
1042 },
1043 output: BindingMessage {
1044 body: SoapBody {
1045 use_attr: UseStyle::Literal,
1046 namespace: None,
1047 encoding_style: None,
1048 },
1049 headers: vec![],
1050 },
1051 }],
1052 };
1053 let binding_b = Binding {
1054 name: "BindingB".to_string(),
1055 port_type: QName::local("PortTypeB"),
1056 soap_binding: SoapBinding {
1057 style: BindingStyle::Document,
1058 transport: "http://schemas.xmlsoap.org/soap/http".to_string(),
1059 soap_version: SoapVersion::Soap12,
1060 },
1061 operations: vec![BindingOperation {
1062 name: "OpB".to_string(),
1063 soap_action: "urn:OpB".to_string(),
1064 input: BindingMessage {
1065 body: SoapBody {
1066 use_attr: UseStyle::Literal,
1067 namespace: None,
1068 encoding_style: None,
1069 },
1070 headers: vec![],
1071 },
1072 output: BindingMessage {
1073 body: SoapBody {
1074 use_attr: UseStyle::Literal,
1075 namespace: None,
1076 encoding_style: None,
1077 },
1078 headers: vec![],
1079 },
1080 }],
1081 };
1082
1083 let service_a = Service {
1085 name: "ServiceA".to_string(),
1086 ports: vec![Port {
1087 name: "PortA".to_string(),
1088 binding: QName::local("BindingA"),
1089 address: "http://localhost/soap/a".to_string(),
1090 }],
1091 };
1092 let service_b = Service {
1093 name: "ServiceB".to_string(),
1094 ports: vec![Port {
1095 name: "PortB".to_string(),
1096 binding: QName::local("BindingB"),
1097 address: "http://localhost/soap/b".to_string(),
1098 }],
1099 };
1100
1101 let mut messages = HashMap::new();
1102 messages.insert("OpARequest".to_string(), msg_a);
1103 messages.insert("OpBRequest".to_string(), msg_b);
1104 let mut port_types = HashMap::new();
1105 port_types.insert("PortTypeA".to_string(), pt_a);
1106 port_types.insert("PortTypeB".to_string(), pt_b);
1107 let mut bindings = HashMap::new();
1108 bindings.insert("BindingA".to_string(), binding_a);
1109 bindings.insert("BindingB".to_string(), binding_b);
1110 let mut services = HashMap::new();
1111 services.insert("ServiceA".to_string(), service_a);
1112 services.insert("ServiceB".to_string(), service_b);
1113
1114 let definition = WsdlDefinition {
1115 target_namespace: target_ns,
1116 imports: vec![],
1117 types: TypesSection::default(),
1118 messages,
1119 port_types,
1120 bindings,
1121 services,
1122 };
1123
1124 ResolvedWsdl {
1125 definition,
1126 type_registry: crate::xsd::types::TypeRegistry::new(),
1127 element_type_map: HashMap::new(),
1128 raw_bytes: vec![],
1129 }
1130 }
1131
1132 #[test]
1133 fn build_dispatch_table_rpc_binding() {
1134 let resolved = make_resolved_wsdl_rpc("GetOp", "urn:GetOp", Some("http://example.com/svc"));
1135
1136 let mut handlers = HashMap::new();
1137 handlers.insert("GetOp".to_string(), mock_handler("GetOp"));
1138
1139 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
1140
1141 let rpc_qname = QName::new("http://example.com/svc", "GetOp");
1143 let result = route(&table, &rpc_qname, None);
1144 assert!(
1145 result.is_ok(),
1146 "Expected RPC QName to route successfully, got: {result:?}"
1147 );
1148 }
1149
1150 #[test]
1151 fn rpc_dispatch_by_wrapper_element() {
1152 let resolved = make_resolved_wsdl_rpc("GetOp", "urn:GetOp", Some("http://example.com/svc"));
1153
1154 let mut handlers = HashMap::new();
1155 handlers.insert("GetOp".to_string(), mock_handler("GetOp"));
1156
1157 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
1158
1159 let rpc_qname = QName::new("http://example.com/svc", "GetOp");
1161 let result = route(&table, &rpc_qname, None);
1162 assert!(
1163 result.is_ok(),
1164 "route by synthesized RPC QName should return Ok"
1165 );
1166 }
1167
1168 #[test]
1169 fn build_dispatch_table_rpc_missing_namespace_falls_back_to_target_ns() {
1170 let resolved = make_resolved_wsdl_rpc("GetOp", "urn:GetOp", None);
1172 let target_ns = resolved.definition.target_namespace.clone();
1173
1174 let mut handlers = HashMap::new();
1175 handlers.insert("GetOp".to_string(), mock_handler("GetOp"));
1176
1177 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
1178
1179 let fallback_qname = QName::new(&target_ns, "GetOp");
1181 let result = route(&table, &fallback_qname, None);
1182 assert!(
1183 result.is_ok(),
1184 "RPC without namespace should fall back to targetNamespace, got: {result:?}"
1185 );
1186 }
1187
1188 #[test]
1189 fn build_dispatch_table_for_service_isolates_operations() {
1190 let resolved = make_resolved_wsdl_two_services();
1191
1192 let op_a_qname = QName::new("http://example.com/multi", "OpA");
1193 let op_b_qname = QName::new("http://example.com/multi", "OpB");
1194
1195 let mut handlers_a = HashMap::new();
1196 handlers_a.insert("OpA".to_string(), mock_handler("OpA"));
1197
1198 let mut handlers_b = HashMap::new();
1199 handlers_b.insert("OpB".to_string(), mock_handler("OpB"));
1200
1201 let table_a = build_dispatch_table_for_service(
1202 "ServiceA",
1203 &resolved,
1204 handlers_a,
1205 &HashSet::new(),
1206 None,
1207 )
1208 .unwrap();
1209 let table_b = build_dispatch_table_for_service(
1210 "ServiceB",
1211 &resolved,
1212 handlers_b,
1213 &HashSet::new(),
1214 None,
1215 )
1216 .unwrap();
1217
1218 assert!(
1220 route(&table_a, &op_a_qname, None).is_ok(),
1221 "ServiceA should route OpA"
1222 );
1223 assert!(
1225 route(&table_b, &op_b_qname, None).is_ok(),
1226 "ServiceB should route OpB"
1227 );
1228 assert!(
1230 route(&table_a, &op_b_qname, None).is_err(),
1231 "ServiceA should NOT route OpB"
1232 );
1233 }
1234
1235 const INLINE_ELEMENT_WSDL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
1240<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
1241 xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
1242 xmlns:tns="http://example.com/svc"
1243 xmlns:xs="http://www.w3.org/2001/XMLSchema"
1244 targetNamespace="http://example.com/svc">
1245 <types>
1246 <xs:schema targetNamespace="http://example.com/svc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
1247 <xs:element name="GetFoo">
1248 <xs:complexType>
1249 <xs:sequence>
1250 <xs:element name="RequiredField" type="xs:string" minOccurs="1"/>
1251 <xs:element name="OptionalField" type="xs:string" minOccurs="0"/>
1252 </xs:sequence>
1253 </xs:complexType>
1254 </xs:element>
1255 <xs:element name="GetFooResponse">
1256 <xs:complexType><xs:sequence/></xs:complexType>
1257 </xs:element>
1258 </xs:schema>
1259 </types>
1260 <message name="GetFooRequest"><part name="parameters" element="tns:GetFoo"/></message>
1261 <message name="GetFooResponse"><part name="parameters" element="tns:GetFooResponse"/></message>
1262 <portType name="SvcPortType">
1263 <operation name="GetFoo">
1264 <input message="tns:GetFooRequest"/>
1265 <output message="tns:GetFooResponse"/>
1266 </operation>
1267 </portType>
1268 <binding name="SvcBinding" type="tns:SvcPortType">
1269 <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
1270 <operation name="GetFoo">
1271 <soap12:operation soapAction="urn:GetFoo"/>
1272 <input><soap12:body use="literal"/></input>
1273 <output><soap12:body use="literal"/></output>
1274 </operation>
1275 </binding>
1276 <service name="SvcService">
1277 <port name="SvcPort" binding="tns:SvcBinding">
1278 <soap12:address location="http://localhost/svc"/>
1279 </port>
1280 </service>
1281</definitions>"#;
1282
1283 const NAMED_TYPE_REF_WSDL: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
1285<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
1286 xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
1287 xmlns:tns="http://example.com/svc"
1288 xmlns:xs="http://www.w3.org/2001/XMLSchema"
1289 targetNamespace="http://example.com/svc">
1290 <types>
1291 <xs:schema targetNamespace="http://example.com/svc" xmlns:xs="http://www.w3.org/2001/XMLSchema">
1292 <xs:complexType name="GetBarType">
1293 <xs:sequence>
1294 <xs:element name="BarRequired" type="xs:string" minOccurs="1"/>
1295 </xs:sequence>
1296 </xs:complexType>
1297 <xs:element name="GetBar" type="tns:GetBarType"/>
1298 <xs:element name="GetBarResponse">
1299 <xs:complexType><xs:sequence/></xs:complexType>
1300 </xs:element>
1301 </xs:schema>
1302 </types>
1303 <message name="GetBarRequest"><part name="parameters" element="tns:GetBar"/></message>
1304 <message name="GetBarResponse"><part name="parameters" element="tns:GetBarResponse"/></message>
1305 <portType name="SvcPortType">
1306 <operation name="GetBar">
1307 <input message="tns:GetBarRequest"/>
1308 <output message="tns:GetBarResponse"/>
1309 </operation>
1310 </portType>
1311 <binding name="SvcBinding" type="tns:SvcPortType">
1312 <soap12:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
1313 <operation name="GetBar">
1314 <soap12:operation soapAction="urn:GetBar"/>
1315 <input><soap12:body use="literal"/></input>
1316 <output><soap12:body use="literal"/></output>
1317 </operation>
1318 </binding>
1319 <service name="SvcService">
1320 <port name="SvcPort" binding="tns:SvcBinding">
1321 <soap12:address location="http://localhost/svc"/>
1322 </port>
1323 </service>
1324</definitions>"#;
1325
1326 fn resolve_test_wsdl(wsdl: &str) -> ResolvedWsdl {
1327 use crate::wsdl::parser::WsdlError;
1328 use crate::wsdl::resolver::{resolve_wsdl, WsdlLoader};
1329 struct NullLoader;
1330 impl WsdlLoader for NullLoader {
1331 fn load(&self, loc: &str) -> Result<Vec<u8>, WsdlError> {
1332 Err(WsdlError::MalformedXml(format!("NullLoader: {loc}")))
1333 }
1334 }
1335 let mut visited = HashSet::new();
1336 resolve_wsdl(wsdl.as_bytes(), &NullLoader, &mut visited)
1337 .expect("WSDL resolution should succeed")
1338 }
1339
1340 #[test]
1342 fn doc_literal_inline_type_missing_required_field_rejected() {
1343 let resolved = resolve_test_wsdl(INLINE_ELEMENT_WSDL);
1344
1345 let elem_qname = QName::new("http://example.com/svc", "GetFoo");
1346
1347 assert!(
1350 resolved.type_registry.lookup(&elem_qname).is_some(),
1351 "Inline element type must be in TypeRegistry after resolve_wsdl"
1352 );
1353
1354 let mut handlers = HashMap::new();
1356 handlers.insert("GetFoo".to_string(), mock_handler("GetFoo"));
1357 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
1358
1359 let entry = route(&table, &elem_qname, None).unwrap();
1360 assert!(
1362 entry.validation_type.is_some(),
1363 "validation_type must be Some for document/literal op with inline type"
1364 );
1365
1366 let body_missing = br#"<tns:GetFoo xmlns:tns="http://example.com/svc"/>"#;
1368 let result = validate_request(
1369 body_missing,
1370 &resolved.type_registry,
1371 entry.validation_type.as_ref(),
1372 );
1373 assert!(
1374 result.is_err(),
1375 "Request missing RequiredField must produce a validation error"
1376 );
1377 let fault = result.unwrap_err();
1378 assert_eq!(fault.code, crate::fault::FaultCode::Sender);
1379 assert!(
1380 fault.reason.contains("RequiredField"),
1381 "Fault reason must mention the missing field, got: {}",
1382 fault.reason
1383 );
1384
1385 let body_ok = br#"<tns:GetFoo xmlns:tns="http://example.com/svc"><tns:RequiredField>val</tns:RequiredField></tns:GetFoo>"#;
1387 let result_ok = validate_request(
1388 body_ok,
1389 &resolved.type_registry,
1390 entry.validation_type.as_ref(),
1391 );
1392 assert!(
1393 result_ok.is_ok(),
1394 "Request with RequiredField must pass validation, got: {result_ok:?}"
1395 );
1396 }
1397
1398 #[test]
1400 fn doc_literal_named_type_ref_missing_required_field_rejected() {
1401 let resolved = resolve_test_wsdl(NAMED_TYPE_REF_WSDL);
1402
1403 let elem_qname = QName::new("http://example.com/svc", "GetBar");
1404 let type_qname = QName::new("http://example.com/svc", "GetBarType");
1405
1406 assert!(
1408 resolved.type_registry.lookup(&type_qname).is_some(),
1409 "Named type GetBarType must be in TypeRegistry"
1410 );
1411
1412 let mut handlers = HashMap::new();
1414 handlers.insert("GetBar".to_string(), mock_handler("GetBar"));
1415 let table = build_dispatch_table(&resolved, handlers, &HashSet::new(), None).unwrap();
1416
1417 let entry = route(&table, &elem_qname, None).unwrap();
1418 assert!(
1419 entry.validation_type.is_some(),
1420 "validation_type must be Some for document/literal op with named type ref"
1421 );
1422 assert_eq!(
1424 entry.validation_type.as_ref().unwrap(),
1425 &type_qname,
1426 "validation_type must be the named type QName, not the element QName"
1427 );
1428
1429 let body_missing = br#"<tns:GetBar xmlns:tns="http://example.com/svc"/>"#;
1431 let result = validate_request(
1432 body_missing,
1433 &resolved.type_registry,
1434 entry.validation_type.as_ref(),
1435 );
1436 assert!(
1437 result.is_err(),
1438 "Request missing BarRequired must produce a validation error"
1439 );
1440 let fault = result.unwrap_err();
1441 assert_eq!(fault.code, crate::fault::FaultCode::Sender);
1442 assert!(
1443 fault.reason.contains("BarRequired"),
1444 "Fault reason must mention the missing field, got: {}",
1445 fault.reason
1446 );
1447
1448 let body_ok = br#"<tns:GetBar xmlns:tns="http://example.com/svc"><tns:BarRequired>val</tns:BarRequired></tns:GetBar>"#;
1450 let result_ok = validate_request(
1451 body_ok,
1452 &resolved.type_registry,
1453 entry.validation_type.as_ref(),
1454 );
1455 assert!(
1456 result_ok.is_ok(),
1457 "Request with BarRequired must pass validation, got: {result_ok:?}"
1458 );
1459 }
1460}