use std::collections::HashMap;
use super::emit;
use super::walk::visit_vast_elements;
use crate::parse::VastDocument;
use crate::{DetectedVersion, Issue, Severity, ValidationContext};
pub fn check(
doc: &VastDocument,
_version: &DetectedVersion,
ctx: &ValidationContext,
issues: &mut Vec<Issue>,
) {
check_wrapper_depth(doc, ctx, issues);
check_ad_sequence(doc, ctx, issues);
check_singular_children(doc, ctx, issues);
}
#[rustfmt::skip]
const SINGULAR_CHILDREN: &[(&str, &str, &str)] = &[
("InLine", "AdSystem", "<InLine> has more than one <AdSystem>; the spec allows exactly one"),
("InLine", "AdTitle", "<InLine> has more than one <AdTitle>; the spec allows exactly one"),
("InLine", "AdServingId", "<InLine> has more than one <AdServingId>; the spec allows exactly one"),
("InLine", "Creatives", "<InLine> has more than one <Creatives>; the spec allows exactly one"),
("InLine", "Advertiser", "<InLine> has more than one <Advertiser>; the spec allows at most one"),
("InLine", "Description", "<InLine> has more than one <Description>; the spec allows at most one"),
("InLine", "Survey", "<InLine> has more than one <Survey>; the spec allows at most one"),
("InLine", "Expires", "<InLine> has more than one <Expires>; the spec allows at most one"),
("InLine", "Pricing", "<InLine> has more than one <Pricing>; the spec allows at most one"),
("InLine", "ViewableImpression", "<InLine> has more than one <ViewableImpression>; the spec allows at most one"),
("InLine", "AdVerifications", "<InLine> has more than one <AdVerifications>; the spec allows at most one"),
("InLine", "Extensions", "<InLine> has more than one <Extensions>; the spec allows at most one"),
("Wrapper", "AdSystem", "<Wrapper> has more than one <AdSystem>; the spec allows exactly one"),
("Wrapper", "VASTAdTagURI", "<Wrapper> has more than one <VASTAdTagURI>; the next chain hop is ambiguous"),
("Wrapper", "Creatives", "<Wrapper> has more than one <Creatives>; the spec allows at most one"),
("Wrapper", "Pricing", "<Wrapper> has more than one <Pricing>; the spec allows at most one"),
("Wrapper", "ViewableImpression", "<Wrapper> has more than one <ViewableImpression>; the spec allows at most one"),
("Wrapper", "AdVerifications", "<Wrapper> has more than one <AdVerifications>; the spec allows at most one"),
("Wrapper", "Extensions", "<Wrapper> has more than one <Extensions>; the spec allows at most one"),
("Linear", "Duration", "<Linear> has more than one <Duration>; the spec allows exactly one"),
("Linear", "MediaFiles", "<Linear> has more than one <MediaFiles>; the spec allows at most one"),
("Linear", "VideoClicks", "<Linear> has more than one <VideoClicks>; the spec allows at most one"),
("Linear", "TrackingEvents", "<Linear> has more than one <TrackingEvents>; the spec allows at most one"),
("Linear", "Icons", "<Linear> has more than one <Icons>; the spec allows at most one"),
("Linear", "AdParameters", "<Linear> has more than one <AdParameters>; the spec allows at most one"),
("Creative", "Linear", "<Creative> has more than one <Linear>; a creative carries one ad format"),
("Creative", "NonLinearAds", "<Creative> has more than one <NonLinearAds>; a creative carries one ad format"),
("Creative", "CompanionAds", "<Creative> has more than one <CompanionAds>; a creative carries one ad format"),
("Creative", "CreativeExtensions", "<Creative> has more than one <CreativeExtensions>; the spec allows at most one"),
("NonLinear", "AdParameters", "<NonLinear> has more than one <AdParameters>; the spec allows at most one"),
("NonLinear", "NonLinearClickThrough","<NonLinear> has more than one <NonLinearClickThrough>; the spec allows at most one"),
("Companion", "AdParameters", "<Companion> has more than one <AdParameters>; the spec allows at most one"),
("Companion", "AltText", "<Companion> has more than one <AltText>; the spec allows at most one"),
("Companion", "CompanionClickThrough","<Companion> has more than one <CompanionClickThrough>; the spec allows at most one"),
("Companion", "TrackingEvents", "<Companion> has more than one <TrackingEvents>; the spec allows at most one"),
("Companion", "CreativeExtensions", "<Companion> has more than one <CreativeExtensions>; the spec allows at most one"),
("VideoClicks", "ClickThrough", "<VideoClicks> has more than one <ClickThrough>; the click destination is ambiguous"),
("Icon", "IconClicks", "<Icon> has more than one <IconClicks>; the spec allows at most one"),
("Verification", "TrackingEvents", "<Verification> has more than one <TrackingEvents>; the spec allows at most one"),
("Verification", "VerificationParameters","<Verification> has more than one <VerificationParameters>; the spec allows at most one"),
];
fn check_singular_children(doc: &VastDocument, ctx: &ValidationContext, issues: &mut Vec<Issue>) {
let Some(vast) = doc.vast_root() else { return };
visit_vast_elements(vast, "/VAST", &mut |node, loc| {
if !SINGULAR_CHILDREN.iter().any(|(p, _, _)| *p == node.name) {
return;
}
let mut counts: HashMap<&str, usize> = HashMap::new();
for child in &node.children {
*counts.entry(child.name.as_str()).or_insert(0) += 1;
}
for (parent, child, message) in SINGULAR_CHILDREN {
if *parent != node.name {
continue;
}
if counts.get(child).copied().unwrap_or(0) > 1 {
emit(
ctx,
issues,
"VAST-2.0-duplicate-singular-element",
Severity::Error,
message,
Some(format!("{}/{}", loc.path(), child)),
"IAB VAST 4.2 XSD",
Some(node),
);
}
}
});
}
fn check_wrapper_depth(_doc: &VastDocument, ctx: &ValidationContext, issues: &mut Vec<Issue>) {
if ctx.wrapper_depth > ctx.max_wrapper_depth {
emit(
ctx,
issues,
"VAST-2.0-wrapper-depth",
Severity::Error,
"Wrapper chain depth exceeds the maximum allowed limit",
Some("/VAST".to_owned()),
"IAB VAST 4.x §2.3",
None,
)
}
}
fn check_ad_sequence(doc: &VastDocument, ctx: &ValidationContext, issues: &mut Vec<Issue>) {
let Some(vast) = doc.vast_root() else { return };
let ads: Vec<_> = vast.children_named("Ad").collect();
if ads.len() < 2 {
return;
}
let with_seq = ads.iter().filter(|a| a.attr("sequence").is_some()).count();
if with_seq > 0 && with_seq < ads.len() {
emit(
ctx, issues,
"VAST-2.0-ad-sequence",
Severity::Warning,
"Multiple <Ad> elements present but sequence attribute is missing on some — ambiguous ordering",
Some("/VAST".to_owned()),
"IAB VAST 2.0 §2.2",
None,
)
}
}