diff --git a/modules/pubmatic/openwrap/adpod/adpod.go b/modules/pubmatic/openwrap/adpod/adpod.go index 91e1ff290eb..f6f6cdeec6d 100644 --- a/modules/pubmatic/openwrap/adpod/adpod.go +++ b/modules/pubmatic/openwrap/adpod/adpod.go @@ -3,13 +3,16 @@ package adpod import ( "encoding/json" "errors" + "fmt" "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/cache" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" - "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/ortb" "github.com/prebid/prebid-server/v2/util/ptrutil" ) @@ -32,30 +35,19 @@ func setDefaultValues(adpodConfig *models.AdPod) { } -func GetAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, partnerConfigMap map[int]map[string]string, pubId string, me metrics.MetricsEngine) (*models.AdPod, error) { - adpodConfigs, ok, err := resolveAdpodConfigs(impVideo, requestExtConfigs, adUnitConfig, pubId, me) +func GetV25AdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, partnerConfigMap map[int]map[string]string, pubId string, me metrics.MetricsEngine) (*models.AdPod, error) { + adpodConfigs, ok, err := resolveV25AdpodConfigs(impVideo, requestExtConfigs, adUnitConfig, pubId, me) if !ok || err != nil { return nil, err } - videoAdDuration := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationKey) - if len(videoAdDuration) > 0 { - adpodConfigs.VideoAdDuration = utils.GetIntArrayFromString(videoAdDuration, models.ArraySeparator) - } - - videoAdDurationMatchingPolicy := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationMatchingKey) - if len(videoAdDurationMatchingPolicy) > 0 { - adpodConfigs.VideoAdDurationMatching = videoAdDurationMatchingPolicy - } - // Set default value if adpod object does not exists setDefaultValues(adpodConfigs) return adpodConfigs, nil - } -func resolveAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, pubId string, me metrics.MetricsEngine) (*models.AdPod, bool, error) { +func resolveV25AdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.ExtRequestAdPod, adUnitConfig *adunitconfig.AdConfig, pubId string, me metrics.MetricsEngine) (*models.AdPod, bool, error) { var adpodConfig *models.AdPod // Check in impression extension @@ -79,10 +71,9 @@ func resolveAdpodConfigs(impVideo *openrtb2.Video, requestExtConfigs *models.Ext } return nil, false, nil - } -func Validate(config *models.AdPod) error { +func ValidateV25Configs(rCtx models.RequestCtx, config *models.AdPod) error { if config == nil { return nil } @@ -119,9 +110,9 @@ func Validate(config *models.AdPod) error { return errors.New("adpod.adminduration must be less than adpod.admaxduration") } - if len(config.VideoAdDuration) > 0 { + if rCtx.AdpodProfileConfig != nil && len(rCtx.AdpodProfileConfig.AdserverCreativeDurations) > 0 { validDurations := false - for _, videoDuration := range config.VideoAdDuration { + for _, videoDuration := range rCtx.AdpodProfileConfig.AdserverCreativeDurations { if videoDuration >= config.MinDuration && videoDuration <= config.MaxDuration { validDurations = true break @@ -135,3 +126,170 @@ func Validate(config *models.AdPod) error { return nil } + +func GetAdpodConfigs(rctx models.RequestCtx, cache cache.Cache, adunit *adunitconfig.AdConfig) ([]models.PodConfig, error) { + // Fetch Adpod Configs from UI + pods, err := cache.GetAdpodConfig(rctx.PubID, rctx.ProfileID, rctx.DisplayVersionID) + if err != nil { + return nil, err + } + + var uiAdpodConfigs []models.PodConfig + if pods != nil { + uiAdpodConfigs = append(uiAdpodConfigs, decouplePodConfigs(pods)...) + } + + // Vmap adpod configs + var adrules []models.PodConfig + if adunit != nil && adunit.Adrule != nil { + for _, rule := range adunit.Adrule { + if rule != nil { + adrules = append(adrules, models.PodConfig{ + PodID: rule.PodID, + PodDur: rule.PodDur, + MaxSeq: rule.MaxSeq, + MinDuration: rule.MinDuration, + MaxDuration: rule.MaxDuration, + RqdDurs: rule.RqdDurs, + }) + } + } + } + + var podConfigs []models.PodConfig + if len(uiAdpodConfigs) > 0 && adunit.Video != nil && adunit.Video.UsePodConfig != nil && *adunit.Video.UsePodConfig { + podConfigs = append(podConfigs, uiAdpodConfigs...) + } else if len(adrules) > 0 && rctx.AdruleFlag { + podConfigs = append(podConfigs, adrules...) + } + + return podConfigs, nil +} + +func decouplePodConfigs(pods *adpodconfig.AdpodConfig) []models.PodConfig { + if pods == nil { + return nil + } + + var podConfigs []models.PodConfig + // Add all dynamic adpods + for i, dynamic := range pods.Dynamic { + podConfigs = append(podConfigs, models.PodConfig{ + PodID: fmt.Sprintf("dynamic-%d", i+1), + PodDur: dynamic.PodDur, + MaxSeq: dynamic.MaxSeq, + MinDuration: dynamic.MinDuration, + MaxDuration: dynamic.MaxDuration, + RqdDurs: dynamic.RqdDurs, + }) + } + + // Add all structured adpods + for i, structured := range pods.Structured { + podConfigs = append(podConfigs, models.PodConfig{ + PodID: fmt.Sprintf("structured-%d", i+1), + MinDuration: structured.MinDuration, + MaxDuration: structured.MaxDuration, + RqdDurs: structured.RqdDurs, + }) + } + + // Add all hybrid adpods + for i, hybrid := range pods.Hybrid { + pod := models.PodConfig{ + PodID: fmt.Sprintf("hybrid-%d", i+1), + MinDuration: hybrid.MinDuration, + MaxDuration: hybrid.MaxDuration, + RqdDurs: hybrid.RqdDurs, + } + + if hybrid.PodDur != nil { + pod.PodDur = *hybrid.PodDur + } + + if hybrid.MaxSeq != nil { + pod.MaxSeq = *hybrid.MaxSeq + } + + podConfigs = append(podConfigs, pod) + } + + return podConfigs +} + +func ValidateAdpodConfigs(configs []models.PodConfig) error { + for _, config := range configs { + if config.RqdDurs == nil && config.MinDuration == 0 && config.MaxDuration == 0 { + return errors.New("slot duration is missing in adpod config") + } + + if config.MinDuration > config.MaxDuration { + return errors.New("min duration should be less than max duration") + } + + if config.RqdDurs == nil && config.MaxDuration <= 0 { + return errors.New("max duration should be greater than 0") + } + + if config.PodDur < 0 { + return errors.New("pod duration should be positive number") + } + + if config.MaxSeq < 0 { + return errors.New("max sequence should be positive number") + } + } + + return nil +} + +func ApplyAdpodConfigs(rctx models.RequestCtx, bidRequest *openrtb2.BidRequest) *openrtb2.BidRequest { + if len(rctx.ImpAdPodConfig) == 0 { + return bidRequest + } + + imps := make([]openrtb2.Imp, 0) + for _, imp := range bidRequest.Imp { + if imp.Video == nil { + imps = append(imps, imp) + continue + } + + // Give priority to adpod config in request + if len(imp.Video.PodID) > 0 { + imps = append(imps, imp) + continue + } + + impPodConfig, ok := rctx.ImpAdPodConfig[imp.ID] + if !ok || len(impPodConfig) == 0 { + imps = append(imps, imp) + continue + } + + // Apply adpod config + for i, podConfig := range impPodConfig { + impCopy := ortb.DeepCloneImpression(&imp) + impCopy.ID = fmt.Sprintf("%s-%s-%d", impCopy.ID, podConfig.PodID, i) + impCopy.Video.PodID = podConfig.PodID + impCopy.Video.MaxSeq = podConfig.MaxSeq + impCopy.Video.PodDur = podConfig.PodDur + impCopy.Video.MinDuration = podConfig.MinDuration + impCopy.Video.MaxDuration = podConfig.MaxDuration + impCopy.Video.RqdDurs = podConfig.RqdDurs + + impCtx := rctx.ImpBidCtx[imp.ID] + impCtxCopy := impCtx.DeepCopy() + impCtxCopy.Video = impCopy.Video + + rctx.ImpBidCtx[impCopy.ID] = impCtxCopy + imps = append(imps, *impCopy) + } + + // Delete original imp from context + delete(rctx.ImpBidCtx, imp.ID) + } + + bidRequest.Imp = imps + return bidRequest +} diff --git a/modules/pubmatic/openwrap/adpod/auction/auction.go b/modules/pubmatic/openwrap/adpod/auction/auction.go index 4ededc7e7fc..be0b81d5d15 100644 --- a/modules/pubmatic/openwrap/adpod/auction/auction.go +++ b/modules/pubmatic/openwrap/adpod/auction/auction.go @@ -39,7 +39,7 @@ func FormAdpodBidsAndPerformExclusion(response *openrtb2.BidResponse, rctx model return nil, errs } - impAdpodBidsMap, _ := generateAdpodBids(response.SeatBid, rctx.ImpBidCtx) + impAdpodBidsMap, _ := generateAdpodBids(response.SeatBid, rctx.ImpBidCtx, rctx.AdpodProfileConfig) adpodBids, errs := doAdPodExclusions(impAdpodBidsMap, rctx.ImpBidCtx) if len(errs) > 0 { return nil, errs @@ -77,7 +77,7 @@ func addTargetingKey(bid *openrtb2.Bid, key openrtb_ext.TargetingKey, value stri return err } -func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.ImpCtx) (map[string]*AdPodBid, []openrtb2.SeatBid) { +func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.ImpCtx, adpodProfileCfg *models.AdpodProfileConfig) (map[string]*AdPodBid, []openrtb2.SeatBid) { impAdpodBidsMap := make(map[string]*AdPodBid) videoSeatBids := make([]openrtb2.SeatBid, 0) @@ -133,12 +133,13 @@ func generateAdpodBids(seatBids []openrtb2.SeatBid, impCtx map[string]models.Imp // } //get duration of creative - duration, status := getBidDuration(bid, *eachImpCtx.AdpodConfig, eachImpCtx.ImpAdPodCfg, sequence) + duration, status := getBidDuration(bid, *eachImpCtx.AdpodConfig, adpodProfileCfg, eachImpCtx.ImpAdPodCfg, sequence) if eachImpCtx.BidIDToDur == nil { eachImpCtx.BidIDToDur = map[string]int64{} } eachImpCtx.BidIDToDur[bid.ID] = duration impCtx[impId] = eachImpCtx + eachImpBid := Bid{ Bid: bid, ExtBid: ext, @@ -185,7 +186,7 @@ it will try to get the actual ad duration returned by the bidder using prebid.vi if prebid.video.duration not present then uses defaultDuration passed as an argument if video lengths matching policy is present for request then it will validate and update duration based on policy */ -func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, config []*models.ImpAdPodConfig, sequence int) (int64, int64) { +func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, adpodProfileCfg *models.AdpodProfileConfig, config []*models.ImpAdPodConfig, sequence int) (int64, int64) { // C1: Read it from bid.ext.prebid.video.duration field duration, err := jsonparser.GetInt(bid.Ext, "prebid", "video", "duration") @@ -201,8 +202,8 @@ func getBidDuration(bid *openrtb2.Bid, adpodConfig models.AdPod, config []*model } // C2: Based on video lengths matching policy validate and return duration - if len(adpodConfig.VideoAdDurationMatching) > 0 { - return getDurationBasedOnDurationMatchingPolicy(duration, adpodConfig.VideoAdDurationMatching, config) + if adpodProfileCfg != nil && len(adpodProfileCfg.AdserverCreativeDurationMatchingPolicy) > 0 { + return getDurationBasedOnDurationMatchingPolicy(duration, adpodProfileCfg.AdserverCreativeDurationMatchingPolicy, config) } //default return duration which is present in bid.ext.prebid.vide.duration field diff --git a/modules/pubmatic/openwrap/adpod/impressions/duration_ranges.go b/modules/pubmatic/openwrap/adpod/impressions/duration_ranges.go index d086cb2d83e..1ff280064f5 100644 --- a/modules/pubmatic/openwrap/adpod/impressions/duration_ranges.go +++ b/modules/pubmatic/openwrap/adpod/impressions/duration_ranges.go @@ -12,14 +12,19 @@ type DurRangeConfig struct { //IImpressions interface } // newByDurationRanges will create new object ob byDurRangeConfig for creating impressions for adpod request -func newByDurationRanges(policy string, durations []int, maxAds, adMinDuration, adMaxDuration int) DurRangeConfig { - return DurRangeConfig{ - policy: policy, - durations: durations, +func newByDurationRanges(adpodProfileCfg *models.AdpodProfileConfig, maxAds, adMinDuration, adMaxDuration int) DurRangeConfig { + cfg := DurRangeConfig{ maxAds: maxAds, adMinDuration: adMinDuration, adMaxDuration: adMaxDuration, } + + if adpodProfileCfg != nil { + cfg.durations = adpodProfileCfg.AdserverCreativeDurations + cfg.policy = adpodProfileCfg.AdserverCreativeDurationMatchingPolicy + } + + return cfg } // Get function returns lists of min,max duration ranges ganerated based on durations diff --git a/modules/pubmatic/openwrap/adpod/impressions/impression.go b/modules/pubmatic/openwrap/adpod/impressions/impression.go index 4535b49a845..f1fd6e7be4c 100644 --- a/modules/pubmatic/openwrap/adpod/impressions/impression.go +++ b/modules/pubmatic/openwrap/adpod/impressions/impression.go @@ -27,14 +27,14 @@ type ImpGenerator interface { // Algorithm() int // returns algorithm used for computing number of impressions } -func GenerateImpressions(request *openrtb_ext.RequestWrapper, impCtx map[string]models.ImpCtx, pubId string, me metrics.MetricsEngine) ([]*openrtb_ext.ImpWrapper, []error) { +func GenerateImpressions(request *openrtb_ext.RequestWrapper, impCtx map[string]models.ImpCtx, adpodProfileCfg *models.AdpodProfileConfig, pubId string, me metrics.MetricsEngine) ([]*openrtb_ext.ImpWrapper, []error) { var imps []*openrtb_ext.ImpWrapper var errs []error for _, impWrapper := range request.GetImp() { eachImpCtx := impCtx[impWrapper.ID] - impAdpodConfig, err := getAdPodImpConfig(impWrapper.Imp, eachImpCtx.AdpodConfig) + impAdpodConfig, err := getAdPodImpConfig(impWrapper.Imp, eachImpCtx.AdpodConfig, adpodProfileCfg) if impAdpodConfig == nil { imps = append(imps, impWrapper) if err != nil { @@ -95,13 +95,13 @@ func generateImpressionID(impID string, seqNo int) string { } // getAdPodImpsConfigs will return number of impressions configurations within adpod -func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod) ([]*models.ImpAdPodConfig, error) { +func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig) ([]*models.ImpAdPodConfig, error) { // This case for non adpod video impression if adpod == nil { return nil, nil } - selectedAlgorithm := SelectAlgorithm(adpod) - impGen := NewImpressions(imp.Video.MinDuration, imp.Video.MaxDuration, adpod, selectedAlgorithm) + selectedAlgorithm := SelectAlgorithm(adpod, adpodProfileCfg) + impGen := NewImpressions(imp.Video.MinDuration, imp.Video.MaxDuration, adpod, adpodProfileCfg, selectedAlgorithm) impRanges := impGen.Get() // labels := metrics.PodLabels{AlgorithmName: impressions.MonitorKey[selectedAlgorithm], NoOfImpressions: new(int)} @@ -132,10 +132,10 @@ func getAdPodImpConfig(imp *openrtb2.Imp, adpod *models.AdPod) ([]*models.ImpAdP // Return Value: // - MinMaxAlgorithm (default) // - ByDurationRanges: if reqAdPod extension has VideoAdDuration and VideoAdDurationMatchingPolicy is "exact" algorithm -func SelectAlgorithm(reqAdPod *models.AdPod) int { +func SelectAlgorithm(reqAdPod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig) int { if reqAdPod != nil { - if len(reqAdPod.VideoAdDuration) > 0 && - (models.OWExactVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching || models.OWRoundupVideoAdDurationMatching == reqAdPod.VideoAdDurationMatching) { + if adpodProfileCfg != nil && len(adpodProfileCfg.AdserverCreativeDurations) > 0 && + (models.OWExactVideoAdDurationMatching == adpodProfileCfg.AdserverCreativeDurationMatchingPolicy || models.OWRoundupVideoAdDurationMatching == adpodProfileCfg.AdserverCreativeDurationMatchingPolicy) { return models.ByDurationRanges } } @@ -146,7 +146,7 @@ func SelectAlgorithm(reqAdPod *models.AdPod) int { // based on input algorithm type // if invalid algorithm type is passed, it returns default algorithm which will compute // impressions based on minimum ad slot duration -func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, algorithm int) ImpGenerator { +func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, adpodProfileCfg *models.AdpodProfileConfig, algorithm int) ImpGenerator { switch algorithm { case models.MaximizeForDuration: g := newMaximizeForDuration(podMinDuration, podMaxDuration, adpod) @@ -157,10 +157,7 @@ func NewImpressions(podMinDuration, podMaxDuration int64, adpod *models.AdPod, a return &g case models.ByDurationRanges: - g := newByDurationRanges(adpod.VideoAdDurationMatching, adpod.VideoAdDuration, - int(adpod.MaxAds), - adpod.MinDuration, adpod.MaxDuration) - + g := newByDurationRanges(adpodProfileCfg, int(adpod.MaxAds), adpod.MinDuration, adpod.MaxDuration) return &g } diff --git a/modules/pubmatic/openwrap/beforevalidationhook.go b/modules/pubmatic/openwrap/beforevalidationhook.go index f5589235548..7dfeabeda0d 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook.go +++ b/modules/pubmatic/openwrap/beforevalidationhook.go @@ -28,6 +28,7 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" modelsAdunitConfig "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/utils" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/ptrutil" ) @@ -171,6 +172,15 @@ func (m OpenWrap) handleBeforeValidationHook( } } + videoAdDuration := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationKey) + policy := models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.VideoAdDurationMatchingKey) + if len(videoAdDuration) > 0 { + rCtx.AdpodProfileConfig = &models.AdpodProfileConfig{ + AdserverCreativeDurations: utils.GetIntArrayFromString(videoAdDuration, models.ArraySeparator), + AdserverCreativeDurationMatchingPolicy: policy, + } + } + rCtx.PartnerConfigMap = partnerConfigMap // keep a copy at module level as well if ver, err := strconv.Atoi(models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.DisplayVersionID)); err == nil { rCtx.DisplayVersionID = ver @@ -387,7 +397,7 @@ func (m OpenWrap) handleBeforeValidationHook( var adpodConfig *models.AdPod if rCtx.IsCTVRequest { - adpodConfig, err = adpod.GetAdpodConfigs(imp.Video, requestExt.AdPod, videoAdUnitCtx.AppliedSlotAdUnitConfig, partnerConfigMap, rCtx.PubIDStr, m.metricEngine) + adpodConfig, err = adpod.GetV25AdpodConfigs(imp.Video, requestExt.AdPod, videoAdUnitCtx.AppliedSlotAdUnitConfig, partnerConfigMap, rCtx.PubIDStr, m.metricEngine) if err != nil { result.NbrCode = int(nbr.InvalidAdpodConfig) result.Errors = append(result.Errors, "failed to get adpod configurations for "+imp.ID+" reason: "+err.Error()) @@ -396,19 +406,39 @@ func (m OpenWrap) handleBeforeValidationHook( } //Adding default durations for CTV Test requests - if rCtx.IsTestRequest > 0 && adpodConfig != nil && adpodConfig.VideoAdDuration == nil { - adpodConfig.VideoAdDuration = []int{5, 10} + if rCtx.IsTestRequest > 0 && adpodConfig != nil && rCtx.AdpodProfileConfig == nil { + rCtx.AdpodProfileConfig = &models.AdpodProfileConfig{ + AdserverCreativeDurations: []int{5, 10}, + AdserverCreativeDurationMatchingPolicy: openrtb_ext.OWRoundupVideoAdDurationMatching, + } } - if rCtx.IsTestRequest > 0 && adpodConfig != nil && len(adpodConfig.VideoAdDurationMatching) == 0 { - adpodConfig.VideoAdDurationMatching = openrtb_ext.OWRoundupVideoAdDurationMatching + + if err := adpod.ValidateV25Configs(rCtx, adpodConfig); err != nil { + result.NbrCode = int(nbr.InvalidAdpodConfig) + result.Errors = append(result.Errors, "invalid adpod configurations for "+imp.ID+" reason: "+err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) + return result, nil } - if err := adpod.Validate(adpodConfig); err != nil { + podConfigs, err := adpod.GetAdpodConfigs(rCtx, m.cache, videoAdUnitCtx.AppliedSlotAdUnitConfig) + if err != nil { + result.NbrCode = int(nbr.InvalidAdpodConfig) + result.Errors = append(result.Errors, "failed to get adpod configurations for "+imp.ID+" reason: "+err.Error()) + rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) + return result, nil + } + + err = adpod.ValidateAdpodConfigs(podConfigs) + if err != nil { result.NbrCode = int(nbr.InvalidAdpodConfig) result.Errors = append(result.Errors, "invalid adpod configurations for "+imp.ID+" reason: "+err.Error()) rCtx.ImpBidCtx = getDefaultImpBidCtx(*payload.BidRequest) return result, nil } + + if len(podConfigs) > 0 { + rCtx.ImpAdPodConfig[imp.ID] = podConfigs + } } bidderMeta := make(map[string]models.PartnerData) @@ -638,6 +668,10 @@ func (m OpenWrap) handleBeforeValidationHook( result.ChangeSet.AddMutation(func(ep hookstage.BeforeValidationRequestPayload) (hookstage.BeforeValidationRequestPayload, error) { rctx := moduleCtx.ModuleContext["rctx"].(models.RequestCtx) + defer func() { + moduleCtx.ModuleContext["rctx"] = rctx + }() + var err error if rctx.IsCTVRequest && ep.BidRequest.Source != nil && ep.BidRequest.Source.SChain != nil { err = ctv.IsValidSchain(ep.BidRequest.Source.SChain) @@ -657,6 +691,8 @@ func (m OpenWrap) handleBeforeValidationHook( if err != nil { result.Errors = append(result.Errors, err.Error()) } + + ep.BidRequest = adpod.ApplyAdpodConfigs(rctx, ep.BidRequest) } return ep, err }, hookstage.MutationUpdate, "request-body-with-profile-data") diff --git a/modules/pubmatic/openwrap/beforevalidationhook_test.go b/modules/pubmatic/openwrap/beforevalidationhook_test.go index 1ab42d56ec2..4b3de0603c8 100644 --- a/modules/pubmatic/openwrap/beforevalidationhook_test.go +++ b/modules/pubmatic/openwrap/beforevalidationhook_test.go @@ -23,8 +23,8 @@ import ( metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" mock_metrics "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics/mock" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adpodconfig" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" - modelsAdunitConfig "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" mock_profilemetadata "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/profilemetadata/mock" mock_feature "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/publisherfeature/mock" @@ -4362,6 +4362,278 @@ func TestOpenWrapHandleBeforeValidationHook(t *testing.T) { nilCurrencyConversion: false, }, }, + { + name: "Read_adpod_configs_from_UI_and_update_request_impressions_with_configs", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubID: 5890, + PubIDStr: "5890", + ProfileID: 4444, + ProfileIDStr: "4444", + DisplayID: 1, + SSAuction: 1, + Platform: "video", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: true, + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Endpoint: models.EndpointJson, + Method: "POST", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + MetricsEngine: mockEngine, + AdruleFlag: false, + AdpodProfileConfig: nil, + ImpAdPodConfig: make(map[string][]models.PodConfig), + }, + }, + }, + bidrequest: json.RawMessage(`{"id":"1559039248176","test":0,"at":1,"tmax":500,"source":{"fd":1,"tid":"1559039248176","pchain":"pchaintagid"},"imp":[{"id":"28635736ddc2bb2","ext":{"bidder":{"appnexus":{"keywords":[],"dealtier":{"prefix":"apnx","mindealtier":4}}}},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"video":{"mimes":["video/3gpp","video/mp4"],"minduration":10,"maxduration":40,"protocols":[2,3,5,6],"w":280,"h":360,"startdelay":1,"placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3],"playbackend":1}}],"site":{"id":"123","name":"EbayShopping","domain":"ebay.com","cat":["IAB1-5"],"sectioncat":["IAB1-5"],"pagecat":["IAB1-5"],"page":"http://ebay.com/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?appnexus_deal_priority=6&appnexus_video_fixedbid=10&videobid=4&cat=IAB20-3","ref":"http://ebay.com/home","search":"NewCloths","mobile":1,"privacypolicy":1,"keywords":"Cloths","publisher":{"id":"5890","name":"Test Publisher","cat":["IAB1-5"],"domain":"publisher.com"},"content":{"id":"381d2e0b-548d-4f27-bfdd-e6e66f43557e","episode":1,"title":"StarWars","series":"StarWars","season":"Season3","artist":"GeorgeLucas","genre":"Action","album":"Action","isrc":"2","url":"http://www.pubmatic.com/test/","cat":["IAB1-1","IAB1-2"],"prodq":1,"videoquality":1,"context":1,"contentrating":"MPAA","userrating":"9-Stars","qagmediarating":1,"keywords":"ActionMovies","livestream":1,"sourcerelationship":1,"len":12000,"language":"en","embeddable":1,"producer":{"id":"123","name":"GaryKurtz","cat":["IAB1-5","IAB1-6"],"domain":"producer.com"}}},"device":{"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","dnt":0,"lmt":0,"ip":"172.16.8.74","ipv6":"2001:db8::8a2e:370:7334","devicetype":1,"make":"Apple","model":"iPhone X","os":"iOS","osv":"10","hwv":"10x","h":768,"w":1366,"ppi":4096,"pxratio":1.3,"js":1,"flashver":"1.1","language":"en","carrier":"VERIZON","mccmnc":"310-005","connectiontype":2,"ifa":"EA7583CD-A667-48BC-B806-42ECB2B48606","didsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","didmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","macsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","macmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606"},"user":{"id":"45067fec-eab7-4ca0-ad3a-87b01f21846a","buyeruid":"45067fec-eab7-4ca0-ad3a-87b01f21846a","yob":1990,"gender":"M","keywords":"Movies","customdata":"StarWars"},"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"1YNN"}},"ext":{"wrapper":{"profileid":4444,"versionid":1,"ssauction":1,"sumry_disable":0,"clientconfig":1,"supportdeals":true}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "adunit@0x0": { + SlotName: "adunit@0x0", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"adunit@0x0"}, + HashValueMap: map[string]string{ + "adunit@0x0": "1232433543534543", + }, + }) + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + models.PLATFORM_KEY: models.PLATFORM_VIDEO, + }, + }, nil) + mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "adunit": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MIMEs: []string{"video/mp4", "video/mpeg"}, + W: ptrutil.ToPtr[int64](640), + H: ptrutil.ToPtr[int64](480), + }, + }, + UsePodConfig: ptrutil.ToPtr(true), + }, + }, + }, + }) + mockCache.EXPECT().GetAdpodConfig(gomock.Any(), gomock.Any(), gomock.Any()).Return(&adpodconfig.AdpodConfig{ + Dynamic: []adpodconfig.Dynamic{ + { + PodDur: 60, + MaxSeq: 3, + MinDuration: 20, + MaxDuration: 30, + }, + }, + Structured: []adpodconfig.Structured{ + { + MinDuration: 15, + MaxDuration: 15, + }, + }, + }, nil) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "4444") + mockEngine.EXPECT().RecordPublisherRequests(models.EndpointJson, "5890", "video") + mockEngine.EXPECT().RecordPlatformPublisherPartnerReqStats("video", "5890", "appnexus") + mockEngine.EXPECT().RecordCTVRequests("json", models.PLATFORM_DISPLAY) + mockEngine.EXPECT().RecordCTVHTTPMethodRequests("json", "5890", "POST") + mockEngine.EXPECT().RecordVideoInstlImpsStats("5890", "4444") + mockEngine.EXPECT().RecordReqImpsWithContentCount("5890", models.ContentTypeSite) + mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) + mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) + mockProfileMetaData.EXPECT().GetProfileTypePlatform(gomock.Any()).Return(0, false) + }, + want: want{ + hookResult: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: false, + NbrCode: 0, + ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, + DebugMessages: []string{`new imp: {"28635736ddc2bb2":{"ImpID":"28635736ddc2bb2","TagID":"adunit","Div":"","SlotName":"adunit","AdUnitName":"adunit","Secure":0,"BidFloor":1,"BidFloorCur":"USD","IsRewardInventory":null,"Banner":false,"Video":{"mimes":["video/3gpp","video/mp4"],"minduration":10,"maxduration":40,"startdelay":1,"protocols":[2,3,5,6],"w":280,"h":360,"placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"playbackend":1,"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3]},"Native":null,"IncomingSlots":["280x360"],"Type":"video","Bidders":{"appnexus":{"PartnerID":2,"PrebidBidderCode":"appnexus","MatchedSlot":"adunit@0x0","KGP":"_AU_@_W_x_H_","KGPV":"","IsRegex":false,"Params":{"placementId":0,"site":"12313","adtag":"45343"},"VASTTagFlags":null}},"NonMapped":{},"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}},"BidCtx":{},"BannerAdUnitCtx":{"MatchedSlot":"adunit","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"video":{"enabled":true,"config":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"usepodconfig":true}},"AppliedSlotAdUnitConfig":{"video":{"enabled":true,"config":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"usepodconfig":true}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"VideoAdUnitCtx":{"MatchedSlot":"adunit","IsRegex":false,"MatchedRegex":"","SelectedSlotAdUnitConfig":{"video":{"enabled":true,"config":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"usepodconfig":true}},"AppliedSlotAdUnitConfig":{"video":{"enabled":true,"config":{"mimes":["video/mp4","video/mpeg"],"w":640,"h":480},"usepodconfig":true}},"UsingDefaultConfig":false,"AllowedConnectionTypes":null},"BidderError":"","IsAdPodRequest":false,"AdpodConfig":null,"ImpAdPodCfg":null,"BidIDToAPRC":null,"AdserverURL":"","BidIDToDur":null}}`, `new request.ext: {"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true,"includebrandcategory":{"primaryadserver":0,"publisher":"","withcategory":false,"translatecategories":false,"skipdedup":true}},"macros":{"[PLATFORM]":"2","[PROFILE_ID]":"4444","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, + AnalyticsTags: hookanalytics.Analytics{}, + }, + bidRequest: json.RawMessage(`{"id":"1559039248176","imp":[{"id":"28635736ddc2bb2-dynamic-1-0","video":{"mimes":["video/3gpp","video/mp4"],"minduration":20,"maxduration":30,"startdelay":1,"maxseq":3,"poddur":60,"protocols":[2,3,5,6],"w":280,"h":360,"podid":"dynamic-1","placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"playbackend":1,"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3]},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}},{"id":"28635736ddc2bb2-structured-1-1","video":{"mimes":["video/3gpp","video/mp4"],"minduration":15,"maxduration":15,"startdelay":1,"protocols":[2,3,5,6],"w":280,"h":360,"podid":"structured-1","placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"playbackend":1,"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3]},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}}],"site":{"id":"123","name":"EbayShopping","domain":"ebay.com","cat":["IAB1-5"],"sectioncat":["IAB1-5"],"pagecat":["IAB1-5"],"page":"http://ebay.com/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?appnexus_deal_priority=6\u0026appnexus_video_fixedbid=10\u0026videobid=4\u0026cat=IAB20-3","ref":"http://ebay.com/home","search":"NewCloths","mobile":1,"privacypolicy":1,"publisher":{"id":"5890","name":"Test Publisher","cat":["IAB1-5"],"domain":"publisher.com"},"content":{"id":"381d2e0b-548d-4f27-bfdd-e6e66f43557e","episode":1,"title":"StarWars","series":"StarWars","season":"Season3","artist":"GeorgeLucas","genre":"Action","album":"Action","isrc":"2","producer":{"id":"123","name":"GaryKurtz","cat":["IAB1-5","IAB1-6"],"domain":"producer.com"},"url":"http://www.pubmatic.com/test/","cat":["IAB1-1","IAB1-2"],"prodq":1,"videoquality":1,"context":1,"contentrating":"MPAA","userrating":"9-Stars","qagmediarating":1,"keywords":"ActionMovies","livestream":1,"sourcerelationship":1,"len":12000,"language":"en","embeddable":1},"keywords":"Cloths"},"device":{"dnt":0,"lmt":0,"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","ip":"172.16.8.74","ipv6":"2001:db8::8a2e:370:7334","devicetype":1,"make":"Apple","model":"iPhone X","os":"iOS","osv":"10","hwv":"10x","h":768,"w":1366,"ppi":4096,"pxratio":1.3,"js":1,"flashver":"1.1","language":"en","carrier":"VERIZON","mccmnc":"310-005","connectiontype":2,"ifa":"EA7583CD-A667-48BC-B806-42ECB2B48606","didsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","didmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","macsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","macmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606"},"user":{"id":"45067fec-eab7-4ca0-ad3a-87b01f21846a","buyeruid":"45067fec-eab7-4ca0-ad3a-87b01f21846a","yob":1990,"gender":"M","keywords":"Movies","customdata":"StarWars"},"at":1,"tmax":500,"source":{"fd":1,"tid":"1559039248176","pchain":"pchaintagid"},"regs":{"ext":{"gdpr":1,"us_privacy":"1YNN"}},"ext":{"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true,"includebrandcategory":{"primaryadserver":0,"publisher":"","withcategory":false,"translatecategories":false,"skipdedup":true}},"macros":{"[PLATFORM]":"2","[PROFILE_ID]":"4444","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), + error: false, + nilCurrencyConversion: false, + doMutate: true, + }, + }, + { + name: "Read_adpod_configs_from_Adrule_and_update_request_impressions_with_configs", + args: args{ + ctx: context.Background(), + moduleCtx: hookstage.ModuleInvocationContext{ + ModuleContext: hookstage.ModuleContext{ + "rctx": models.RequestCtx{ + PubID: 5890, + PubIDStr: "5890", + ProfileID: 4444, + ProfileIDStr: "4444", + DisplayID: 1, + SSAuction: 1, + Platform: "video", + Debug: true, + UA: "go-test", + IP: "127.0.0.1", + IsCTVRequest: true, + UidCookie: &http.Cookie{ + Name: "uids", + Value: `eyJ0ZW1wVUlEcyI6eyIzM2Fjcm9zcyI6eyJ1aWQiOiIxMTkxNzkxMDk5Nzc2NjEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTo0My4zODg4Nzk5NVoifSwiYWRmIjp7InVpZCI6IjgwNDQ2MDgzMzM3Nzg4MzkwNzgiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMS4wMzMwNTQ3MjdaIn0sImFka2VybmVsIjp7InVpZCI6IkE5MTYzNTAwNzE0OTkyOTMyOTkwIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuMzczMzg1NjYyWiJ9LCJhZGtlcm5lbEFkbiI6eyJ1aWQiOiJBOTE2MzUwMDcxNDk5MjkzMjk5MCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEzLjQzNDkyNTg5NloifSwiYWRtaXhlciI6eyJ1aWQiOiIzNjZhMTdiMTJmMjI0ZDMwOGYzZTNiOGRhOGMzYzhhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjU5MjkxNDgwMVoifSwiYWRueHMiOnsidWlkIjoiNDE5Mjg5ODUzMDE0NTExOTMiLCJleHBpcmVzIjoiMjAyMy0wMS0xOFQwOTo1MzowOC44MjU0NDI2NzZaIn0sImFqYSI6eyJ1aWQiOiJzMnN1aWQ2RGVmMFl0bjJveGQ1aG9zS1AxVmV3IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTMuMjM5MTc2MDU0WiJ9LCJlcGxhbm5pbmciOnsidWlkIjoiQUoxRjBTOE5qdTdTQ0xWOSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjkyOTk2MDQ3M1oifSwiZ2Ftb3NoaSI6eyJ1aWQiOiJndXNyXzM1NmFmOWIxZDhjNjQyYjQ4MmNiYWQyYjdhMjg4MTYxIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuNTI0MTU3MjI1WiJ9LCJncmlkIjp7InVpZCI6IjRmYzM2MjUwLWQ4NTItNDU5Yy04NzcyLTczNTZkZTE3YWI5NyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE0LjY5NjMxNjIyN1oifSwiZ3JvdXBtIjp7InVpZCI6IjdENzVEMjVGLUZBQzktNDQzRC1CMkQxLUIxN0ZFRTExRTAyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjM5LjIyNjIxMjUzMloifSwiaXgiOnsidWlkIjoiWW9ORlNENlc5QkphOEh6eEdtcXlCUUFBXHUwMDI2Mjk3IiwiZXhwaXJlcyI6IjIwMjMtMDUtMzFUMDc6NTM6MzguNTU1ODI3MzU0WiJ9LCJqaXhpZSI6eyJ1aWQiOiI3MzY3MTI1MC1lODgyLTExZWMtYjUzOC0xM2FjYjdhZjBkZTQiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi4xOTEwOTk3MzJaIn0sImxvZ2ljYWQiOnsidWlkIjoiQVZ4OVROQS11c25pa3M4QURzTHpWa3JvaDg4QUFBR0JUREh0UUEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS40NTUxNDk2MTZaIn0sIm1lZGlhbmV0Ijp7InVpZCI6IjI5Nzg0MjM0OTI4OTU0MTAwMDBWMTAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMy42NzIyMTUxMjhaIn0sIm1naWQiOnsidWlkIjoibTU5Z1hyN0xlX1htIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTcuMDk3MDAxNDcxWiJ9LCJuYW5vaW50ZXJhY3RpdmUiOnsidWlkIjoiNmFlYzhjMTAzNzlkY2I3ODQxMmJjODBiNmRkOWM5NzMxNzNhYjdkNzEyZTQzMWE1YTVlYTcwMzRlNTZhNThhMCIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjE2LjcxNDgwNzUwNVoifSwib25ldGFnIjp7InVpZCI6IjdPelZoVzFOeC1LOGFVak1HMG52NXVNYm5YNEFHUXZQbnVHcHFrZ3k0ckEiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OTowOS4xNDE3NDEyNjJaIn0sIm9wZW54Ijp7InVpZCI6IjVkZWNlNjIyLTBhMjMtMGRhYi0zYTI0LTVhNzcwMTBlNDU4MiIsImV4cGlyZXMiOiIyMDIzLTA1LTMxVDA3OjUyOjQ3LjE0MDQxNzM2M1oifSwicHVibWF0aWMiOnsidWlkIjoiN0Q3NUQyNUYtRkFDOS00NDNELUIyRDEtQjE3RkVFMTFFMDI3IiwiZXhwaXJlcyI6IjIwMjItMTAtMzFUMDk6MTQ6MjUuNzM3MjU2ODk5WiJ9LCJyaWNoYXVkaWVuY2UiOnsidWlkIjoiY2I2YzYzMjAtMzNlMi00Nzc0LWIxNjAtMXp6MTY1NDg0MDc0OSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjUyNTA3NDE4WiJ9LCJzbWFydHlhZHMiOnsidWlkIjoiMTJhZjE1ZTQ0ZjAwZDA3NjMwZTc0YzQ5MTU0Y2JmYmE0Zjg0N2U4ZDRhMTU0YzhjM2Q1MWY1OGNmNzJhNDYyNyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjgyNTAzMTg4NFoifSwic21pbGV3YW50ZWQiOnsidWlkIjoiZGQ5YzNmZTE4N2VmOWIwOWNhYTViNzExNDA0YzI4MzAiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNC4yNTU2MDkzNjNaIn0sInN5bmFjb3JtZWRpYSI6eyJ1aWQiOiJHRFBSIiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MDkuOTc5NTgzNDM4WiJ9LCJ0cmlwbGVsaWZ0Ijp7InVpZCI6IjcwMjE5NzUwNTQ4MDg4NjUxOTQ2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA4Ljk4OTY3MzU3NFoifSwidmFsdWVpbXByZXNzaW9uIjp7InVpZCI6IjlkMDgxNTVmLWQ5ZmUtNGI1OC04OThlLWUyYzU2MjgyYWIzZSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjA5LjA2NzgzOTE2NFoifSwidmlzeCI6eyJ1aWQiOiIyN2UwYWMzYy1iNDZlLTQxYjMtOTkyYy1mOGQyNzE0OTQ5NWUiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxMi45ODk1MjM1NzNaIn0sInlpZWxkbGFiIjp7InVpZCI6IjY5NzE0ZDlmLWZiMDAtNGE1Zi04MTljLTRiZTE5MTM2YTMyNSIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjExLjMwMzAyNjYxNVoifSwieWllbGRtbyI6eyJ1aWQiOiJnOTZjMmY3MTlmMTU1MWIzMWY2MyIsImV4cGlyZXMiOiIyMDIyLTA2LTI0VDA1OjU5OjEwLjExMDUyODYwOVoifSwieWllbGRvbmUiOnsidWlkIjoiMmE0MmZiZDMtMmM3MC00ZWI5LWIxYmQtMDQ2OTY2NTBkOTQ4IiwiZXhwaXJlcyI6IjIwMjItMDYtMjRUMDU6NTk6MTAuMzE4MzMzOTM5WiJ9LCJ6ZXJvY2xpY2tmcmF1ZCI6eyJ1aWQiOiJiOTk5NThmZS0yYTg3LTJkYTQtOWNjNC05NjFmZDExM2JlY2UiLCJleHBpcmVzIjoiMjAyMi0wNi0yNFQwNTo1OToxNS43MTk1OTQ1NjZaIn19LCJiZGF5IjoiMjAyMi0wNS0xN1QwNjo0ODozOC4wMTc5ODgyMDZaIn0=`, + }, + KADUSERCookie: &http.Cookie{ + Name: "KADUSERCOOKIE", + Value: `7D75D25F-FAC9-443D-B2D1-B17FEE11E027`, + }, + OriginCookie: "go-test", + Endpoint: models.EndpointJson, + Method: "POST", + Aliases: make(map[string]string), + ImpBidCtx: make(map[string]models.ImpCtx), + PrebidBidderCode: make(map[string]string), + BidderResponseTimeMillis: make(map[string]int), + SeatNonBids: make(map[string][]openrtb_ext.NonBid), + MetricsEngine: mockEngine, + AdruleFlag: true, + AdpodProfileConfig: nil, + ImpAdPodConfig: make(map[string][]models.PodConfig), + }, + }, + }, + bidrequest: json.RawMessage(`{"id":"1559039248176","test":0,"at":1,"tmax":500,"source":{"fd":1,"tid":"1559039248176","pchain":"pchaintagid"},"imp":[{"id":"28635736ddc2bb2","ext":{"bidder":{"appnexus":{"keywords":[],"dealtier":{"prefix":"apnx","mindealtier":4}}}},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"video":{"mimes":["video/3gpp","video/mp4"],"minduration":10,"maxduration":40,"protocols":[2,3,5,6],"w":280,"h":360,"startdelay":1,"placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3],"playbackend":1}}],"site":{"id":"123","name":"EbayShopping","domain":"ebay.com","cat":["IAB1-5"],"sectioncat":["IAB1-5"],"pagecat":["IAB1-5"],"page":"http://ebay.com/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?appnexus_deal_priority=6&appnexus_video_fixedbid=10&videobid=4&cat=IAB20-3","ref":"http://ebay.com/home","search":"NewCloths","mobile":1,"privacypolicy":1,"keywords":"Cloths","publisher":{"id":"5890","name":"Test Publisher","cat":["IAB1-5"],"domain":"publisher.com"},"content":{"id":"381d2e0b-548d-4f27-bfdd-e6e66f43557e","episode":1,"title":"StarWars","series":"StarWars","season":"Season3","artist":"GeorgeLucas","genre":"Action","album":"Action","isrc":"2","url":"http://www.pubmatic.com/test/","cat":["IAB1-1","IAB1-2"],"prodq":1,"videoquality":1,"context":1,"contentrating":"MPAA","userrating":"9-Stars","qagmediarating":1,"keywords":"ActionMovies","livestream":1,"sourcerelationship":1,"len":12000,"language":"en","embeddable":1,"producer":{"id":"123","name":"GaryKurtz","cat":["IAB1-5","IAB1-6"],"domain":"producer.com"}}},"device":{"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","dnt":0,"lmt":0,"ip":"172.16.8.74","ipv6":"2001:db8::8a2e:370:7334","devicetype":1,"make":"Apple","model":"iPhone X","os":"iOS","osv":"10","hwv":"10x","h":768,"w":1366,"ppi":4096,"pxratio":1.3,"js":1,"flashver":"1.1","language":"en","carrier":"VERIZON","mccmnc":"310-005","connectiontype":2,"ifa":"EA7583CD-A667-48BC-B806-42ECB2B48606","didsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","didmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","macsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","macmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606"},"user":{"id":"45067fec-eab7-4ca0-ad3a-87b01f21846a","buyeruid":"45067fec-eab7-4ca0-ad3a-87b01f21846a","yob":1990,"gender":"M","keywords":"Movies","customdata":"StarWars"},"regs":{"coppa":0,"ext":{"gdpr":1,"us_privacy":"1YNN"}},"ext":{"wrapper":{"profileid":4444,"versionid":1,"ssauction":1,"sumry_disable":0,"clientconfig":1,"supportdeals":true}}}`), + }, + fields: fields{ + cache: mockCache, + metricEngine: mockEngine, + }, + setup: func() { + mockCache.EXPECT().GetMappingsFromCacheV25(gomock.Any(), gomock.Any()).Return(map[string]models.SlotMapping{ + "adunit@0x0": { + SlotName: "adunit@0x0", + SlotMappings: map[string]interface{}{ + models.SITE_CACHE_KEY: "12313", + models.TAG_CACHE_KEY: "45343", + }, + }, + }) + mockCache.EXPECT().GetSlotToHashValueMapFromCacheV25(gomock.Any(), gomock.Any()).Return(models.SlotMappingInfo{ + OrderedSlotList: []string{"adunit@0x0"}, + HashValueMap: map[string]string{ + "adunit@0x0": "1232433543534543", + }, + }) + mockCache.EXPECT().GetPartnerConfigMap(gomock.Any(), gomock.Any(), gomock.Any()).Return(map[int]map[string]string{ + 2: { + models.PARTNER_ID: "2", + models.PREBID_PARTNER_NAME: "appnexus", + models.BidderCode: "appnexus", + models.SERVER_SIDE_FLAG: "1", + models.KEY_GEN_PATTERN: "_AU_@_W_x_H_", + models.TIMEOUT: "200", + }, + -1: { + models.DisplayVersionID: "1", + models.PLATFORM_KEY: models.PLATFORM_VIDEO, + }, + }, nil) + mockCache.EXPECT().GetAdunitConfigFromCache(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&adunitconfig.AdUnitConfig{ + ConfigPattern: "_AU_", + Config: map[string]*adunitconfig.AdConfig{ + "adunit": { + Video: &adunitconfig.Video{ + Enabled: ptrutil.ToPtr(true), + Config: &adunitconfig.VideoConfig{ + Video: openrtb2.Video{ + MIMEs: []string{"video/mp4", "video/mpeg"}, + W: ptrutil.ToPtr[int64](640), + H: ptrutil.ToPtr[int64](480), + }, + }, + }, + Adrule: []*openrtb2.Video{ + { + PodID: "dynamic-1", + PodDur: 60, + MaxSeq: 3, + MinDuration: 20, + MaxDuration: 30, + }, + { + PodID: "structured-1", + MinDuration: 15, + MaxDuration: 15, + }, + }, + }, + }, + }) + mockCache.EXPECT().GetAdpodConfig(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) + //prometheus metrics + mockEngine.EXPECT().RecordPublisherProfileRequests("5890", "4444") + mockEngine.EXPECT().RecordPublisherRequests(models.EndpointJson, "5890", "video") + mockEngine.EXPECT().RecordPlatformPublisherPartnerReqStats("video", "5890", "appnexus") + mockEngine.EXPECT().RecordCTVRequests("json", models.PLATFORM_DISPLAY) + mockEngine.EXPECT().RecordCTVHTTPMethodRequests("json", "5890", "POST") + mockEngine.EXPECT().RecordVideoInstlImpsStats("5890", "4444") + mockEngine.EXPECT().RecordReqImpsWithContentCount("5890", models.ContentTypeSite) + mockFeature.EXPECT().IsTBFFeatureEnabled(gomock.Any(), gomock.Any()).Return(false) + mockFeature.EXPECT().IsAnalyticsTrackingThrottled(gomock.Any(), gomock.Any()).Return(false, false) + mockProfileMetaData.EXPECT().GetProfileTypePlatform(gomock.Any()).Return(0, false) + }, + want: want{ + hookResult: hookstage.HookResult[hookstage.BeforeValidationRequestPayload]{ + Reject: false, + NbrCode: 0, + ChangeSet: hookstage.ChangeSet[hookstage.BeforeValidationRequestPayload]{}, + DebugMessages: []string{`new imp: {"28635736ddc2bb2":{"AdpodConfig":null,"AdserverURL":"","AdUnitName":"adunit","Banner":false,"BannerAdUnitCtx":{"AllowedConnectionTypes":null,"AppliedSlotAdUnitConfig":{"adrule":[{"maxduration":30,"maxseq":3,"mimes":null,"minduration":20,"poddur":60,"podid":"dynamic-1"},{"maxduration":15,"mimes":null,"minduration":15,"podid":"structured-1"}],"video":{"config":{"h":480,"mimes":["video/mp4","video/mpeg"],"w":640},"enabled":true}},"IsRegex":false,"MatchedRegex":"","MatchedSlot":"adunit","SelectedSlotAdUnitConfig":{"adrule":[{"maxduration":30,"maxseq":3,"mimes":null,"minduration":20,"poddur":60,"podid":"dynamic-1"},{"maxduration":15,"mimes":null,"minduration":15,"podid":"structured-1"}],"video":{"config":{"h":480,"mimes":["video/mp4","video/mpeg"],"w":640},"enabled":true}},"UsingDefaultConfig":false},"BidCtx":{},"BidderError":"","Bidders":{"appnexus":{"IsRegex":false,"KGP":"_AU_@_W_x_H_","KGPV":"","MatchedSlot":"adunit@0x0","Params":{"adtag":"45343","placementId":0,"site":"12313"},"PartnerID":2,"PrebidBidderCode":"appnexus","VASTTagFlags":null}},"BidFloor":1,"BidFloorCur":"USD","BidIDToAPRC":null,"BidIDToDur":null,"Div":"","ImpAdPodCfg":null,"ImpID":"28635736ddc2bb2","IncomingSlots":["280x360"],"IsAdPodRequest":false,"IsRewardInventory":null,"Native":null,"NewExt":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"adtag":"45343","placementId":0,"site":"12313"}}}},"NonMapped":{},"Secure":0,"SlotName":"adunit","TagID":"adunit","Type":"video","Video":{"api":[1,2],"battr":[5,6,7],"companiontype":[1,2,3],"delivery":[2],"h":360,"linearity":1,"maxbitrate":2000,"maxduration":40,"maxextended":10,"mimes":["video/3gpp","video/mp4"],"minbitrate":1000,"minduration":10,"placement":5,"playbackend":1,"playbackmethod":[1],"pos":7,"protocols":[2,3,5,6],"sequence":1,"skip":1,"skipafter":10,"skipmin":5,"startdelay":1,"w":280},"VideoAdUnitCtx":{"AllowedConnectionTypes":null,"AppliedSlotAdUnitConfig":{"adrule":[{"maxduration":30,"maxseq":3,"mimes":null,"minduration":20,"poddur":60,"podid":"dynamic-1"},{"maxduration":15,"mimes":null,"minduration":15,"podid":"structured-1"}],"video":{"config":{"h":480,"mimes":["video/mp4","video/mpeg"],"w":640},"enabled":true}},"IsRegex":false,"MatchedRegex":"","MatchedSlot":"adunit","SelectedSlotAdUnitConfig":{"adrule":[{"maxduration":30,"maxseq":3,"mimes":null,"minduration":20,"poddur":60,"podid":"dynamic-1"},{"maxduration":15,"mimes":null,"minduration":15,"podid":"structured-1"}],"video":{"config":{"h":480,"mimes":["video/mp4","video/mpeg"],"w":640},"enabled":true}},"UsingDefaultConfig":false}}}`, `new request.ext: {"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true,"includebrandcategory":{"primaryadserver":0,"publisher":"","withcategory":false,"translatecategories":false,"skipdedup":true}},"macros":{"[PLATFORM]":"2","[PROFILE_ID]":"4444","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}`}, + AnalyticsTags: hookanalytics.Analytics{}, + }, + bidRequest: json.RawMessage(`{"id":"1559039248176","imp":[{"id":"28635736ddc2bb2-dynamic-1-0","video":{"mimes":["video/3gpp","video/mp4"],"minduration":20,"maxduration":30,"startdelay":1,"maxseq":3,"poddur":60,"protocols":[2,3,5,6],"w":280,"h":360,"podid":"dynamic-1","placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"playbackend":1,"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3]},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}},{"id":"28635736ddc2bb2-structured-1-1","video":{"mimes":["video/3gpp","video/mp4"],"minduration":15,"maxduration":15,"startdelay":1,"protocols":[2,3,5,6],"w":280,"h":360,"podid":"structured-1","placement":5,"linearity":1,"skip":1,"skipmin":5,"skipafter":10,"sequence":1,"battr":[5,6,7],"maxextended":10,"minbitrate":1000,"maxbitrate":2000,"playbackmethod":[1],"playbackend":1,"delivery":[2],"pos":7,"api":[1,2],"companiontype":[1,2,3]},"displaymanager":"PubMaticSDK","displaymanagerver":"PubMaticSDK-1.0","instl":1,"tagid":"adunit","bidfloor":1,"bidfloorcur":"USD","secure":0,"iframebuster":["1"],"exp":1,"ext":{"data":{"pbadslot":"adunit"},"prebid":{"bidder":{"appnexus":{"placementId":0,"site":"12313","adtag":"45343"}}}}}],"site":{"id":"123","name":"EbayShopping","domain":"ebay.com","cat":["IAB1-5"],"sectioncat":["IAB1-5"],"pagecat":["IAB1-5"],"page":"http://ebay.com/inte/automation/s2s/pwt_parameter_validation_muti_slot_multi_size.html?appnexus_deal_priority=6\u0026appnexus_video_fixedbid=10\u0026videobid=4\u0026cat=IAB20-3","ref":"http://ebay.com/home","search":"NewCloths","mobile":1,"privacypolicy":1,"publisher":{"id":"5890","name":"Test Publisher","cat":["IAB1-5"],"domain":"publisher.com"},"content":{"id":"381d2e0b-548d-4f27-bfdd-e6e66f43557e","episode":1,"title":"StarWars","series":"StarWars","season":"Season3","artist":"GeorgeLucas","genre":"Action","album":"Action","isrc":"2","producer":{"id":"123","name":"GaryKurtz","cat":["IAB1-5","IAB1-6"],"domain":"producer.com"},"url":"http://www.pubmatic.com/test/","cat":["IAB1-1","IAB1-2"],"prodq":1,"videoquality":1,"context":1,"contentrating":"MPAA","userrating":"9-Stars","qagmediarating":1,"keywords":"ActionMovies","livestream":1,"sourcerelationship":1,"len":12000,"language":"en","embeddable":1},"keywords":"Cloths"},"device":{"dnt":0,"lmt":0,"ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36","ip":"172.16.8.74","ipv6":"2001:db8::8a2e:370:7334","devicetype":1,"make":"Apple","model":"iPhone X","os":"iOS","osv":"10","hwv":"10x","h":768,"w":1366,"ppi":4096,"pxratio":1.3,"js":1,"flashver":"1.1","language":"en","carrier":"VERIZON","mccmnc":"310-005","connectiontype":2,"ifa":"EA7583CD-A667-48BC-B806-42ECB2B48606","didsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","didmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","dpidmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606","macsha1":"EA7583CD-A667-48BC-B806-42ECB2B48606","macmd5":"EA7583CD-A667-48BC-B806-42ECB2B48606"},"user":{"id":"45067fec-eab7-4ca0-ad3a-87b01f21846a","buyeruid":"45067fec-eab7-4ca0-ad3a-87b01f21846a","yob":1990,"gender":"M","keywords":"Movies","customdata":"StarWars"},"at":1,"tmax":500,"source":{"fd":1,"tid":"1559039248176","pchain":"pchaintagid"},"regs":{"ext":{"gdpr":1,"us_privacy":"1YNN"}},"ext":{"prebid":{"bidadjustmentfactors":{"appnexus":1},"bidderparams":{"pubmatic":{"wiid":""}},"debug":true,"floors":{"enforcement":{"enforcepbs":true},"enabled":true},"targeting":{"pricegranularity":{"precision":2,"ranges":[{"min":0,"max":5,"increment":0.05},{"min":5,"max":10,"increment":0.1},{"min":10,"max":20,"increment":0.5}]},"mediatypepricegranularity":{},"includewinners":true,"includebidderkeys":true,"includebrandcategory":{"primaryadserver":0,"publisher":"","withcategory":false,"translatecategories":false,"skipdedup":true}},"macros":{"[PLATFORM]":"2","[PROFILE_ID]":"4444","[PROFILE_VERSION]":"1","[UNIX_TIMESTAMP]":"0","[WRAPPER_IMPRESSION_ID]":""}}}}`), + error: false, + nilCurrencyConversion: false, + doMutate: true, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -5735,7 +6007,7 @@ func TestGetTagID(t *testing.T) { func TestUpdateImpVideoWithVideoConfig(t *testing.T) { type args struct { imp *openrtb2.Imp - configObjInVideoConfig *modelsAdunitConfig.VideoConfig + configObjInVideoConfig *adunitconfig.VideoConfig } tests := []struct { name string @@ -5749,7 +6021,7 @@ func TestUpdateImpVideoWithVideoConfig(t *testing.T) { ID: "123", Video: &openrtb2.Video{}, }, - configObjInVideoConfig: &modelsAdunitConfig.VideoConfig{ + configObjInVideoConfig: &adunitconfig.VideoConfig{ Video: openrtb2.Video{ W: ptrutil.ToPtr[int64](300), H: ptrutil.ToPtr[int64](250), @@ -5818,7 +6090,7 @@ func TestUpdateImpVideoWithVideoConfig(t *testing.T) { }, }, - configObjInVideoConfig: &modelsAdunitConfig.VideoConfig{ + configObjInVideoConfig: &adunitconfig.VideoConfig{ Video: openrtb2.Video{ W: ptrutil.ToPtr[int64](400), H: ptrutil.ToPtr[int64](300), @@ -6226,7 +6498,7 @@ func TestIsVastUnwrapEnabled(t *testing.T) { func TestSetImpBidFloorParams(t *testing.T) { type args struct { rCtx models.RequestCtx - adUnitCfg *modelsAdunitConfig.AdConfig + adUnitCfg *adunitconfig.AdConfig imp *openrtb2.Imp conversions currency.Conversions } @@ -6242,7 +6514,7 @@ func TestSetImpBidFloorParams(t *testing.T) { rCtx: models.RequestCtx{ IsMaxFloorsEnabled: false, }, - adUnitCfg: &modelsAdunitConfig.AdConfig{ + adUnitCfg: &adunitconfig.AdConfig{ BidFloor: ptrutil.ToPtr(2.0), BidFloorCur: ptrutil.ToPtr("USD"), }, @@ -6260,7 +6532,7 @@ func TestSetImpBidFloorParams(t *testing.T) { rCtx: models.RequestCtx{ IsMaxFloorsEnabled: true, }, - adUnitCfg: &modelsAdunitConfig.AdConfig{ + adUnitCfg: &adunitconfig.AdConfig{ BidFloor: ptrutil.ToPtr(2.0), BidFloorCur: ptrutil.ToPtr("USD"), }, @@ -6278,7 +6550,7 @@ func TestSetImpBidFloorParams(t *testing.T) { rCtx: models.RequestCtx{ IsMaxFloorsEnabled: true, }, - adUnitCfg: &modelsAdunitConfig.AdConfig{ + adUnitCfg: &adunitconfig.AdConfig{ BidFloor: ptrutil.ToPtr(2.0), BidFloorCur: ptrutil.ToPtr("USD"), }, diff --git a/modules/pubmatic/openwrap/cache/gocache/adpod_config_test.go b/modules/pubmatic/openwrap/cache/gocache/adpod_config_test.go index 28a5991a44f..0763a8f05a4 100644 --- a/modules/pubmatic/openwrap/cache/gocache/adpod_config_test.go +++ b/modules/pubmatic/openwrap/cache/gocache/adpod_config_test.go @@ -63,7 +63,7 @@ func TestCachePopulateCacheWithAdpodConfig(t *testing.T) { MinDuration: 10, MaxDuration: 20, PodDur: 60, - Maxseq: 3, + MaxSeq: 3, }, }, }, nil) @@ -77,7 +77,7 @@ func TestCachePopulateCacheWithAdpodConfig(t *testing.T) { MinDuration: 10, MaxDuration: 20, PodDur: 60, - Maxseq: 3, + MaxSeq: 3, }, }, }, @@ -150,7 +150,7 @@ func TestCacheGetAdpodConfigs(t *testing.T) { MinDuration: 10, MaxDuration: 20, PodDur: 60, - Maxseq: 3, + MaxSeq: 3, }, }, }, nil) @@ -162,7 +162,7 @@ func TestCacheGetAdpodConfigs(t *testing.T) { MinDuration: 10, MaxDuration: 20, PodDur: 60, - Maxseq: 3, + MaxSeq: 3, }, }, }, @@ -214,7 +214,7 @@ func TestCacheGetAdpodConfigs(t *testing.T) { MinDuration: 30, MaxDuration: 60, PodDur: 120, - Maxseq: 4, + MaxSeq: 4, }, }, } @@ -227,7 +227,7 @@ func TestCacheGetAdpodConfigs(t *testing.T) { MinDuration: 30, MaxDuration: 60, PodDur: 120, - Maxseq: 4, + MaxSeq: 4, }, }, }, diff --git a/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go b/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go index 3feb5d4d2d3..8757acc794c 100644 --- a/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go +++ b/modules/pubmatic/openwrap/database/mysql/adpod_config_test.go @@ -65,7 +65,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { MaxDuration: 60, MinDuration: 1, PodDur: 180, - Maxseq: 5, + MaxSeq: 5, }, }, }, @@ -105,7 +105,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { MaxDuration: 60, MinDuration: 1, PodDur: 180, - Maxseq: 5, + MaxSeq: 5, }, }, }, @@ -143,8 +143,8 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { Dynamic: []adpodconfig.Dynamic{ { PodDur: 600, - Maxseq: 5, - Rqddurs: []int{6, 60, 120, 600}, + MaxSeq: 5, + RqdDurs: []int64{6, 60, 120, 600}, }, }, }, @@ -188,7 +188,7 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { MaxDuration: 60, MinDuration: 1, PodDur: 180, - Maxseq: 5, + MaxSeq: 5, }, }, Structured: []adpodconfig.Structured{ @@ -205,8 +205,8 @@ func TestMySqlDBGetAdpodConfigs(t *testing.T) { { MaxDuration: 20, MinDuration: 5, - Maxseq: ptrutil.ToPtr(3), - PodDur: ptrutil.ToPtr(60), + MaxSeq: ptrutil.ToPtr(int64(3)), + PodDur: ptrutil.ToPtr(int64(60)), }, }, }, diff --git a/modules/pubmatic/openwrap/entrypointhook.go b/modules/pubmatic/openwrap/entrypointhook.go index 95b069bf4cf..c8d3fb5679d 100644 --- a/modules/pubmatic/openwrap/entrypointhook.go +++ b/modules/pubmatic/openwrap/entrypointhook.go @@ -112,6 +112,7 @@ func (m OpenWrap) handleEntrypointHook( LoggerImpressionID: requestExtWrapper.LoggerImpressionID, ClientConfigFlag: requestExtWrapper.ClientConfigFlag, SSAI: requestExtWrapper.SSAI, + AdruleFlag: requestExtWrapper.Video.AdruleFlag, IP: models.GetIP(payload.Request), IsCTVRequest: models.IsCTVAPIRequest(payload.Request.URL.Path), TrackerEndpoint: m.cfg.Tracker.Endpoint, @@ -136,9 +137,11 @@ func (m OpenWrap) handleEntrypointHook( SendBurl: endpoint == models.EndpointAppLovinMax || getSendBurl(payload.Body), } - // SSAuction will be always 1 for CTV request if rCtx.IsCTVRequest { + // SSAuction will be always 1 for CTV request rCtx.SSAuction = 1 + + rCtx.ImpAdPodConfig = make(map[string][]models.PodConfig) } // only http.ErrNoCookie is returned, we can ignore it diff --git a/modules/pubmatic/openwrap/models/adpod.go b/modules/pubmatic/openwrap/models/adpod.go index 4b453d0616a..83f8867a91b 100644 --- a/modules/pubmatic/openwrap/models/adpod.go +++ b/modules/pubmatic/openwrap/models/adpod.go @@ -43,3 +43,12 @@ type ImpAdPodConfig struct { MinDuration int64 `json:"minduration,omitempty"` MaxDuration int64 `json:"maxduration,omitempty"` } + +type PodConfig struct { + PodID string + PodDur int64 + MaxSeq int64 + MinDuration int64 + MaxDuration int64 + RqdDurs []int64 +} diff --git a/modules/pubmatic/openwrap/models/adpodconfig/adpodconfig.go b/modules/pubmatic/openwrap/models/adpodconfig/adpodconfig.go index 948670718ce..2da4fdda974 100644 --- a/modules/pubmatic/openwrap/models/adpodconfig/adpodconfig.go +++ b/modules/pubmatic/openwrap/models/adpodconfig/adpodconfig.go @@ -7,23 +7,23 @@ type AdpodConfig struct { } type Dynamic struct { - PodDur int `json:"poddur,omitempty"` - Maxseq int `json:"maxseq,omitempty"` - MinDuration int `json:"minduration,omitempty"` - MaxDuration int `json:"maxduration,omitempty"` - Rqddurs []int `json:"rqddurs,omitempty"` + PodDur int64 `json:"poddur,omitempty"` + MaxSeq int64 `json:"maxseq,omitempty"` + MinDuration int64 `json:"minduration,omitempty"` + MaxDuration int64 `json:"maxduration,omitempty"` + RqdDurs []int64 `json:"rqddurs,omitempty"` } type Structured struct { - MinDuration int `json:"minduration,omitempty"` - MaxDuration int `json:"maxduration,omitempty"` - Rqddurs []int `json:"rqddurs,omitempty"` + MinDuration int64 `json:"minduration,omitempty"` + MaxDuration int64 `json:"maxduration,omitempty"` + RqdDurs []int64 `json:"rqddurs,omitempty"` } type Hybrid struct { - PodDur *int `json:"poddur,omitempty"` - Maxseq *int `json:"maxseq,omitempty"` - MinDuration int `json:"minduration,omitempty"` - MaxDuration int `json:"maxduration,omitempty"` - Rqddurs []int `json:"rqddurs,omitempty"` + PodDur *int64 `json:"poddur,omitempty"` + MaxSeq *int64 `json:"maxseq,omitempty"` + MinDuration int64 `json:"minduration,omitempty"` + MaxDuration int64 `json:"maxduration,omitempty"` + RqdDurs []int64 `json:"rqddurs,omitempty"` } diff --git a/modules/pubmatic/openwrap/models/openwrap.go b/modules/pubmatic/openwrap/models/openwrap.go index 135a7a0564a..1dff3e33d82 100644 --- a/modules/pubmatic/openwrap/models/openwrap.go +++ b/modules/pubmatic/openwrap/models/openwrap.go @@ -9,9 +9,13 @@ import ( "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/metrics" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/adunitconfig" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/models/nbr" + "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/ortb" "github.com/prebid/prebid-server/v2/modules/pubmatic/openwrap/wakanda" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/usersync" + "github.com/prebid/prebid-server/v2/util/maputil" + "github.com/prebid/prebid-server/v2/util/ptrutil" + "github.com/prebid/prebid-server/v2/util/sliceutil" ) type RequestCtx struct { @@ -118,6 +122,16 @@ type RequestCtx struct { PriceGranularity *openrtb_ext.PriceGranularity IsMaxFloorsEnabled bool SendBurl bool + + // Adpod + AdruleFlag bool + AdpodProfileConfig *AdpodProfileConfig + ImpAdPodConfig map[string][]PodConfig +} + +type AdpodProfileConfig struct { + AdserverCreativeDurations []int `json:"videoadduration,omitempty"` //Range of ad durations allowed in the response + AdserverCreativeDurationMatchingPolicy string `json:"videoaddurationmatching,omitempty"` //Flag indicating exact ad duration requirement. (default)empty/exact/round. } type OwBid struct { @@ -168,7 +182,7 @@ type ImpCtx struct { //temp BidderError string - // CTV + // Adpod IsAdPodRequest bool AdpodConfig *AdPod ImpAdPodCfg []*ImpAdPodConfig @@ -283,3 +297,17 @@ func IsNewWinningBid(bid, wbid *OwBid, preferDeals bool) bool { bid.Nbr = nbr.LossBidLostToHigherBid.Ptr() return false } + +func (ic *ImpCtx) DeepCopy() ImpCtx { + impCtx := *ic + impCtx.IsRewardInventory = ptrutil.Clone(ic.IsRewardInventory) + impCtx.Video = ortb.DeepCopyImpVideo(ic.Video) + impCtx.Native = ortb.DeepCopyImpNative(ic.Native) + impCtx.IncomingSlots = sliceutil.Clone(ic.IncomingSlots) + impCtx.Bidders = maputil.Clone(ic.Bidders) + impCtx.NonMapped = maputil.Clone(ic.NonMapped) + impCtx.NewExt = sliceutil.Clone(ic.NewExt) + impCtx.BidCtx = maputil.Clone(ic.BidCtx) + + return impCtx +} diff --git a/modules/pubmatic/openwrap/models/request.go b/modules/pubmatic/openwrap/models/request.go index 17051bfe2b3..f6582500905 100644 --- a/modules/pubmatic/openwrap/models/request.go +++ b/modules/pubmatic/openwrap/models/request.go @@ -25,14 +25,12 @@ type ExtRequestAdPod struct { // AdPod holds Video AdPod specific extension parameters at impression level type AdPod struct { - MinAds int `json:"minads,omitempty"` //Default 1 if not specified - MaxAds int `json:"maxads,omitempty"` //Default 1 if not specified - MinDuration int `json:"adminduration,omitempty"` // (adpod.adminduration * adpod.minads) should be greater than or equal to video.minduration - MaxDuration int `json:"admaxduration,omitempty"` // (adpod.admaxduration * adpod.maxads) should be less than or equal to video.maxduration + video.maxextended - AdvertiserExclusionPercent *int `json:"excladv,omitempty"` // Percent value 0 means none of the ads can be from same advertiser 100 means can have all same advertisers - IABCategoryExclusionPercent *int `json:"excliabcat,omitempty"` // Percent value 0 means all ads should be of different IAB categories. - VideoAdDuration []int `json:"videoadduration,omitempty"` //Range of ad durations allowed in the response - VideoAdDurationMatching string `json:"videoaddurationmatching,omitempty"` //Flag indicating exact ad duration requirement. (default)empty/exact/round. + MinAds int `json:"minads,omitempty"` //Default 1 if not specified + MaxAds int `json:"maxads,omitempty"` //Default 1 if not specified + MinDuration int `json:"adminduration,omitempty"` // (adpod.adminduration * adpod.minads) should be greater than or equal to video.minduration + MaxDuration int `json:"admaxduration,omitempty"` // (adpod.admaxduration * adpod.maxads) should be less than or equal to video.maxduration + video.maxextended + AdvertiserExclusionPercent *int `json:"excladv,omitempty"` // Percent value 0 means none of the ads can be from same advertiser 100 means can have all same advertisers + IABCategoryExclusionPercent *int `json:"excliabcat,omitempty"` // Percent value 0 means all ads should be of different IAB categories. } // ImpExtension - Impression Extension @@ -109,9 +107,14 @@ type RequestExtWrapper struct { LoggerImpressionID string `json:"wiid,omitempty"` SSAI string `json:"ssai,omitempty"` KeyValues map[string]interface{} `json:"kv,omitempty"` + Video ExtRequestWrapperVideo `json:"video,omitempty"` PubId int `json:"-"` } +type ExtRequestWrapperVideo struct { + AdruleFlag bool `json:"adrule,omitempty"` +} + type BidderWrapper struct { Flag bool VASTagFlags map[string]bool diff --git a/modules/pubmatic/openwrap/ortb/imp.go b/modules/pubmatic/openwrap/ortb/imp.go new file mode 100644 index 00000000000..9a2c64aea36 --- /dev/null +++ b/modules/pubmatic/openwrap/ortb/imp.go @@ -0,0 +1,115 @@ +package ortb + +import ( + "github.com/PubMatic-OpenWrap/prebid-server/v2/util/ptrutil" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/util/sliceutil" +) + +func DeepCloneImpression(imp *openrtb2.Imp) *openrtb2.Imp { + clone := *imp + + clone.Metric = sliceutil.Clone(imp.Metric) + clone.Banner = DeepCopyImpBanner(imp.Banner) + clone.Video = DeepCopyImpVideo(imp.Video) + clone.Audio = DeepCopyImpAudio(imp.Audio) + clone.Native = DeepCopyImpNative(imp.Native) + clone.PMP = DeepCopyImpPMP(imp.PMP) + clone.ClickBrowser = ptrutil.Clone(imp.ClickBrowser) + clone.Secure = ptrutil.Clone(imp.Secure) + clone.IframeBuster = sliceutil.Clone(imp.IframeBuster) + clone.Qty = ptrutil.Clone(imp.Qty) + clone.Refresh = ptrutil.Clone(imp.Refresh) + clone.Ext = sliceutil.Clone(imp.Ext) + return &clone +} + +func DeepCopyImpVideo(video *openrtb2.Video) *openrtb2.Video { + if video == nil { + return nil + } + + videoCopy := *video + videoCopy.MIMEs = sliceutil.Clone(video.MIMEs) + videoCopy.StartDelay = ptrutil.Clone(video.StartDelay) + videoCopy.Protocols = sliceutil.Clone(video.Protocols) + videoCopy.W = ptrutil.Clone(video.W) + videoCopy.H = ptrutil.Clone(video.H) + videoCopy.RqdDurs = sliceutil.Clone(video.RqdDurs) + videoCopy.Skip = ptrutil.Clone(video.Skip) + videoCopy.BAttr = sliceutil.Clone(video.BAttr) + videoCopy.BoxingAllowed = ptrutil.Clone(video.BoxingAllowed) + videoCopy.PlaybackMethod = sliceutil.Clone(video.PlaybackMethod) + videoCopy.Delivery = sliceutil.Clone(video.Delivery) + videoCopy.Pos = ptrutil.Clone(video.Pos) + videoCopy.CompanionAd = sliceutil.Clone(video.CompanionAd) + videoCopy.API = sliceutil.Clone(video.API) + videoCopy.CompanionType = sliceutil.Clone(video.CompanionType) + videoCopy.DurFloors = sliceutil.Clone(video.DurFloors) + videoCopy.Ext = sliceutil.Clone(video.Ext) + return &videoCopy +} + +func DeepCopyImpNative(native *openrtb2.Native) *openrtb2.Native { + if native == nil { + return nil + } + + nativeCopy := *native + nativeCopy.API = sliceutil.Clone(native.API) + nativeCopy.BAttr = sliceutil.Clone(native.BAttr) + nativeCopy.Ext = sliceutil.Clone(native.Ext) + return &nativeCopy +} + +func DeepCopyImpBanner(banner *openrtb2.Banner) *openrtb2.Banner { + if banner == nil { + return nil + } + + bannerCopy := *banner + bannerCopy.Format = sliceutil.Clone(banner.Format) + bannerCopy.W = ptrutil.Clone(banner.W) + bannerCopy.H = ptrutil.Clone(banner.H) + bannerCopy.BType = sliceutil.Clone(banner.BType) + bannerCopy.BAttr = sliceutil.Clone(banner.BAttr) + bannerCopy.MIMEs = sliceutil.Clone(banner.MIMEs) + bannerCopy.ExpDir = sliceutil.Clone(banner.ExpDir) + bannerCopy.API = sliceutil.Clone(banner.API) + bannerCopy.Vcm = ptrutil.Clone(banner.Vcm) + bannerCopy.Ext = sliceutil.Clone(banner.Ext) + return &bannerCopy +} + +func DeepCopyImpAudio(audio *openrtb2.Audio) *openrtb2.Audio { + if audio == nil { + return nil + } + + audioCopy := *audio + audioCopy.MIMEs = sliceutil.Clone(audio.MIMEs) + audioCopy.Protocols = sliceutil.Clone(audio.Protocols) + audioCopy.StartDelay = ptrutil.Clone(audio.StartDelay) + audioCopy.RqdDurs = sliceutil.Clone(audio.RqdDurs) + audioCopy.BAttr = sliceutil.Clone(audio.BAttr) + audioCopy.Delivery = sliceutil.Clone(audio.Delivery) + audioCopy.CompanionAd = sliceutil.Clone(audio.CompanionAd) + audioCopy.API = sliceutil.Clone(audio.API) + audioCopy.CompanionType = sliceutil.Clone(audio.CompanionType) + audioCopy.Stitched = ptrutil.Clone(audio.Stitched) + audioCopy.NVol = ptrutil.Clone(audio.NVol) + audioCopy.DurFloors = sliceutil.Clone(audio.DurFloors) + audioCopy.Ext = sliceutil.Clone(audio.Ext) + return &audioCopy +} + +func DeepCopyImpPMP(pmp *openrtb2.PMP) *openrtb2.PMP { + if pmp == nil { + return nil + } + + pmpCopy := *pmp + pmpCopy.Deals = sliceutil.Clone(pmp.Deals) + pmpCopy.Ext = sliceutil.Clone(pmp.Ext) + return &pmpCopy +} diff --git a/modules/pubmatic/openwrap/processedauctionhook.go b/modules/pubmatic/openwrap/processedauctionhook.go index 5d381d36194..2ecc1b7c5f0 100644 --- a/modules/pubmatic/openwrap/processedauctionhook.go +++ b/modules/pubmatic/openwrap/processedauctionhook.go @@ -39,7 +39,7 @@ func (m OpenWrap) HandleProcessedAuctionHook( var imps []*openrtb_ext.ImpWrapper var errs []error if rctx.IsCTVRequest { - imps, errs = impressions.GenerateImpressions(payload.Request, rctx.ImpBidCtx, rctx.PubIDStr, m.metricEngine) + imps, errs = impressions.GenerateImpressions(payload.Request, rctx.ImpBidCtx, rctx.AdpodProfileConfig, rctx.PubIDStr, m.metricEngine) if len(errs) > 0 { for i := range errs { result.Warnings = append(result.Warnings, errs[i].Error())