DEV Community

Aleksander Sekowski
Aleksander Sekowski

Posted on

There Is No VAST 4.3 Schema, and the 4.4 Draft Accepts a Completely Empty Ad

If you validate VAST tags, you probably assume there is a schema behind the version number. For 4.3 there is not, and for the 4.4 draft there is one that accepts an ad containing nothing at all.

Both are checkable in about thirty seconds. Neither is a hypothetical.

The 4.3 schema is one byte

IAB Tech Lab publishes VAST in a public repo, with schemas under schemas/. Ask the API what is in the 4.3 file:

$ curl -s https://api.github.com/repos/InteractiveAdvertisingBureau/vast/contents/schemas/vast_4.3.xsd \
  | jq '.size'
1
Enter fullscreen mode Exit fullscreen mode

One byte. Pull it down and it is a single newline. The file was created, committed, and never filled in.

So when a tool says it validated your tag "against VAST 4.3," it did not mean against a 4.3 XSD, because none exists. In practice tools do one of three things: fall back to the 4.2 schema, check structure they encoded by hand from the specification PDF, or check nothing version-specific at all. All three are defensible. None of them is what most people picture.

This is worth knowing mostly because 4.3 is the current released version. The version people are told to target is the one with no machine-readable definition.

The 4.4 draft validates an empty wrapper

4.4 is a draft, and drafts are allowed to be rough. This one has a specific defect that is worth understanding because it is a good lesson in how XSD actually behaves.

Take the smallest wrapper ad you can write, one with no children whatsoever:

<?xml version="1.0" encoding="UTF-8"?>
<VAST xmlns="http://www.iab.com/VAST" version="4.4">
  <Ad id="empty">
    <Wrapper/>
  </Ad>
</VAST>
Enter fullscreen mode Exit fullscreen mode

Against the 4.4 draft schema:

$ xmllint --noout --schema vast_4.4.xsd ew.xml
ew.xml validates
Enter fullscreen mode Exit fullscreen mode

The same document, retargeted at 4.2:

$ xmllint --noout --schema vast_4.2.xsd ew42.xml
ew42.xml:4: element Wrapper: Schemas validity error : Element
'{http://www.iab.com/VAST}Wrapper': Missing child element(s).
Expected is ( {http://www.iab.com/VAST}AdSystem ).
ew42.xml fails to validate
Enter fullscreen mode Exit fullscreen mode

A wrapper with no <AdSystem>, no <VASTAdTagURI>, no <Impression>. It has no ad server to attribute, nowhere to go next, and nothing to track. Under the draft schema it is a valid ad.

Why it happens

Here is the content model in the draft:

<xs:complexType name="vastWrapper_type">
  <xs:choice minOccurs="0" maxOccurs="unbounded">
    <xs:element name="AdSystem"     type="vastAdSystem_type"/>
    <xs:element name="VASTAdTagURI" type="vastURIElement_type"/>
    <xs:element name="Impression"   type="vastImpression_type"/>
    ...
Enter fullscreen mode Exit fullscreen mode

xs:choice minOccurs="0" maxOccurs="unbounded" says: zero or more of any of these, in any order, any number of times. The per-element minOccurs and maxOccurs that would normally express "exactly one AdSystem, at least one Impression" are not there, and adding them would not help much, because a repeating choice group reasons about the group, not the branch.

So the model has thrown away three separate things at once: which children are required, how many of each are allowed, and what order they come in. <Wrapper/> satisfies it by taking the choice zero times. So does a wrapper with fourteen <VASTAdTagURI> elements.

The 4.2 schema does not have this problem because it uses xs:sequence with explicit occurrence constraints on each element, which is the ordinary way to say "one of these, then one or more of those."

The fix that looks right and is not

The instinct when you see an order-independent content model is to reach for xs:all. It exists precisely to say "all of these, in any order."

It does not work here, and it is worth knowing why before you suggest it to anyone.

In XSD 1.0, every particle inside xs:all is capped at maxOccurs="1". That is not a stylistic limit, it is in the language. VAST needs <Impression> to repeat, since a wrapper routinely carries several impression pixels from different parties. Model it with xs:all and the schema will reject the second <Impression>, turning a cardinality bug that is too loose into one that is too strict.

I suggested xs:all in the issue I filed, then tested it against libxml2 and had to retract it in a follow-up comment. The correct fix is the boring one: xs:sequence with real minOccurs and maxOccurs per element, which is what 4.2 already does and what the draft moved away from.

I filed this as issue #58 and opened PR #59 restoring required elements and ordering. Both are open at the time of writing. The patch is +22/-22 and keeps every IAB sample tag passing, which I checked by running the full sample set before and after.

The larger point about schema validation

Even a correct XSD would only get you part of the way, and this is the thing worth internalising if you are building tag QA.

A schema can express structure: which elements exist, how they nest, how many times, what type the text is. It cannot express most of what the VAST specification actually requires, because those requirements are conditional, cross-referential, or written in prose. A few examples:

  • apiFramework="VPAID" is deprecated as of VAST 4.1. Schema-legal, still a problem.
  • sequence on <Ad> is typed xs:integer, so sequence="0" and sequence="-1" validate. Nothing in the schema requires sequence values to be unique across a pod either, so two ads can claim the same slot.
  • A skipoffset larger than the creative's <Duration> is two valid fields that contradict each other.
  • An <Icon> positioned outside the player dimensions is arithmetic, not structure.
  • Wrapper chain depth limits are a runtime property of a document you have not fetched yet.

None of that is a schema's job. It means the schema check is a floor, not a ceiling, and "it validates" answers a narrower question than most people are asking when they say it.

This is why I ended up writing a linter rather than shipping an XSD. vastlint is open source, written in Rust, and its rules come from the specification prose as much as from the schemas, with each rule tracing back to the version and source it came from. The tradeoff is honest: rules extracted from prose require judgment and can be wrong, whereas a schema check cannot be wrong about the thing it checks. It just checks less than you need.

If you want to reproduce any of this

Everything above is one command each.

# the 4.3 placeholder
curl -s https://api.github.com/repos/InteractiveAdvertisingBureau/vast/contents/schemas/vast_4.3.xsd | jq .size

# the empty wrapper, against the draft schema
xmllint --noout --schema vast_4.4.xsd ew.xml
Enter fullscreen mode Exit fullscreen mode

For the prose-level rules, the IAB VAST validator guide sets out what spec compliance covers and where platform-specific behaviour takes over, and the tag tester will fetch a live tag and follow its wrapper chain so you can see the resolved document rather than the one you started with.

To be clear about affiliation: vastlint is independent and not an IAB Tech Lab product. The issue and the PR above are ordinary contributions to a public repo, filed because the bug was easier to fix than to work around.

Top comments (0)