https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43694 Bug ID: 43694 Summary: Koha::REST::V1::to_xml emits child elements in random order, producing schema-invalid XML Initiative type: --- Sponsorship --- status: Product: Koha Version: Main Hardware: All OS: All Status: NEW Severity: major Priority: P5 - low Component: REST API Assignee: koha-bugs@lists.koha-community.org Reporter: minhpq@tinhvan.com QA Contact: tomascohen@gmail.com CC: tomascohen@gmail.com Target Milestone: --- Koha::REST::V1::to_xml builds XML by walking Perl hash keys: sub to_xml { # V1.pm line 240 my $root_key = ( keys %$json )[0]; # line 244 ... sub _json_to_xml { # line 261 foreach my $key ( keys %$json ) { # line 265 Perl randomises hash key order per process (PERL_PERTURB_KEYS, default since 5.18), so the order of child elements differs from one run to the next. The ISO 18626 schema uses xs:sequence, not xs:all, so element order is significant: a message whose children are emitted in the wrong order is invalid and a conformant receiver rejects it at parse time. The same applies to the root element: line 244 takes ( keys %$json )[0], which is only correct because these payloads happen to have exactly one top-level key. It is fragile for the same reason. How to reproduce: Run this five times on a Koha instance and compare the output lines. use Modern::Perl; use XML::LibXML; use Koha::REST::V1; my $json = { supplyingAgencyMessage => { header => { supplyingAgencyId => { agencyIdType => 'ISIL', agencyIdValue => 'X' }, requestingAgencyId => { agencyIdType => 'ISIL', agencyIdValue => 'Y' }, timestamp => '2026-09-30T00:00:00Z', requestingAgencyRequestId => '1', supplyingAgencyRequestId => '2', }, messageInfo => { reasonForMessage => 'StatusChange' }, statusInfo => { status => 'Loaned', lastChange => '2026-09-30T00:00:00Z' }, }, }; my $doc = XML::LibXML->new->parse_string( Koha::REST::V1::to_xml($json) ); my $root = $doc->documentElement; say join( ', ', map { $_->nodeName } $root->findnodes('./*') ); Observed on Koha 26.05.02, Perl v5.38.2 — five consecutive runs: header, statusInfo, messageInfo header, messageInfo, statusInfo messageInfo, header, statusInfo statusInfo, header, messageInfo messageInfo, statusInfo, header The schema requires header, messageInfo, statusInfo, deliveryInfo, returnInfo in that order (ISO-18626-v1_2.xsd), so only the second run above is valid. Nested elements are scrambled the same way: header itself must be supplyingAgencyId, requestingAgencyId, multipleItemRequestId, timestamp, requestingAgencyRequestId, supplyingAgencyRequestId, requestingAgencyAuthentication. Why this has not been noticed: 1. ISO 18626 testing so far appears to have been Koha-to-Koha. The inbound side, Koha::REST::V1::parse_xml/_parse_node, builds a hash and therefore does not care about element order, so two Koha instances understand each other whatever order they emit. 2. Koha does validate these payloads — Koha::ILL::ISO18626::is_invalid resolves the swagger definition and calls $schema->validate($json). But that validates the JSON structure, and JSON objects are unordered by definition, so the check passes while the XML that goes out on the wire is invalid. The validation gives false confidence here rather than catching the problem. This makes the defect worse than an ordinary bug: it is not deterministic. Testing an integration once and seeing it work says nothing, because the next message may be ordered differently. How it was found: Interoperability testing between Koha 26.05.02 and a third-party ISO 18626 implementation written in .NET, generated directly from the official v1.2 XSD (https://illtransactions.org/schemas/ISO-18626-v1_2.xsd). The first supplyingAgencyMessage Koha sent was rejected at XML deserialisation; retrying produced a different failure position, which is what led to the hash ordering. Suggested fix: to_xml has no way to know the required order on its own, so it needs the order to come from somewhere. Two options that both avoid changing every caller: a. Derive the order from the OpenAPI definition that already describes each message. JSON::Validator keeps the properties in the order they appear in the spec file, so the swagger definition can supply the sequence. This also keeps the spec as the single source of truth. b. Let callers pass an explicit ordering, e.g. to_xml($json, { order => {...} }), and have the ISO 18626 code supply the sequences from the XSD. Whichever is chosen, it is worth adding a test that serialises a known payload and asserts the resulting XML validates against the official XSD — a JSON-level check cannot catch this class of bug. Related: bug 43674 (ISO 18626 messages are missing the mandatory ISO18626Message envelope), also in Koha::REST::V1. Fixing either one alone still leaves Koha unable to exchange ISO 18626 messages with a conformant implementation. -- You are receiving this mail because: You are watching all bug changes. You are the assignee for the bug.