github.com/prebid/prebid-server/v2@v2.18.0/adservertargeting/adservertargeting.go (about)

     1  package adservertargeting
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/url"
     6  	"strings"
     7  
     8  	"github.com/buger/jsonparser"
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    11  )
    12  
    13  type DataSource string
    14  
    15  const (
    16  	SourceBidRequest  DataSource = "bidrequest"
    17  	SourceStatic      DataSource = "static"
    18  	SourceBidResponse DataSource = "bidresponse"
    19  )
    20  
    21  const (
    22  	bidderMacro   = "{{BIDDER}}"
    23  	pathDelimiter = "."
    24  )
    25  
    26  var (
    27  	allowedTypes = []jsonparser.ValueType{jsonparser.String, jsonparser.Number}
    28  )
    29  
    30  // RequestTargetingData struct to hold pre-processed ad server targeting keys and values
    31  type RequestTargetingData struct {
    32  	SingleVal             json.RawMessage
    33  	TargetingValueByImpId map[string][]byte
    34  }
    35  
    36  type ResponseTargetingData struct {
    37  	Key      string
    38  	HasMacro bool
    39  	Path     string
    40  }
    41  
    42  type adServerTargetingData struct {
    43  	RequestTargetingData  map[string]RequestTargetingData
    44  	ResponseTargetingData []ResponseTargetingData
    45  }
    46  
    47  func Apply(
    48  	reqWrapper *openrtb_ext.RequestWrapper,
    49  	resolvedRequest json.RawMessage,
    50  	response *openrtb2.BidResponse,
    51  	queryParams url.Values,
    52  	bidResponseExt *openrtb_ext.ExtBidResponse,
    53  	truncateTargetAttribute *int) *openrtb2.BidResponse {
    54  
    55  	adServerTargeting, warnings := collect(reqWrapper, resolvedRequest, queryParams)
    56  	response, warnings = resolve(adServerTargeting, response, warnings, truncateTargetAttribute)
    57  
    58  	if len(warnings) > 0 {
    59  		bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral] = append(bidResponseExt.Warnings[openrtb_ext.BidderReservedGeneral], warnings...)
    60  	}
    61  	return response
    62  }
    63  
    64  // collect gathers targeting keys and values from request based on initial config
    65  // and optimizes future key and value that should be collected from response
    66  func collect(
    67  	reqWrapper *openrtb_ext.RequestWrapper, resolvedRequest json.RawMessage,
    68  	queryParams url.Values) (*adServerTargetingData, []openrtb_ext.ExtBidderMessage) {
    69  
    70  	var warnings []openrtb_ext.ExtBidderMessage
    71  
    72  	adServerTargeting, err := getAdServerTargeting(reqWrapper)
    73  	if err != nil {
    74  		warnings = append(warnings, createWarning("unable to extract adServerTargeting from request"))
    75  		return nil, warnings
    76  	}
    77  	adServerTargeting, validationWarnings := validateAdServerTargeting(adServerTargeting)
    78  	if len(validationWarnings) > 0 {
    79  		warnings = append(warnings, validationWarnings...)
    80  	}
    81  
    82  	requestTargetingData := map[string]RequestTargetingData{}
    83  	responseTargetingData := []ResponseTargetingData{}
    84  
    85  	impsCache := requestCache{resolvedReq: resolvedRequest}
    86  
    87  	for _, targetingObj := range adServerTargeting {
    88  		source := strings.ToLower(targetingObj.Source)
    89  		switch DataSource(source) {
    90  		case SourceBidRequest:
    91  			//causes PBS to treat 'value' as a path to pull from the request object
    92  			value, err := getValueFromBidRequest(&impsCache, targetingObj.Value, queryParams)
    93  			if err != nil {
    94  				warnings = append(warnings, createWarning(err.Error()))
    95  			} else {
    96  				requestTargetingData[targetingObj.Key] = value
    97  			}
    98  		case SourceStatic:
    99  			// causes PBS to just use the 'value' provided
   100  			staticValue := RequestTargetingData{SingleVal: json.RawMessage(targetingObj.Value)}
   101  			requestTargetingData[targetingObj.Key] = staticValue
   102  		case SourceBidResponse:
   103  			//causes PBS to treat 'value' as a path to pull from the bidder's response object, specifically seatbid[j].bid[k]
   104  			bidResponseTargeting := ResponseTargetingData{}
   105  			bidResponseTargeting.Key = targetingObj.Key
   106  			bidResponseTargeting.Path = targetingObj.Value
   107  			bidResponseTargeting.HasMacro = strings.Contains(strings.ToUpper(targetingObj.Key), bidderMacro)
   108  			responseTargetingData = append(responseTargetingData, bidResponseTargeting)
   109  		}
   110  	}
   111  
   112  	adServerTargetingData := &adServerTargetingData{
   113  		RequestTargetingData:  requestTargetingData,
   114  		ResponseTargetingData: responseTargetingData,
   115  	}
   116  
   117  	return adServerTargetingData, warnings
   118  }
   119  
   120  func resolve(
   121  	adServerTargetingData *adServerTargetingData,
   122  	response *openrtb2.BidResponse,
   123  	warnings []openrtb_ext.ExtBidderMessage,
   124  	truncateTargetAttribute *int) (*openrtb2.BidResponse, []openrtb_ext.ExtBidderMessage) {
   125  
   126  	bidCache := bidsCache{bids: make(map[string]map[string][]byte)}
   127  
   128  	for _, seat := range response.SeatBid {
   129  		bidderName := seat.Seat
   130  		for i, bid := range seat.Bid {
   131  			targetingData := make(map[string]string)
   132  			processRequestTargetingData(adServerTargetingData, targetingData, bid.ImpID)
   133  			respWarnings := processResponseTargetingData(adServerTargetingData, targetingData, bidderName, bid, bidCache, response, seat.Ext)
   134  			if len(respWarnings) > 0 {
   135  				warnings = append(warnings, respWarnings...)
   136  			}
   137  			seat.Bid[i].Ext = buildBidExt(targetingData, bid, warnings, truncateTargetAttribute)
   138  		}
   139  	}
   140  	return response, warnings
   141  }