0123

Validate Against the Schema

xml
xsd
schema-validation
getlasterrortext

Validate Against the Schema

Your company receives order confirmations from a partner portal as XML. Last month a malformed message crashed the nightly import halfway through; last week a message that parsed fine but broke the agreed structure imported garbage instead. The new rule: every payload passes a gate before any import logic sees it — and when a message is rejected, support wants to read why in the log, not just "invalid".

The contract

Inbound messages must obey this XSD (namespace urn:tryal:orders):

<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:tryal:orders" elementFormDefault="qualified">
  <xsd:element name="Order">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="OrderNo" type="xsd:string" />
        <xsd:element name="Quantity" type="xsd:int" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>
</xsd:schema>

A valid message looks like this — and the sender may just as well declare the namespace under a prefix (<po:Order xmlns:po="urn:tryal:orders">…) instead of as the default; both are the same document:

<Order xmlns="urn:tryal:orders"><OrderNo>SO-1001</OrderNo><Quantity>3</Quantity></Order>

The partner guarantees that whenever a message parses as XML at all, its root is an Order element in the namespace urn:tryal:orders. What still goes wrong — and what your gate must catch — is payloads that do not parse as XML at all, and parseable orders whose content breaks the schema: an element the schema does not allow, a missing required element, or a Quantity that is not an integer.

Requirements

The starter ships two objects. Keep the enum "Xml Verdict" (values Valid, NotWellFormed, SchemaInvalid) and the given SchemaText() / SchemaNamespace() procedures exactly as they are — the tests bind to the enum by name, and rewriting the embedded schema changes what "valid" means.

Implement Validate in the codeunit named "Inbound Xml Validation":

procedure Validate(XmlPayload: Text; var Diagnostic: Text): Enum "Xml Verdict"

Rules:

  1. If XmlPayload is not well-formed XML — plain text, an empty string, a mismatched closing tag — return NotWellFormed and leave Diagnostic empty.
  2. If it is well-formed but breaks the schema, return SchemaInvalid and put the violation text into Diagnostic. That text must name the offending piece: the unexpected element's name, the missing element's name, or the rejected Quantity value. The message a schema validator produces does this on its own — you are not expected to compose these sentences yourself.
  3. If it satisfies the schema, return Valid and leave Diagnostic empty.
  4. Validate must never raise an error, whatever the payload contains.
  5. Diagnostic describes only the current call: whatever the caller's variable held before the call — including the text of an earlier failed validation — must be gone afterwards.

What the tests check

Randomized valid orders — with the namespace declared as default and under a prefix — must come back Valid with an empty Diagnostic. Generated plain text, an empty payload and a mismatched closing tag must come back NotWellFormed with an empty Diagnostic. Schema violations are graded with randomized offenders: an extra element with a generated name (Diagnostic must contain that name), an order missing Quantity (Diagnostic must contain Quantity), and a Quantity holding generated alphabetic text (Diagnostic must contain exactly that text). Two tests validate a broken order and then a second payload through the same Diagnostic variable — once a valid order, once plain text — and the stale text must not survive the second call in either. Verdicts are compared exactly, and an error raised by any Validate call fails that test.

Pick object IDs in the range 50100–50199 and reference other objects by name, never by ID.

Learn More

Hint 1
Well-formedness and schema validity are different questions with different failure channels: a parse attempt on the XmlDocument type answers the first politely with a Boolean, while a schema check reports a violation by raising an error. Your gate's job is to catch that error and turn it into a verdict plus a diagnostic — never to let it escape.
Hint 2
You don't have to build the validator: the System Application ships a small XML validation codeunit whose Try procedure takes the XML text, the schema text and the schema's namespace. Called in a condition, it returns false instead of raising, and the violation message is then waiting in GetLastErrorText().
Hint 3
The shape of the solution: clear Diagnostic first; XmlDocument.ReadFrom returning false means NotWellFormed; then ClearLastError() and codeunit "Xml Validation".TryValidateAgainstSchema(XmlPayload, SchemaText(), SchemaNamespace()) — false means SchemaInvalid with GetLastErrorText() as the Diagnostic, otherwise Valid. Without the up-front clears, text from an earlier call can leak into a later verdict.
ALBusiness Central 28.5
Press Compile to check your code compiles — Submit runs the tests.
The code editor is desktop-only
Open this problem on a computer to write and run code. Reading the description, tests and discussion works fine here.