OMG Web-Enabled DDS 1.0 — Spec Coverage
Spec: OMG Web-Enabled DDS 1.0 — formal/2014-12-01 →
Context: DDS-WEB defines a WebDDS object model + a REST PSM, with which
web clients (browsers, mobile apps, cloud services) participate in DDS
topics over HTTP. ZeroDDS implements the WebDDS object model (§7) + the
REST-PSM URI routing + status mapping + header set + XML element-tag
registry (§8.3) as a pure-Rust no_std+alloc library. The
HTTP server implementation + XML body serialization are caller-layer —
realized in the daemon crate crates/webdds-bridge (binary
zerodds-webdds-bridged), which serves REST (§8.3) and SOAP (§8.4) from
one process over crates/dcps’s public typed API.
Implementation:
crates/web/· docs.rs — WebDDS object model + REST PSM (URI routing, status mapping, header set, XML element-tag registry), 10 modules, 81 tests green.crates/webdds-bridge/· docs.rs — thezerodds-webdds-bridgeddaemon: aDdsBackendimpl against the typedzerodds-dcpsAPI (Gateway), type resolution from a config-named IDL file or TypeLookup discovery (TypeRegistry), an XML<->DynamicData codec driven by the type’s own descriptor (dynxml, never an opaque byte guess), REST + SOAP dispatch, a hand-rolled HTTP/1.1 server (Content-Length + chunked + keep-alive), WSDL generation from config-named IDL interfaces (zerodds_lang_rules::adapter::interface), business-interface dispatch over DDS-RPC 1.0’s Basic Mapping (one topic pair per interface, §7.5.1.1). 119 tests green (110 unit + 9 e2e, two of them against a real, build-time-generatedReplieron a second, independentDomainParticipantover real RTPS/UDP).
§1 Scope
§1.1-§1.6 Overview + conformance + examples
Spec: §1, p. 1-4 — a DDS gateway service over HTTP/REST. §1.1/§1.2/§1.6 are purpose, background and explicitly non-normative example scenarios; the checkable claims sit in §1.3.1 (a session whose life spans multiple physical connections; an access control model per principal), §1.4 (the object model extends the DDS object model with an access control model and an application management model) and §1.5 (two platform mappings: REST and SOAP).
Repo: crates/web/src/lib.rs (crate doc + re-exports),
crates/web/src/session.rs::SessionId,
crates/web/src/access_control.rs::{Permissions, Rule, Operation, Decision},
crates/web/src/rest.rs::parse_route; the SOAP mapping of §1.5 lives in
crates/zerodds-soap/src/wsdl.rs.
Tests: three tests, one per checkable claim:
crates/web/src/lib.rs::scope_tests::scope_session_identity_survives_reconstruction_across_connections
(§1.3.1 reason 2 — the same token string yields the same SessionId even
when reconstructed; a different token is a different session, an empty one is
no session),
crates/web/src/lib.rs::scope_tests::scope_access_control_gates_per_operation_and_per_topic
(§1.3.1 reason 3 + §1.4 — the decision depends on the operation and the
topic; the right topic with the wrong operation is denied, an unlisted topic
is denied, an empty rule set denies rather than permits),
crates/web/src/lib.rs::scope_tests::scope_rest_mapping_addresses_the_webdds_object_model
(§1.5 — the REST mapping addresses Application and DomainParticipant under
the §8.3.1 prefix, a URI without that prefix is not part of the platform).
Status: done
§2 Conformance
§2 Profile triple (REST + SIMPLE-REST + SIMPLE-WSDL-SOAP)
Spec: §2, p. 5 — REST Mandatory, SIMPLE-REST + SIMPLE-WSDL-SOAP
Optional.
Repo: ZeroDDS implements REST (mandatory) plus SOAP.
Tests: crates/web/src/rest.rs::tests::rest_prefix_constant_matches_spec,
crates/web/src/rest.rs::tests::table_5_every_pim_operation_has_a_route,
crates/web/src/headers.rs::tests::validate_required_passes_with_all_three_headers,
crates/web/src/status.rs::tests::ok_maps_to_201_for_post_create;
SIMPLE-WSDL-SOAP: crates/zerodds-soap/src/wsdl.rs::tests::wsdl_11_emits_definitions_root,
crates/zerodds-soap/src/envelope.rs::tests::parse_round_trip.
Status: done — the mandatory profile (REST) and the optional SIMPLE-WSDL-SOAP profile both implemented (see §8.4); the SIMPLE-REST profile analogous to the REST subset.
§3-§6 References + terminology
§3 Normative references
Spec: §3, p. 5 — DDS 1.2, DDS-CCM, DDS-XTYPES, HTTP RFC 2616, HTTP-Auth RFC 2617, WebSockets RFC 6455, SOAP 1.2, WSDL 1.1, XML 1.1.
Repo: —
Tests: —
Status: n/a (informative) — an external normative reference list;
HTTP/RFC/XML are fulfilled operationally in the respective consumer items
§7/§8.
§4-§6 Terms + symbols + acks
Spec: §4-§6, p. 6-8 — terms and definitions, symbols, acknowledgements.
Repo: —
Tests: —
Status: n/a (informative) — glossary/symbols/acks; without a code
mapping.
§7 WebDDS object model
§7.1 WebDDS object model overview
Spec: §7.1, p. 9 — the WebDDS object model is a containment tree:
WebDDS::Root holds application, client, type and QoS instances, and an
application holds its DomainParticipants.
Repo: crates/web/src/model.rs::{WebDdsRoot, Application, DomainParticipant, Client} — containment as ownership
(WebDdsRoot::applications, Application::participants,
WebDdsRoot::clients).
Tests: crates/web/src/model.rs::tests::every_model_object_is_reachable_from_the_root,
crates/web/src/model.rs::tests::application_carries_participants.
Status: done
§7.2 Singleton WebDDS::Root
Spec: §7.2, p. 10 — WebDDS::Root is a singleton; all other classes are
reachable via Root.
Repo: crates/web/src/model.rs::WebDdsRoot as a singleton pattern
(instantiated via the application lifecycle).
Tests: crates/web/src/model.rs::tests::root_starts_empty_and_is_the_only_container,
crates/web/src/model.rs::tests::root_holds_one_entry_per_application_name,
crates/web/src/model.rs::tests::every_model_object_is_reachable_from_the_root.
Status: done
§7.3 Access control + SessionId
Spec: §7.3, p. 11-13 — AccessController + session tracking.
Repo: crates/web/src/session.rs::SessionId +
crates/web/src/model.rs::Client + crates/web/src/access_control.rs
(permissions data model + an evaluate(op, topic) -> Decision engine, a
glob matcher with */prefix*/*suffix patterns, the spec §7.3 decision
tree: first deny-match wins, otherwise first permit-match, otherwise
default).
Tests: crates/web/src/session.rs::tests::{session_id_carries_token_string, empty_session_id_is_invalid};
crates/web/src/access_control.rs::tests::{empty_permissions_returns_default_deny, allow_rule_grants_permit, deny_rule_overrides_allow_when_listed_first, glob_prefix_pattern_matches, glob_suffix_pattern_matches, operation_mismatch_falls_through_to_default, first_matching_deny_wins_over_later_allow} (7).
Status: done
§7.3.1 Class WebDDS::Root
Spec: §7.3.1, p. 13-17 — Root operations: create_application, delete_application, get_applications (with fnmatch), create/delete/update/get_qos_library, create/delete/get_type.
Repo: WebDdsRoot::{create_application, delete_application, get_applications} with an fnmatch wildcard subset.
Tests: crates/web/src/model.rs::tests::{create_application_returns_session_id, duplicate_application_yields_object_already_exists, delete_application_removes_existing, delete_unknown_application_yields_invalid_object, get_applications_with_wildcard_matches_all, get_applications_with_prefix_pattern_matches_subset, get_applications_with_exact_match}.
Cross-ref schema storage (Phase-B cluster-8): QosLibrary CRUD via
crates/xml/src/qos.rs::QosLibrary, crates/xml/src/parser.rs::parse_xml_tree; type CRUD via the
crates/types/ TypeLibrary. Bridge to the DCPS API in
crates/web/src/bridge.rs::DdsBackend trait (see §7.4).
Status: done
§7.4 Class operations Application + Participant + …
Spec: §7.4, p. 17-58 — DomainParticipant, Topic, Publisher, Subscriber, DataWriter, DataReader, WaitSet — all CRUD.
Repo: data-model stubs in crates/web/src/model.rs::{Application, DomainParticipant}.
Repo: crates/web/src/bridge.rs::DdsBackend trait with all ~35 §7.4
class operations: create/update/delete/list for Application, Participant,
Topic, Publisher, Subscriber, DataWriter, DataReader, plus write_sample/
read_samples and create/update/wait/delete/list_waitset (§7.4.9’s
long-poll WaitSet::get). QosLibrary/QosProfile/Type CRUD (§7.3.1 Root
metadata, not a DCPS entity) is deliberately not in the trait.
enforce(decision) -> Result<(), BackendError> composes orthogonally
with the AccessController engine from §7.3.
Repo: crates/webdds-bridge/src/gateway.rs::Gateway — the concrete
DdsBackend impl against crates/dcps/’s public typed API
(DomainParticipant/Topic/Publisher/Subscriber/DataWriter/
DataReader/WaitSet). Entity names the trait does not take from the
caller (participant, publisher, subscriber, writer, reader) are generated
and returned in the Created path. PUT/update on a DCPS entity calls
set_qos on the live zerodds-dcps entity (zerodds_dcps::Entity) —
crate::qos_update reads the PUT body’s <qos> element and merges it
onto the entity’s current QoS (not a fresh default: zerodds_qos’s own
policy defaults differ from zerodds_dcps’s entity defaults, and a
“replace” approach tripped ImmutablePolicy on a Changeable=NO policy
the caller never named — measured, see qos_update’s module doc).
UpdateWaitset replaces the condition set instead (§7.4.3.5).
Repo: a waitset’s long-poll waits without the gateway’s lock
(Gateway::waitset_handle, WaitsetHandle::wait); clients of the same
waitset take turns, because a DCPS WaitSet refuses a second concurrent
wait with PRECONDITION_NOT_MET. Measured on the Linux test host before: during a
4 s long-poll a list of applications took 3.7 s, now 0.4 ms. A timeout
that is not a finite, non-negative number of seconds and a domain_id
that is not a domain id are InvalidInput (422); before, the
connection’s thread ended without a response on -1, nan, 1e300
(Duration::from_secs_f64), abc silently waited 10 s, and an
unreadable domain_id gave domain 0. A missing domain_id is still
domain 0 (spec text on it not checked). Tests:
crates/webdds-bridge/tests/daemon_e2e.rs::a_long_poll_leaves_other_requests_alone_and_a_bad_timeout_is_refused,
crates/webdds-bridge/src/gateway.rs::tests::{a_timeout_is_a_finite_non_negative_number_of_seconds, a_domain_id_is_read_or_refused}.
Repo: an inline <qos> is not the only source §7.4 bodies provide
for QoS — <qos_profile>name</qos_profile> names a stored
QosLibrary/QosProfile pair by reference. crate::qos_update:: resolve_entity_qos/resolve_qos_profile_ref resolve the reference
against crate::metadata::MetadataStore (zerodds_xml::resolve_profile)
and merge an additionally present inline <qos> over it (override
wins). §7.4.3.1/§7.4.3.2/§7.4.4.7/§7.4.4.8/§7.4.4.11/§7.4.4.12 also let
create_participant/create_publisher/create_subscriber and their
update_* counterparts carry nested <publishers>/<subscribers>/
<data_writers>/<data_readers> — crate::contained_entities parses
them, crate::gateway::{sync_publishers, sync_subscribers, sync_data_writers, sync_data_readers} reconcile the live DCPS entities
against the parsed set by name (add, re-apply QoS, or remove). update_*
distinguishes “the element is absent” (leave untouched) from “the
element is empty” (delete every contained entity of that kind) —
literally what the spec’s own sentence (“if the
…ObjectRepresentation specifies a set of contained entities”) asks for.
Tests: crates/webdds-bridge/src/qos_update.rs::tests (incl.
qos_profile_reference_resolves_a_stored_profile,
qos_profile_reference_to_an_unknown_profile_is_a_bad_request,
inline_qos_overrides_the_profiles_value_for_the_same_policy);
crates/webdds-bridge/src/contained_entities.rs::tests (incl.
nested_publishers_and_data_writers_parse,
a_publishers_qos_profile_reference_resolves,
a_contained_entity_without_a_name_is_a_bad_request);
crates/webdds-bridge/src/gateway.rs::tests:: update_participant_creates_and_reconciles_contained_entities (real DCPS
entities, offline domain: create -> reconcile -> leave-untouched, full
cycle).
Tests: crates/web/src/bridge.rs::tests::{enforce_permit_allows_call, enforce_deny_returns_forbidden, permission_check_then_backend_call_chains_correctly, create_then_delete_application_round_trip, create_application_twice_yields_conflict} (5);
crates/webdds-bridge/src/gateway.rs::tests (3) +
crates/webdds-bridge/tests/daemon_e2e.rs::write_over_http_arrives_at_an_independent_participant
(an HTTP write through the daemon, verified by a second, independent
DomainParticipant over real RTPS/UDP).
Status: done
§7.4.8 DataReader::read with a SampleSelector
Spec: §7.4.8, p. 51-53 — read with a SampleSelector BNF grammar
(FilterExpression + MetadataExpression with AND/OR logic).
Repo: crates/web/src/sample_selector.rs::parse_sample_selector returns
an AST (SampleSelector with filter: Option<FilterExpression> +
metadata: Vec<MetadataExpression>). A recursive-descent parser with all 6
comparison operators (=,!=,<,<=,>,>=), AND/OR logic, parentheses,
integer/string/boolean literals, a dotted field path,
sample_state/view_state/instance_state metadata with all values.
Tests: crates/web/src/sample_selector.rs::tests::{parses_simple_equality_filter, parses_inequality_with_string_literal, parses_dotted_field_path, parses_and_conjunction, parses_or_with_parenthesis, parses_metadata_only_expression, parses_filter_plus_metadata, parses_all_three_metadata_kinds, comparison_operators_full_coverage, boolean_literal_supported, rejects_unknown_metadata_key, rejects_unknown_metadata_value, rejects_unbalanced_parenthesis, rejects_trailing_garbage} (14).
Status: done
§8 REST PSM
§8.1 Formats
Spec: §8.1, p. 59 — Content-Type application/dds-web+xml
(mandatory) as the XML format; further formats optional. A full-text
search of the PDF (§8.2, §8.3.5 Tab 7/8) finds no second
representation — DDS-WEB 1.0 defines no JSON.
Repo: crates/web/src/representation.rs::{ContentType, CONTENT_TYPE_DDS_WEB_XML}. The constant’s own value was, as of before
this round, wrong codebase-wide (application/zerodds-web+xml, six
files) — a measured bug, not a missing representation.
Tests: crates/web/src/representation.rs::tests::content_type_constant.
Status: done
§8.2 Representations
Spec: §8.2, p. 60-61 — four representation kinds: QoS, Type, Data, Entity (with XML element tags per kind).
Repo: crates/web/src/representation.rs::{RepresentationKind, element_name}.
Tests: crates/web/src/representation.rs::tests::element_names_match_spec_tab_6,
crates/web/src/representation.rs::tests::types_representation_uses_dds_xtypes_root.
Status: done — element tag registry in crates/web/src/representation.rs; the XML bodies (application/dds-web+xml) are written by crates/webdds-bridge (gateway.rs, contained_entities.rs).
§8.3.1 Mapping to resource URIs
Spec: §8.3.1, p. 62 Tab 4 — all URI patterns with prefix /dds/rest1.
Repo: crates/web/src/rest.rs::{REST_PREFIX, RestRoute, parse_route, RestMethod} with all 30+ routes.
Tests: crates/web/src/rest.rs::tests::{rest_prefix_constant_matches_spec, missing_prefix_yields_error, create_application_route, get_applications_route, delete_application_route_extracts_app_name, create_participant_route_extracts_app_name, delete_participant_route_extracts_app_and_participant, data_writer_write_route_extracts_4_segments, data_reader_read_route_extracts_4_segments, qos_profile_routes_extract_lib_and_profile_name, waitset_routes_extract_app_and_waitset, topic_publisher_subscriber_create_routes, unknown_paths_yield_unknown_variant, types_routes_match_root_pattern} (14).
Status: done
§8.3.2 Mapping to HTTP methods + status codes
Spec: §8.3.2, p. 62-64 — method mapping (POST/PUT/GET/DELETE) + status codes (201, 204, 200, 409, 422, 404, 401, 403, 500).
Repo: crates/web/src/status.rs::{ReturnStatus, http_status_for}.
Tests: crates/web/src/status.rs::tests::{ok_maps_to_201_for_post_create, ok_maps_to_204_for_delete, ok_maps_to_200_for_get, ok_maps_to_204_for_put_update, object_already_exists_maps_to_409, invalid_input_maps_to_422, invalid_object_maps_to_404, access_denied_maps_to_401, permissions_error_maps_to_403, generic_service_error_maps_to_500, dds_error_maps_to_500} (11).
Status: done
§8.3.3 Complete mapping (Tab 5)
Spec: §8.3.3, p. 64-67 — Table 5, “Complete mapping of WebDDS PIM operations to REST methods”: 48 rows, an HTTP method and a URI per operation. Plus the sentence above the table: “the HEAD HTTP method shall be supported on the same URIs as the GET method. The HEAD method shall behave identically to the GET method except for it shall return no body.”
Repo: crates/web/src/rest.rs::RestRoute (47 route variants plus
Unknown), crates/web/src/rest.rs::parse_route,
crates/web/src/rest.rs::canonical_segment.
Tests: the table is transcribed row by row into TABLE_5 (operation,
method, URI, taken from the PDF) and read by five tests:
crates/web/src/rest.rs::tests::table_5_every_pim_operation_has_a_route
(no row falls through to RestRoute::Unknown),
crates/web/src/rest.rs::tests::table_5_rows_do_not_collide (48 rows yield
48 distinct routes — two operations landing on one variant would make the
mapping ambiguous),
crates/web/src/rest.rs::tests::table_5_alternate_segment_spellings_route_identically
(Table 5 spells the same collection two ways — domain_participants against
participants, datawriters/datareaders against
data_writers/data_readers; both spellings reach the same route),
crates/web/src/rest.rs::tests::canonicalisation_is_positional_not_global
(the normalisation applies only in the collection slots, so an application
actually named participants stays a name),
crates/web/src/rest.rs::tests::head_behaves_like_get_on_the_same_uris
(every GET row of the table routes identically under HEAD).
Finding from writing the test: the table was not fully mapped when this
test was added. 23 of the 48 rows fell through to RestRoute::Unknown — the
whole update/delete/get level for waitsets, registered types, topics,
publishers, subscribers, datawriters and datareaders, plus
Publisher::create_datawriter and Subscriber::create_datareader. On top of
that RestMethod::Head existed as an enum variant but was handled nowhere in
parse_route, so every GET URI fell through to Unknown under HEAD. Both
are built as of this state: the 23 missing routes are implemented, HEAD
routes like GET, and the second spelling of the collection segments is
accepted.
Status: done
§8.3.4 Object representations (Tab 6)
Spec: §8.3.4, p. 68-69 Tab 6 — the XML element-tag registry.
Repo: crates/web/src/representation.rs::element_name.
Tests: crates/web/src/representation.rs::tests::element_names_match_spec_tab_6.
Status: done
§8.3.5 HTTP headers (Tab 7+8)
Spec: §8.3.5, p. 70 — required request/response headers (Accept, Content-Length, Content-Type, Cache-Control, OMG-DDS-API-Key, Authentication-Info, Date, Expires, Location, Last-Modified).
Repo: crates/web/src/headers.rs::{RequestHeaders, ResponseHeaders, REQUEST_API_KEY, validate_required}.
Repo (daemon): the library types alone were not enough — the daemon
called validate_required nowhere, as of before this round, so a
request with no Accept (or one naming none of these representations)
was still served. crates/webdds-bridge/src/rest_service.rs:: check_representation_headers now checks before every REST operation: a
missing Accept -> 400, an Accept that names neither
application/dds-web+xml nor a matching */*/application/* -> 406, a
Content-Type naming anything else -> 415.
Tests: crates/web/src/headers.rs::tests::{request_api_key_constant_matches_spec, validate_required_passes_with_all_three_headers, missing_api_key_rejected, missing_accept_rejected, missing_cache_control_rejected, response_headers_default_is_empty} (6);
crates/webdds-bridge/src/rest_service.rs::tests::{missing_accept_header_is_400, accept_header_naming_an_unserved_representation_is_406, accept_header_wildcards_are_acceptable, content_type_naming_an_unserved_representation_is_415} (4).
Status: done
§8.4 SOAP PSM
§8.4 Simplified SOAP platform
Spec: §8.4, p. 71+ — optional SIMPLE-WSDL-SOAP.
Repo: crates/zerodds-soap/src/{envelope,addressing,fault,mtom, security,wsdl}.rs (SOAP envelope codec, WS-Addressing, MTOM, WS-Security
hooks, WSDL emitter; 1218 SLOC).
Repo (daemon): crates/webdds-bridge/src/soap_service.rs dispatches
the SOAP envelope’s body (a DDS-WEB object-model operation name such as
CreateApplication/WriteSample — the same operation set REST exposes)
against the same Gateway: REST and SOAP are sibling bindings of one
object model, not a second protocol. crates/webdds-bridge/src/wsdl_bridge.rs
generates the WSDL both for this fixed operation set and for every
config-named IDL interface (via zerodds_lang_rules::adapter::interface
— the IDL-to-operation bridge plan §3 asked for).
A business interface’s operation is WSDL-described and dispatchable:
crate::rpc_dispatch builds, per configured interface, DDS-RPC 1.0
§7.5.1.1’s Basic Mapping types dynamically — one request/reply topic
pair per interface (service_name = the interface’s qualified name,
e.g. demo_Calculator), one Call/Return union over every operation
(discriminator = operation_hash), one Result union per non-oneway
operation (dds::RETCODE_OK -> the Out struct, one case per raises
exception, discriminator = exception_hash). Every synthesized wire
type is @final (classic CDR, no DHEADER) — the same shape
zerodds-idlc generate --lang rust/--lang cpp themselves emit for an
interface (crates/lang-rules/src/emit/interface_rust.rs), and the
same operation_hash/exception_hash/struct-naming functions from
zerodds_lang_rules::adapter::interface those generators call
themselves — not a parallel derivation that happens to compute the same
value. An operation naming no configured interface (and no DDS-WEB
object-model operation either) still returns a named env:Receiver
fault instead of a silent no-op; a declared raises exception does too,
named with its fields (OperationException), not an empty or wrong
success.
Interop evidence: crates/webdds-bridge/tests/soap_rpc_e2e.rs calls
not a hand-written stand-in but a real Replier, generated at build
time with zerodds_lang_rules::generate::source(.., Language::Rust, ..)
(zerodds-rpc-rust-e2e’s Calculator binding — module demo { exception DivByZero; @DDSService interface Calculator { … } }, a return value,
raises, oneway, an attribute, inout — every DDS-RPC 1.0 §7.5.1.1
construct in one interface), on a second, independent
DomainParticipant, over real RTPS/UDP.
Tests: crates/zerodds-soap/src/envelope.rs::tests::{build_simple_body_only, build_with_header, parse_round_trip, parse_missing_envelope_rejected, parse_missing_body_rejected, build_includes_namespace_declaration};
crates/zerodds-soap/src/addressing.rs::tests::{to_and_action_are_emitted, message_id_and_relates_to_round_trip, reply_to_and_fault_to_emit_address_subelement, ns_constant_is_w3c_2005_08};
crates/zerodds-soap/src/fault.rs::tests::{fault_codes_have_correct_qnames, fault_xml_contains_required_elements, fault_with_detail_includes_detail_block, fault_reason_is_xml_escaped};
crates/zerodds-soap/src/wsdl.rs::tests::{wsdl_11_emits_definitions_root, wsdl_20_emits_description_root, wsdl_11_includes_all_operation_messages, wsdl_11_binding_uses_soap_http_transport, wsdl_20_binding_targets_soap_protocol, endpoint_address_is_included};
crates/zerodds-soap/src/security.rs::tests::{username_token_text_password_emits_correct_type, username_token_digest_uses_digest_uri, timestamp_emits_created_and_expires, x509_token_default_uses_v3_value_type, password_type_uris_match_spec};
crates/zerodds-soap/src/mtom.rs::tests::{package_starts_with_xop_root_part, package_includes_attachment_part, package_terminates_with_closing_boundary, xop_include_namespace_is_correct};
crates/webdds-bridge/src/rpc_dispatch.rs::tests::{ finds_a_declared_operation_across_flattened_services, an_operation_with_no_params_gets_a_dummy_member, every_synthetic_type_is_final_not_appendable, builds_one_interface_wide_call_and_return_union};
crates/webdds-bridge/tests/soap_rpc_e2e.rs::{ soap_add_reaches_a_generated_replier_and_returns_its_reply, soap_div_by_zero_surfaces_the_declared_exception, soap_call_to_an_unconfigured_operation_is_a_named_fault} — the first
two against the real, generated Replier;
crates/webdds-bridge/tests/daemon_e2e.rs::soap_write_sample_arrives_at_an_independent_participant
(a WriteSample whose <data> holds the sample as elements in the REST
representation arrives at a second, independent DomainParticipant over
RTPS/UDP; measured on the Linux test host before: env:Receiver “malformed XML
body: no root element”, because the field reader took the children of
<data> for a list),
crates/webdds-bridge/src/xmlfields.rs::tests::a_child_subtree_comes_back_as_xml.
Status: done — the SOAP PSM as a differentiation feature for the Java-enterprise estate, fully covered.
Audit status
16 done / 0 partial / 0 open / 0 blocked / 2 n/a (informative).
Test run:
cargo test -p zerodds-web— 81 tests green, 0 failed. Modules:access_control,bridge,headers,model,representation,rest,sample_selector,session,status, plusscope_tests(§1) and the Table-5 tests inrest(§8.3.3).cargo test -p zerodds-soap --lib— 37 tests green. Modules:addressing,envelope,fault,mtom,security,wsdl.cargo test -p zerodds-webdds-bridge— 110 unit + 9 e2e tests green (the daemon that combines the two libraries and actually serves them; seecrates/webdds-bridge/README.md).
Open items: none.
OMG Web-Enabled DDS 1.0 — Spec-Coverage
Spec: OMG Web-Enabled DDS 1.0 — formal/2014-12-01 →
Kontext: DDS-WEB definiert ein WebDDS-Object-Model + REST-PSM, mit
dem Web-Clients (Browser, Mobile-Apps, Cloud-Services) über HTTP an
DDS-Topics teilnehmen. ZeroDDS implementiert das WebDDS-Object-
Model (§7) + REST-PSM URI-Routing + Status-Mapping + Header-Set +
XML-Element-Tag-Registry (§8.3) als pure-Rust no_std+alloc Library.
Die HTTP-Server-Implementation + XML-Body-Serialization sind
Caller-Layer — realisiert im Daemon-Crate crates/webdds-bridge
(Binary zerodds-webdds-bridged), das REST (§8.3) und SOAP (§8.4) aus
einem Prozess über crates/dcpss öffentliche typisierte API bedient.
Implementation:
crates/web/· docs.rs — WebDDS-Object-Model + REST-PSM (URI-Routing, Status-Mapping, Header-Set, XML-Element-Tag-Registry), 10 Module, 81 Tests grün.crates/webdds-bridge/· docs.rs — Daemonzerodds-webdds-bridged:DdsBackend-Impl gegen die typisiertezerodds-dcps-API (Gateway), Typauflösung aus config-benanntem IDL oder TypeLookup-Discovery (TypeRegistry), XML↔︎ DynamicData-Codec über das Typ-Deskriptor (dynxml, nie Byte-Rätselraten), REST- + SOAP-Dispatch, ein handgeschriebener HTTP/1.1-Server (Content-Length + chunked + Keep-Alive), WSDL-Generierung aus config-benannten IDL-Interfaces (zerodds_lang_rules::adapter::interface), Business-Interface-Dispatch über DDS-RPC 1.0s Basic-Mapping (ein Topic-Paar pro Interface, §7.5.1.1). 119 Tests grün (110 Unit + 9 E2E, darunter zwei gegen einen echten, zur Build-Zeit generiertenReplierauf einem zweiten, unabhängigenDomainParticipantüber echtes RTPS/UDP).
§1 Scope
§1.1-§1.6 Overview + Conformance + Examples
Spec: §1, S. 1-4 — DDS-Gateway-Service über HTTP/REST. §1.1/§1.2/§1.6 sind Zweck, Hintergrund und ausdrücklich nicht-normative Beispielszenarien; prüfbare Aussagen stehen in §1.3.1 (Session über mehrere physische Verbindungen; Access-Control-Modell je Principal), §1.4 (das Objektmodell erweitert das DDS-Objektmodell um Access-Control- und Application-Management-Modell) und §1.5 (zwei Plattform-Mappings: REST und SOAP).
Repo: crates/web/src/lib.rs (Crate-Doc + Re-Exports),
crates/web/src/session.rs::SessionId,
crates/web/src/access_control.rs::{Permissions, Rule, Operation, Decision},
crates/web/src/rest.rs::parse_route; das SOAP-Mapping aus §1.5 liegt in
crates/zerodds-soap/src/wsdl.rs.
Tests: drei Tests, je einer pro prüfbarer Aussage:
crates/web/src/lib.rs::scope_tests::scope_session_identity_survives_reconstruction_across_connections
(§1.3.1 Grund 2 — dieselbe Token-Zeichenkette ergibt dieselbe SessionId,
auch neu konstruiert; ein anderes Token ist eine andere Session, ein leeres
ist keine),
crates/web/src/lib.rs::scope_tests::scope_access_control_gates_per_operation_and_per_topic
(§1.3.1 Grund 3 + §1.4 — die Entscheidung hängt an Operation und Topic;
richtige Topic mit falscher Operation wird abgelehnt, ein nicht gelistetes
Topic ebenso, ein leerer Regelsatz lehnt ab statt zu erlauben),
crates/web/src/lib.rs::scope_tests::scope_rest_mapping_addresses_the_webdds_object_model
(§1.5 — das REST-Mapping adressiert Application und DomainParticipant unter
dem §8.3.1-Präfix, eine URI ohne dieses Präfix gehört nicht zur Plattform).
Status: done
§2 Conformance
§2 Profile-Triple (REST + SIMPLE-REST + SIMPLE-WSDL-SOAP)
Spec: §2, S. 5 — REST Mandatory, SIMPLE-REST + SIMPLE-WSDL-SOAP
Optional.
Repo: ZeroDDS implementiert REST (Mandatory) plus SOAP.
Tests: crates/web/src/rest.rs::tests::rest_prefix_constant_matches_spec,
crates/web/src/rest.rs::tests::table_5_every_pim_operation_has_a_route,
crates/web/src/headers.rs::tests::validate_required_passes_with_all_three_headers,
crates/web/src/status.rs::tests::ok_maps_to_201_for_post_create;
SIMPLE-WSDL-SOAP: crates/zerodds-soap/src/wsdl.rs::tests::wsdl_11_emits_definitions_root,
crates/zerodds-soap/src/envelope.rs::tests::parse_round_trip.
Status: done — Mandatory-Profile (REST) und Optional SIMPLE-WSDL- SOAP-Profile beide implementiert (siehe §8.4); SIMPLE-REST-Profile analog REST-Subset.
§3-§6 Referenzen + Terminologie
§3 Normative References
Spec: §3, S. 5 — DDS 1.2, DDS-CCM, DDS-XTYPES, HTTP RFC 2616, HTTP-Auth RFC 2617, WebSockets RFC 6455, SOAP 1.2, WSDL 1.1, XML 1.1.
Repo: —
Tests: —
Status: n/a (informative) — Externe normative Referenz-Liste; HTTP/RFC/XML werden in den jeweiligen Konsumenten-Items §7/§8 operativ erfüllt.
§4-§6 Terms + Symbols + Acks
Spec: §4-§6, S. 6-8 — Terms and Definitions, Symbols, Acknowledgements.
Repo: —
Tests: —
Status: n/a (informative) — Glossar/Symbole/Acks; ohne Code-Mapping.
§7 WebDDS Object Model
§7.1 WebDDS Object Model Overview
Spec: §7.1, S. 9 — das WebDDS-Object-Model ist ein Containment-Baum:
WebDDS::Root enthält Application-, Client-, Type- und QoS-Instanzen,
eine Application enthält ihre DomainParticipants.
Repo: crates/web/src/model.rs::{WebDdsRoot, Application, DomainParticipant, Client} — Containment als Besitz-Beziehung
(WebDdsRoot::applications, Application::participants,
WebDdsRoot::clients).
Tests: crates/web/src/model.rs::tests::every_model_object_is_reachable_from_the_root,
crates/web/src/model.rs::tests::application_carries_participants.
Status: done
§7.2 Singleton WebDDS::Root
Spec: §7.2, S. 10 — WebDDS::Root ist Singleton; alle anderen
Klassen sind über Root erreichbar.
Repo: crates/web/src/model.rs::WebDdsRoot als Singleton-
Pattern (instanziiert über Application-Lifecycle).
Tests: crates/web/src/model.rs::tests::root_starts_empty_and_is_the_only_container,
crates/web/src/model.rs::tests::root_holds_one_entry_per_application_name,
crates/web/src/model.rs::tests::every_model_object_is_reachable_from_the_root.
Status: done
§7.3 Access Control + SessionId
Spec: §7.3, S. 11-13 — AccessController + Session-Tracking.
Repo: crates/web/src/session.rs::SessionId +
crates/web/src/model.rs::Client + crates/web/src/access_control.rs
(Permissions-Datenmodell + evaluate(op, topic) -> Decision-
Engine, Glob-Matcher mit */prefix*/*suffix-Pattern, Spec
§7.3-Decision-Tree: erste Deny-Match wins, sonst erste Permit-Match,
sonst default).
Tests: crates/web/src/session.rs::tests::{session_id_carries_token_string, empty_session_id_is_invalid};
crates/web/src/access_control.rs::tests::{empty_permissions_returns_default_deny, allow_rule_grants_permit, deny_rule_overrides_allow_when_listed_first, glob_prefix_pattern_matches, glob_suffix_pattern_matches, operation_mismatch_falls_through_to_default, first_matching_deny_wins_over_later_allow} (7).
Status: done
§7.3.1 Class WebDDS::Root
Spec: §7.3.1, S. 13-17 — Root-Operations: create_application, delete_application, get_applications (mit fnmatch), create/delete/update/get_qos_library, create/delete/get_type.
Repo: WebDdsRoot::{create_application, delete_application, get_applications} mit fnmatch-Wildcard-Subset.
Tests: crates/web/src/model.rs::tests::{create_application_returns_session_id, duplicate_application_yields_object_already_exists, delete_application_removes_existing, delete_unknown_application_yields_invalid_object, get_applications_with_wildcard_matches_all, get_applications_with_prefix_pattern_matches_subset, get_applications_with_exact_match}.
Cross-Ref Schema-Storage (Phase-B-Cluster-8): QosLibrary CRUD
über crates/xml/src/qos.rs::QosLibrary, crates/xml/src/parser.rs::parse_xml_tree;
Type-CRUD über crates/types/-TypeLibrary. Bridge zur DCPS-API
in crates/web/src/bridge.rs::DdsBackend-Trait (siehe §7.4).
Status: done
§7.4 Class-Operationen Application + Participant + …
Spec: §7.4, S. 17-58 — DomainParticipant, Topic, Publisher, Subscriber, DataWriter, DataReader, WaitSet — alle CRUD.
Repo: Datenmodell-Stubs in crates/web/src/model.rs::{Application, DomainParticipant}.
Repo: crates/web/src/bridge.rs::DdsBackend Trait mit allen ~35
§7.4-Class-Operationen: create/update/delete/list für Application,
Participant, Topic, Publisher, Subscriber, DataWriter, DataReader, plus
write_sample/read_samples und create/update/wait/delete/list_waitset
(§7.4.9s Long-Poll WaitSet::get). QosLibrary/QosProfile/Type-CRUD
(§7.3.1, Root-Metadaten, keine DCPS-Entität) bewusst nicht im Trait.
enforce(decision) -> Result<(), BackendError> komponiert orthogonal
mit der AccessController-Engine aus §7.3.
Repo: crates/webdds-bridge/src/gateway.rs::Gateway — konkrete
DdsBackend-Impl gegen crates/dcps/s öffentliche typisierte API
(DomainParticipant/Topic/Publisher/Subscriber/DataWriter/
DataReader/WaitSet). Entity-Namen, die der Trait nicht vom Aufrufer
nimmt (Participant/Publisher/Subscriber/Writer/Reader), werden generiert
und im Created-Pfad zurückgegeben. PUT/Update auf einer DCPS-Entität
ruft set_qos auf der lebenden zerodds-dcps-Entität
(zerodds_dcps::Entity) — crate::qos_update liest das <qos>-Element
des PUT-Bodys und merged es auf die aktuelle QoS der Entität (nicht auf
einen frischen Default: zerodds_qoss eigene Policy-Defaults weichen
von zerodds_dcpss Entity-Defaults ab, ein “replace”-Ansatz hätte auf
Changeable=NO-Policies stets einen ImmutablePolicy-Fehler ausgelöst,
selbst wenn der Aufrufer sie nie erwähnt hat — gemessen, siehe
qos_updates Moduldoc). UpdateWaitset ersetzt stattdessen die
Condition-Menge (§7.4.3.5).
Repo: der Long-Poll eines WaitSets wartet ohne die Sperre des
Gateways (Gateway::waitset_handle, WaitsetHandle::wait); Clients
desselben WaitSets warten nacheinander, weil ein DCPS-WaitSet ein
zweites gleichzeitiges wait mit PRECONDITION_NOT_MET abweist. Vorher
auf dem Linux-Testhost gemessen: Während eines Long-Polls von 4 s dauerte eine
Liste der Applications 3,7 s, jetzt 0,4 ms. Ein timeout, der keine
endliche, nicht negative Sekundenzahl ist, und ein domain_id, der
keine Domain-ID ist, ergeben InvalidInput (422); vorher endete der
Verbindungsthread bei -1, nan, 1e300 ohne Antwort
(Duration::from_secs_f64), abc wartete still 10 s, und ein
unlesbarer domain_id ergab Domain 0. Ein fehlender domain_id ist
weiter Domain 0 (Spec-Text dazu nicht geprüft). Tests:
crates/webdds-bridge/tests/daemon_e2e.rs::a_long_poll_leaves_other_requests_alone_and_a_bad_timeout_is_refused,
crates/webdds-bridge/src/gateway.rs::tests::{a_timeout_is_a_finite_non_negative_number_of_seconds, a_domain_id_is_read_or_refused}.
Repo: ein <qos> ist nicht die einzige Quelle, die §7.4-Bodys für
QoS vorsehen — <qos_profile>name</qos_profile> nennt ein gespeichertes
QosLibrary/QosProfile-Paar per Referenz. crate::qos_update:: resolve_entity_qos/resolve_qos_profile_ref lösen die Referenz gegen
crate::metadata::MetadataStore auf (zerodds_xml::resolve_profile)
und mergen ein zusätzlich vorhandenes inline-<qos> darüber (Override
gewinnt). §7.4.3.1/§7.4.3.2/§7.4.4.7/§7.4.4.8/§7.4.4.11/§7.4.4.12
erlauben außerdem, dass create_participant/create_publisher/
create_subscriber und ihre update_*-Gegenstücke verschachtelte
<publishers>/<subscribers>/<data_writers>/<data_readers>
mitbringen — crate::contained_entities parst sie,
crate::gateway::{sync_publishers, sync_subscribers, sync_data_writers, sync_data_readers} gleichen die lebenden
DCPS-Entitäten per Name ab (anlegen, QoS neu anwenden, entfernen).
update_* unterscheidet dabei “Element fehlt” (unverändert lassen)
von “Element ist leer” (alle Contained-Entities dieser Art löschen) —
wörtlich, was der Spec-Satz “if the …ObjectRepresentation specifies a
set of contained entities” fordert.
Tests: crates/webdds-bridge/src/qos_update.rs::tests (u.a.
qos_profile_reference_resolves_a_stored_profile,
qos_profile_reference_to_an_unknown_profile_is_a_bad_request,
inline_qos_overrides_the_profiles_value_for_the_same_policy);
crates/webdds-bridge/src/contained_entities.rs::tests (u.a.
nested_publishers_and_data_writers_parse,
a_publishers_qos_profile_reference_resolves,
a_contained_entity_without_a_name_is_a_bad_request);
crates/webdds-bridge/src/gateway.rs::tests:: update_participant_creates_and_reconciles_contained_entities (echte
DCPS-Entitäten, Offline-Domain: Anlegen → Abgleichen →
Unverändert-lassen im vollen Zyklus).
Tests: crates/web/src/bridge.rs::tests::{enforce_permit_allows_call, enforce_deny_returns_forbidden, permission_check_then_backend_call_chains_correctly, create_then_delete_application_round_trip, create_application_twice_yields_conflict} (5);
crates/webdds-bridge/src/gateway.rs::tests (3) +
crates/webdds-bridge/tests/daemon_e2e.rs::write_over_http_arrives_at_an_independent_participant
(HTTP-Write über den Daemon, verifiziert von einem zweiten,
unabhängigen DomainParticipant über echtes RTPS/UDP).
Status: done
§7.4.8 DataReader::read mit SampleSelector
Spec: §7.4.8, S. 51-53 — read mit SampleSelector-BNF-Grammar
(FilterExpression + MetadataExpression mit AND/OR-Logik).
Repo: crates/web/src/sample_selector.rs::parse_sample_selector
liefert AST (SampleSelector mit filter: Option<FilterExpression>
+ metadata: Vec<MetadataExpression>). Recursive-descent-Parser
mit allen 6 Vergleichs-Operatoren (=,!=,<,<=,>,>=), AND/OR-Logik,
Parentheses, Integer/String/Boolean-Literale, dotted Field-Path,
sample_state/view_state/instance_state-Metadata mit allen Werten.
Tests: crates/web/src/sample_selector.rs::tests::{parses_simple_equality_filter, parses_inequality_with_string_literal, parses_dotted_field_path, parses_and_conjunction, parses_or_with_parenthesis, parses_metadata_only_expression, parses_filter_plus_metadata, parses_all_three_metadata_kinds, comparison_operators_full_coverage, boolean_literal_supported, rejects_unknown_metadata_key, rejects_unknown_metadata_value, rejects_unbalanced_parenthesis, rejects_trailing_garbage} (14).
Status: done
§8 REST PSM
§8.1 Formats
Spec: §8.1, S. 59 — Content-Type application/dds-web+xml
(Mandatory) als XML-Format; weitere Formate optional. Volltextsuche im
PDF (§8.2, §8.3.5 Tab 7/8) findet keine zweite Repräsentation — DDS-WEB
1.0 definiert kein JSON.
Repo: crates/web/src/representation.rs::{ContentType, CONTENT_TYPE_DDS_WEB_XML}. Der Konstantenwert selbst war bis zu diesem
Stand codebase-weit falsch geschrieben (application/zerodds-web+xml,
sechs Dateien) — gemessener Bug, kein Repräsentations-Fehlen.
Tests: crates/web/src/representation.rs::tests::content_type_constant.
Status: done
§8.2 Representations
Spec: §8.2, S. 60-61 — Vier Representation-Kinds: QoS, Type, Data, Entity (mit XML-Element-Tags pro Kind).
Repo: crates/web/src/representation.rs::{RepresentationKind, element_name}.
Tests: crates/web/src/representation.rs::tests::element_names_match_spec_tab_6,
crates/web/src/representation.rs::tests::types_representation_uses_dds_xtypes_root.
Status: done — Element-Tag-Registry in crates/web/src/representation.rs; die XML-Bodies (application/dds-web+xml) schreibt crates/webdds-bridge (gateway.rs, contained_entities.rs).
§8.3.1 Mapping zu Resource URIs
Spec: §8.3.1, S. 62 Tab 4 — alle URI-Patterns mit Prefix
/dds/rest1.
Repo: crates/web/src/rest.rs::{REST_PREFIX, RestRoute, parse_route, RestMethod} mit allen 30+ Routes.
Tests: crates/web/src/rest.rs::tests::{rest_prefix_constant_matches_spec, missing_prefix_yields_error, create_application_route, get_applications_route, delete_application_route_extracts_app_name, create_participant_route_extracts_app_name, delete_participant_route_extracts_app_and_participant, data_writer_write_route_extracts_4_segments, data_reader_read_route_extracts_4_segments, qos_profile_routes_extract_lib_and_profile_name, waitset_routes_extract_app_and_waitset, topic_publisher_subscriber_create_routes, unknown_paths_yield_unknown_variant, types_routes_match_root_pattern} (14).
Status: done
§8.3.2 Mapping zu HTTP-Methods + Status-Codes
Spec: §8.3.2, S. 62-64 — Method-Mapping (POST/PUT/GET/DELETE) + Status-Codes (201, 204, 200, 409, 422, 404, 401, 403, 500).
Repo: crates/web/src/status.rs::{ReturnStatus, http_status_for}.
Tests: crates/web/src/status.rs::tests::{ok_maps_to_201_for_post_create, ok_maps_to_204_for_delete, ok_maps_to_200_for_get, ok_maps_to_204_for_put_update, object_already_exists_maps_to_409, invalid_input_maps_to_422, invalid_object_maps_to_404, access_denied_maps_to_401, permissions_error_maps_to_403, generic_service_error_maps_to_500, dds_error_maps_to_500} (11).
Status: done
§8.3.3 Komplett-Mapping (Tab 5)
Spec: §8.3.3, S. 64-67 — Tab 5, “Complete mapping of WebDDS PIM operations to REST methods”: 48 Zeilen, je Operation eine HTTP-Methode und eine URI. Dazu der Satz über der Tabelle: “the HEAD HTTP method shall be supported on the same URIs as the GET method. The HEAD method shall behave identically to the GET method except for it shall return no body.”
Repo: crates/web/src/rest.rs::RestRoute (47 Routen-Varianten +
Unknown), crates/web/src/rest.rs::parse_route,
crates/web/src/rest.rs::canonical_segment.
Tests: die Tabelle steht als TABLE_5 Zeile für Zeile im Test
(Operation, Methode, URI, aus dem PDF übernommen) und wird von fünf Tests
ausgewertet:
crates/web/src/rest.rs::tests::table_5_every_pim_operation_has_a_route
(keine Zeile fällt auf RestRoute::Unknown durch),
crates/web/src/rest.rs::tests::table_5_rows_do_not_collide (48 Zeilen
ergeben 48 verschiedene Routen — zwei Operationen dürfen nicht auf derselben
Variante landen, sonst wäre das Mapping mehrdeutig),
crates/web/src/rest.rs::tests::table_5_alternate_segment_spellings_route_identically
(Tab 5 schreibt dieselbe Sammlung an zwei Stellen verschieden —
domain_participants gegen participants, datawriters/datareaders gegen
data_writers/data_readers; beide Schreibweisen ergeben dieselbe Route),
crates/web/src/rest.rs::tests::canonicalisation_is_positional_not_global
(die Vereinheitlichung greift nur in den Sammlungs-Slots, eine Application
namens participants bleibt ein Name),
crates/web/src/rest.rs::tests::head_behaves_like_get_on_the_same_uris
(jede GET-Zeile der Tabelle routet unter HEAD identisch).
Befund aus dem Testschreiben: Die Tabelle war beim Anlegen dieses Tests
nicht vollständig abgebildet. 23 der 48 Zeilen fielen auf
RestRoute::Unknown durch — die gesamte Update/Delete/Get-Ebene für
Waitsets, registrierte Typen, Topics, Publisher, Subscriber, DataWriter und
DataReader sowie Publisher::create_datawriter und
Subscriber::create_datareader. Zusätzlich war RestMethod::Head zwar als
Enum-Variante vorhanden, wurde aber von parse_route nirgends behandelt, so
dass jede GET-URI unter HEAD auf Unknown fiel. Beides ist mit diesem Stand
gebaut: die 23 fehlenden Routen sind implementiert, HEAD routet wie GET, und
die zweite Schreibweise der Sammlungssegmente wird angenommen.
Status: done
§8.3.4 Object-Representations (Tab 6)
Spec: §8.3.4, S. 68-69 Tab 6 — XML-Element-Tag-Registry.
Repo: crates/web/src/representation.rs::element_name.
Tests: crates/web/src/representation.rs::tests::element_names_match_spec_tab_6.
Status: done
§8.3.5 HTTP-Headers (Tab 7+8)
Spec: §8.3.5, S. 70 — Required Request-/Response-Headers (Accept, Content-Length, Content-Type, Cache-Control, OMG-DDS-API-Key, Authentication-Info, Date, Expires, Location, Last-Modified).
Repo: crates/web/src/headers.rs::{RequestHeaders, ResponseHeaders, REQUEST_API_KEY, validate_required}.
Repo (Daemon): die Library-Typen allein genügten nicht — der Daemon
rief validate_required bis zu diesem Stand nirgends auf, ein
Request ohne Accept (oder mit einem, das keine dieser
Repräsentationen nennt) wurde trotzdem bedient. crates/webdds-bridge/ src/rest_service.rs::check_representation_headers prüft jetzt vor
jeder REST-Operation: fehlender Accept → 400, ein Accept, das
application/dds-web+xml (oder ein passendes */*/application/*)
nicht nennt → 406, ein Content-Type, der etwas anderes nennt → 415.
Tests: crates/web/src/headers.rs::tests::{request_api_key_constant_matches_spec, validate_required_passes_with_all_three_headers, missing_api_key_rejected, missing_accept_rejected, missing_cache_control_rejected, response_headers_default_is_empty} (6);
crates/webdds-bridge/src/rest_service.rs::tests::{missing_accept_header_is_400, accept_header_naming_an_unserved_representation_is_406, accept_header_wildcards_are_acceptable, content_type_naming_an_unserved_representation_is_415} (4).
Status: done
§8.4 SOAP PSM
§8.4 Simplified SOAP Platform
Spec: §8.4, S. 71+ — Optional SIMPLE-WSDL-SOAP.
Repo: crates/zerodds-soap/src/{envelope,addressing,fault,mtom, security,wsdl}.rs (SOAP-Envelope-Codec, WS-Addressing, MTOM,
WS-Security-Hooks, WSDL-Emitter; 1218 SLOC).
Repo (Daemon): crates/webdds-bridge/src/soap_service.rs dispatcht
den SOAP-Envelope-Body (ein DDS-WEB-Objektmodell-Op-Name wie
CreateApplication/WriteSample, dieselbe Operationsmenge wie REST)
gegen denselben Gateway — REST und SOAP sind Schwester-Bindings
eines Objektmodells, kein zweites Protokoll. crates/webdds-bridge/src/wsdl_bridge.rs
generiert die WSDL sowohl für dieses feste Op-Set als auch für jedes
config-benannte IDL-interface (via zerodds_lang_rules::adapter::interface
— die im Plan §3 verlangte IDL→Operation-Brücke).
Eine Business-Interface-Operation ist WSDL-beschrieben und
dispatchbar: crate::rpc_dispatch baut, pro konfiguriertem interface,
DDS-RPC 1.0 §7.5.1.1s Basic-Mapping-Typen dynamisch — ein
Request/Reply-Topic-Paar pro Interface (service_name =
qualifizierter Interface-Name, z. B. demo_Calculator), ein
Call/Return-Union über alle Operationen (Discriminator =
operation_hash), pro nicht-oneway-Operation ein Result-Union
(dds::RETCODE_OK → Out-Struct, je eine raises-Exception als
weiterer Case, Discriminator = exception_hash). Jeder synthetisierte
Wire-Typ ist @final (klassisches CDR, kein DHEADER) — dieselbe Form,
die zerodds-idlc generate --lang rust/--lang cpp für ein
interface selbst erzeugen
(crates/lang-rules/src/emit/interface_rust.rs), und dieselben
operation_hash/exception_hash/Struct-Namensfunktionen aus
zerodds_lang_rules::adapter::interface, die diese Generatoren selbst
aufrufen — keine parallele, nur zufällig gleich rechnende Ableitung.
Eine Operation, die kein konfiguriertes Interface deklariert (und
keine DDS-WEB-Objektmodell-Operation ist), liefert weiterhin einen
benannten env:Receiver-Fault statt eines stillen No-Op; eine
deklarierte raises-Exception ebenso, benannt mit ihren Feldern
(OperationException), nicht ein leerer oder falscher Erfolg.
Interop-Beleg: crates/webdds-bridge/tests/soap_rpc_e2e.rs ruft
nicht gegen einen handgeschriebenen Stand-in, sondern gegen einen
echten, mit zerodds_lang_rules::generate::source(.., Language::Rust, ..) zur Build-Zeit generierten Replier
(zerodds-rpc-rust-e2es Calculator-Binding, module demo { exception DivByZero; @DDSService interface Calculator { … } } — Rückgabewert,
raises, oneway, attribute, inout, jede DDS-RPC-1.0-§7.5.1.1-Form
in einem Interface) auf einem zweiten, unabhängigen
DomainParticipant, über echtes RTPS/UDP.
Tests: crates/zerodds-soap/src/envelope.rs::tests::{build_simple_body_only, build_with_header, parse_round_trip, parse_missing_envelope_rejected, parse_missing_body_rejected, build_includes_namespace_declaration};
crates/zerodds-soap/src/addressing.rs::tests::{to_and_action_are_emitted, message_id_and_relates_to_round_trip, reply_to_and_fault_to_emit_address_subelement, ns_constant_is_w3c_2005_08};
crates/zerodds-soap/src/fault.rs::tests::{fault_codes_have_correct_qnames, fault_xml_contains_required_elements, fault_with_detail_includes_detail_block, fault_reason_is_xml_escaped};
crates/zerodds-soap/src/wsdl.rs::tests::{wsdl_11_emits_definitions_root, wsdl_20_emits_description_root, wsdl_11_includes_all_operation_messages, wsdl_11_binding_uses_soap_http_transport, wsdl_20_binding_targets_soap_protocol, endpoint_address_is_included};
crates/zerodds-soap/src/security.rs::tests::{username_token_text_password_emits_correct_type, username_token_digest_uses_digest_uri, timestamp_emits_created_and_expires, x509_token_default_uses_v3_value_type, password_type_uris_match_spec};
crates/zerodds-soap/src/mtom.rs::tests::{package_starts_with_xop_root_part, package_includes_attachment_part, package_terminates_with_closing_boundary, xop_include_namespace_is_correct};
crates/webdds-bridge/src/rpc_dispatch.rs::tests::{ finds_a_declared_operation_across_flattened_services, an_operation_with_no_params_gets_a_dummy_member, every_synthetic_type_is_final_not_appendable, builds_one_interface_wide_call_and_return_union};
crates/webdds-bridge/tests/soap_rpc_e2e.rs::{ soap_add_reaches_a_generated_replier_and_returns_its_reply, soap_div_by_zero_surfaces_the_declared_exception, soap_call_to_an_unconfigured_operation_is_a_named_fault} — die ersten
zwei gegen den echten, generierten Replier;
crates/webdds-bridge/tests/daemon_e2e.rs::soap_write_sample_arrives_at_an_independent_participant
(ein WriteSample, dessen <data> das Sample als Elemente in der
REST-Darstellung trägt, kommt bei einem zweiten, unabhängigen
DomainParticipant über RTPS/UDP an; vorher auf dem Linux-Testhost gemessen:
env:Receiver „malformed XML body: no root element“, weil der
Feldleser die Kinder von <data> als Liste nahm),
crates/webdds-bridge/src/xmlfields.rs::tests::a_child_subtree_comes_back_as_xml.
Status: done — SOAP-PSM als Differenzierungs-Feature für Java-Enterprise-Bestand voll abgedeckt.
Audit-Status
16 done / 0 partial / 0 open / 0 blocked / 2 n/a (informative).
Test-Lauf:
cargo test -p zerodds-web— 81 Tests grün, 0 failed. Module:access_control,bridge,headers,model,representation,rest,sample_selector,session,status, plusscope_tests(§1) und die Tab-5-Tests inrest(§8.3.3).cargo test -p zerodds-soap --lib— 37 Tests grün. Module:addressing,envelope,fault,mtom,security,wsdl.cargo test -p zerodds-webdds-bridge— 110 Unit- + 9 E2E-Tests grün (der Daemon, der die zwei Libraries zusammenführt und tatsächlich bedient; siehecrates/webdds-bridge/README.md).
Offene Punkte: keine.