Skip to content

New Module: CTV VAST Enrichment#4673

Open
przemkaczmarek wants to merge 12 commits into
masterfrom
ctv
Open

New Module: CTV VAST Enrichment#4673
przemkaczmarek wants to merge 12 commits into
masterfrom
ctv

Conversation

@przemkaczmarek
Copy link
Copy Markdown
Collaborator

@przemkaczmarek przemkaczmarek commented Feb 2, 2026

This module provides comprehensive VAST processing for CTV ads:

module.go: PBS module entry point (Builder + HandleRawBidderResponseHook)
pipeline.go: Main orchestration and BuildVastFromBidResponse entrypoint
handler.go: HTTP handler for VAST requests
types.go: Type definitions, interfaces (BidSelector, Enricher, Formatter)
config.go: PBS-style layered configuration (host/account/profile merge)
model/: VAST XML structures, parser, and helper functions
select/: Bid selection logic with SINGLE/TOP_N strategies
enrich/: VAST enrichment with VAST_WINS collision policy
format/: VAST XML formatting for GAM_SSU receiver

Features:

Bid selection by price with deal prioritization
VAST XML parsing and skeleton generation
Metadata enrichment (pricing, advertiser, categories, debug)
Pod support with sequence numbering
VAST_WINS policy — preserves existing VAST values, does not overwrite
ChangeSet/UpdateBids mutation pattern (PBS-compliant, same as ortb2blocking)
clearInnerXML() fix preventing duplicate XML elements during serialization
Golden file tests for XML output

Links:
https://docs.google.com/document/d/11M6zyLfgbmPdZeg41xk0T_5n12WbGeSXqqEN6cKyXms/edit?tab=t.0#heading=h.b23io1ujc2es
https://docs.google.com/document/d/1KrEQk7gOZj0pUJHwVb7850awbQdiE8EfzciwlfvtSQQ/edit?tab=t.0
https://docs.google.com/document/d/1z9LpL6pQhNXq3kpG-f0hs9h-2JWaPfhExb_VuNVSsOg/edit?tab=t.xquqb6kq1v9e#heading=h.e5mvrqiytc2u
https://docs.google.com/document/d/1Fkwym4EREfOdaTD8SHUODKwlj-vWpZWQa0eGUToyzPU/edit?tab=t.0#heading=h.iaqb0q3s18nj

This module provides comprehensive VAST processing for CTV ads:

- vast.go: Main orchestration and BuildVastFromBidResponse entrypoint
- handler.go: HTTP handler for VAST requests
- types.go: Type definitions, interfaces (BidSelector, Enricher, Formatter)
- config.go: PBS-style layered configuration (host/account/profile merge)
- model/: VAST XML structures, parser, and helper functions
- select/: Bid selection logic with SINGLE/TOP_N strategies
- enrich/: VAST enrichment with VAST_WINS collision policy
- format/: VAST XML formatting for GAM_SSU receiver

Features:
- Bid selection by price with deal prioritization
- VAST XML parsing and skeleton generation
- Metadata enrichment (pricing, advertiser, categories, debug)
- Pod support with sequence numbering
- Golden file tests for XML output
@bsardo bsardo added the module label Feb 3, 2026
@bsardo bsardo changed the title add CTV VAST module for Connected TV ad processing New Module: CTV VAST Enrichment Feb 3, 2026
…Builder pattern

- Rename vast.go to pipeline.go for clarity
- Add module.go with Builder() and HandleRawBidderResponseHook()
- Add module_test.go with comprehensive tests
- Update README.md and README_EN.md with new structure
- Fix import paths for new module location
- Module now follows ortb2blocking/rulesengine patterns
- Change package name from 'vast' to 'ctv_vast_enrichment'
- Register module in modules/builder.go
- Fix ChangeSet mutation logic to use UpdateBids pattern
- Add 'vast' import alias in subpackages (enrich, select, format)
- Update tests to apply ChangeSet mutations before assertions
…\n\n- Add module registration section (modules/builder.go)\n- Replace YAML enabled_modules with proper host_execution_plan JSON config\n- Document ChangeSet/UpdateBids mutation pattern\n- Document clearInnerXML() XML serialization fix\n- Add package naming note (ctv_vast_enrichment + vast alias)\n- Add step-by-step PBS integration instructions
Comment on lines +361 to +369
// boolPtr is a helper function to create a pointer to a bool value.
func boolPtr(b bool) *bool {
return &b
}

// float64Ptr is a helper function to create a pointer to a float64 value.
func float64Ptr(f float64) *float64 {
return &f
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use ToPtr from ptrutil.go instead of rolling you're own function
https://github.com/prebid/prebid-server/blob/master/util/ptrutil/ptrutil.go#L3

mergedCfg := MergeCTVVastConfig(&m.hostConfig, accountCfg, nil)

// Check if module is enabled
if mergedCfg.Enabled != nil && !*mergedCfg.Enabled {
Copy link
Copy Markdown
Contributor

@ccorbo ccorbo Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: this wont stop module from processing by default when enabled is nil, only if its explicitly false.

 if mergedCfg.Enabled == nil || !*mergedCfg.Enabled { return result, nil }

result, err := BuildVastFromBidResponse(ctx, bidRequest, bidResponse, h.Config, h.Selector, h.Enricher, h.Formatter)
if err != nil {
// Log error but still try to return valid VAST
// result.VastXML should contain no-ad VAST
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add actual logging here if error

// SelectedBid represents a bid that was selected for inclusion in the VAST response.
type SelectedBid struct {
// Bid is the OpenRTB bid object.
Bid openrtb2.Bid
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be a pointer to the underlying bid right?

Comment on lines +270 to +274
if cfg.SelectionStrategy != "" {
rc.SelectionStrategy = SelectionStrategy(cfg.SelectionStrategy)
} else {
rc.SelectionStrategy = SelectionStrategy(DefaultSelectionStrategy)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation on SelectionStrategy?

Comment on lines +277 to +281
if cfg.CollisionPolicy != "" {
rc.CollisionPolicy = CollisionPolicy(cfg.CollisionPolicy)
} else {
rc.CollisionPolicy = CollisionPolicy(DefaultCollisionPolicy)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validation before setting on CollisionPolicy?

}

// If we made changes, set mutation via ChangeSet
if changesMade {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modfiedBids is only being used if enrichment actually happens, there could be a case where we don't have any changes and we build the modifiedBids slice then effectively throw it out.

Can we only build modifiedBids if enrichment actually happens?

)

// Vast represents the root VAST XML element.
type Vast struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: instead of rolling our own VAST struct, we could utilize an opensource library ie https://github.com/rs/vast

cc: @bsardo

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, if we are considering using an open source library, we would use one that:

  1. supports the latest VAST version
  2. has strong adoption
  3. is actively maintained

The library you referenced does not appear to support VAST 4. I'm not sure if that's a deal breaker.

I do also see https://github.com/haxqer/vast which could be an option. We should take a closer look at these libraries. Let's discuss at the PBS-Go Engineering meeting next week.

Copy link
Copy Markdown
Collaborator Author

@przemkaczmarek przemkaczmarek May 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked Go libraries specifically. I couldn’t find a Go VAST library that supports the latest VAST 4.3 version, has strong adoption, and is actively maintained.

The closest option seems to be joeig/go-vast, because it supports parsing, modifying, and building VAST files and has a relatively recent release. However, it only supports VAST 4.2 and has low adoption.

haxqer/vast also supports VAST 4.2 and has more GitHub stars/forks, but it does not have published releases and still has TODOs around tracking events.

So for PBS-Go, I think we probably should not depend on an external Go library unless VAST 4.2 is acceptable. Otherwise, we may need to keep our own structs or use one of these libraries only as a base and extend it to cover the missing VAST 4.3 parts.

@bsardo @ccorbo

Comment thread modules/prebid/ctv_vast_enrichment/module.go
- Replace custom boolPtr/float64Ptr helpers with ptrutil.ToPtr from util/ptrutil
- Remove TestBoolPtr/TestFloat64Ptr tests (covered by ptrutil package)
- Fix enabled check: treat nil as disabled (explicit opt-in required), use IsEnabled()
- Add isValidSelectionStrategy and isValidCollisionPolicy validation with fallback to default for unknown values
- Add CollisionVastWins constant ('VAST_WINS') to CollisionPolicy
- Change SelectedBid.Bid from value type to pointer (*openrtb2.Bid)
- Add glog error logging in handler.go BuildVastFromBidResponse error path
- Lazy-allocate modifiedBids slice only when enrichment actually occurs (avoid unnecessary allocation when no bids are enriched)
- Update README.md and README_EN.md to reflect all above changes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants