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

     1  package stored_responses
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    11  	"github.com/prebid/prebid-server/v2/stored_requests"
    12  )
    13  
    14  type ImpsWithAuctionResponseIDs map[string]string
    15  type ImpBiddersWithBidResponseIDs map[string]map[string]string
    16  type StoredResponseIDs []string
    17  type StoredResponseIdToStoredResponse map[string]json.RawMessage
    18  type BidderImpsWithBidResponses map[openrtb_ext.BidderName]map[string]json.RawMessage
    19  type ImpsWithBidResponses map[string]json.RawMessage
    20  type ImpBidderStoredResp map[string]map[string]json.RawMessage
    21  type ImpBidderReplaceImpID map[string]map[string]bool
    22  type BidderImpReplaceImpID map[string]map[string]bool
    23  
    24  func InitStoredBidResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
    25  	removeImpsWithStoredResponses(req, storedBidResponses)
    26  	return buildStoredResp(storedBidResponses)
    27  }
    28  
    29  // removeImpsWithStoredResponses deletes imps with stored bid resp
    30  func removeImpsWithStoredResponses(req *openrtb2.BidRequest, storedBidResponses ImpBidderStoredResp) {
    31  	imps := req.Imp
    32  	req.Imp = nil //to indicate this bidder doesn't have real requests
    33  	for _, imp := range imps {
    34  		if _, ok := storedBidResponses[imp.ID]; !ok {
    35  			//add real imp back to request
    36  			req.Imp = append(req.Imp, imp)
    37  		}
    38  	}
    39  }
    40  
    41  func buildStoredResp(storedBidResponses ImpBidderStoredResp) BidderImpsWithBidResponses {
    42  	// bidder -> imp id -> stored bid resp
    43  	bidderToImpToResponses := BidderImpsWithBidResponses{}
    44  	for impID, storedData := range storedBidResponses {
    45  		for bidderName, storedResp := range storedData {
    46  			if _, ok := bidderToImpToResponses[openrtb_ext.BidderName(bidderName)]; !ok {
    47  				//new bidder with stored bid responses
    48  				impToStoredResp := ImpsWithBidResponses{}
    49  				impToStoredResp[impID] = storedResp
    50  				bidderToImpToResponses[openrtb_ext.BidderName(bidderName)] = impToStoredResp
    51  			} else {
    52  				bidderToImpToResponses[openrtb_ext.BidderName(bidderName)][impID] = storedResp
    53  			}
    54  		}
    55  	}
    56  	return bidderToImpToResponses
    57  }
    58  
    59  func extractStoredResponsesIds(impInfo []*openrtb_ext.ImpWrapper) (
    60  	StoredResponseIDs,
    61  	ImpBiddersWithBidResponseIDs,
    62  	ImpsWithAuctionResponseIDs,
    63  	ImpBidderReplaceImpID,
    64  	error,
    65  ) {
    66  	// extractStoredResponsesIds returns:
    67  	// 1) all stored responses ids from all imps
    68  	allStoredResponseIDs := StoredResponseIDs{}
    69  	// 2) stored bid responses: imp id to bidder to stored response id
    70  	impBiddersWithBidResponseIDs := ImpBiddersWithBidResponseIDs{}
    71  	// 3) imp id to stored resp id
    72  	impAuctionResponseIDs := ImpsWithAuctionResponseIDs{}
    73  	// 4) imp id to bidder to bool replace imp in response
    74  	impBidderReplaceImp := ImpBidderReplaceImpID{}
    75  
    76  	for index, impData := range impInfo {
    77  		impId := impData.ID
    78  		impExt, err := impData.GetImpExt()
    79  		if err != nil {
    80  			return nil, nil, nil, nil, err
    81  		}
    82  		impExtPrebid := impExt.GetPrebid()
    83  		if impExtPrebid == nil {
    84  			continue
    85  		}
    86  
    87  		if impExtPrebid.StoredAuctionResponse != nil {
    88  			if len(impExtPrebid.StoredAuctionResponse.ID) == 0 {
    89  				return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index)
    90  			}
    91  			allStoredResponseIDs = append(allStoredResponseIDs, impExtPrebid.StoredAuctionResponse.ID)
    92  
    93  			impAuctionResponseIDs[impId] = impExtPrebid.StoredAuctionResponse.ID
    94  
    95  		}
    96  		if len(impExtPrebid.StoredBidResponse) > 0 {
    97  
    98  			// bidders can be specified in imp.ext and in imp.ext.prebid.bidders
    99  			allBidderNames := make([]string, 0)
   100  			for bidderName := range impExtPrebid.Bidder {
   101  				allBidderNames = append(allBidderNames, bidderName)
   102  			}
   103  			for extData := range impExt.GetExt() {
   104  				// no bidders will not be processed
   105  				allBidderNames = append(allBidderNames, extData)
   106  			}
   107  
   108  			bidderStoredRespId := make(map[string]string)
   109  			bidderReplaceImpId := make(map[string]bool)
   110  			for _, bidderResp := range impExtPrebid.StoredBidResponse {
   111  				if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 {
   112  					return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index)
   113  				}
   114  
   115  				for _, bidderName := range allBidderNames {
   116  					if _, found := bidderStoredRespId[bidderName]; !found && strings.EqualFold(bidderName, bidderResp.Bidder) {
   117  						bidderStoredRespId[bidderName] = bidderResp.ID
   118  						impBiddersWithBidResponseIDs[impId] = bidderStoredRespId
   119  
   120  						// stored response config can specify if imp id should be replaced with imp id from request
   121  						replaceImpId := true
   122  						if bidderResp.ReplaceImpId != nil {
   123  							// replaceimpid is true if not specified
   124  							replaceImpId = *bidderResp.ReplaceImpId
   125  						}
   126  						bidderReplaceImpId[bidderName] = replaceImpId
   127  						impBidderReplaceImp[impId] = bidderReplaceImpId
   128  
   129  						//storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids
   130  						allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID)
   131  					}
   132  				}
   133  			}
   134  		}
   135  	}
   136  	return allStoredResponseIDs, impBiddersWithBidResponseIDs, impAuctionResponseIDs, impBidderReplaceImp, nil
   137  }
   138  
   139  // ProcessStoredResponses takes the incoming request as JSON with any
   140  // stored requests/imps already merged into it, scans it to find any stored auction response ids and stored bid response ids
   141  // in the request/imps and produces a map of imp IDs to stored auction responses and map of imp to bidder to stored response.
   142  // Note that processStoredResponses must be called after processStoredRequests
   143  // because stored imps and stored requests can contain stored auction responses and stored bid responses
   144  // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses.
   145  func ProcessStoredResponses(ctx context.Context, requestWrapper *openrtb_ext.RequestWrapper, storedRespFetcher stored_requests.Fetcher) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) {
   146  
   147  	storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(requestWrapper.GetImp())
   148  	if err != nil {
   149  		return nil, nil, nil, []error{err}
   150  	}
   151  
   152  	if len(storedResponsesIds) > 0 {
   153  		storedResponses, errs := storedRespFetcher.FetchResponses(ctx, storedResponsesIds)
   154  		if len(errs) > 0 {
   155  			return nil, nil, nil, errs
   156  		}
   157  		bidderImpIdReplaceImp := flipMap(impBidderReplaceImp)
   158  
   159  		impIdToStoredResp, impBidderToStoredBidResponse, errs := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId)
   160  
   161  		return impIdToStoredResp, impBidderToStoredBidResponse, bidderImpIdReplaceImp, errs
   162  	}
   163  	return nil, nil, nil, nil
   164  }
   165  
   166  // flipMap takes map[impID][bidderName]replaceImpId and modifies it to map[bidderName][impId]replaceImpId
   167  func flipMap(impBidderReplaceImpId ImpBidderReplaceImpID) BidderImpReplaceImpID {
   168  	flippedMap := BidderImpReplaceImpID{}
   169  	for impId, impData := range impBidderReplaceImpId {
   170  		for bidder, replaceImpId := range impData {
   171  			if _, ok := flippedMap[bidder]; !ok {
   172  				flippedMap[bidder] = make(map[string]bool)
   173  			}
   174  			flippedMap[bidder][impId] = replaceImpId
   175  		}
   176  	}
   177  	return flippedMap
   178  }
   179  
   180  func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp, []error) {
   181  	var errs []error
   182  	//imp id to stored resp body
   183  	impIdToStoredResp := ImpsWithBidResponses{}
   184  	//stored bid responses: imp id to bidder to stored response body
   185  	impBidderToStoredBidResponse := ImpBidderStoredResp{}
   186  
   187  	for impId, respId := range impIdToRespId {
   188  		if len(storedResponses[respId]) == 0 {
   189  			errs = append(errs, fmt.Errorf("failed to fetch stored auction response for impId = %s and storedAuctionResponse id = %s", impId, respId))
   190  		} else {
   191  			impIdToStoredResp[impId] = storedResponses[respId]
   192  		}
   193  	}
   194  
   195  	for impId, bidderStoredResp := range impBidderToStoredBidResponseId {
   196  		bidderStoredResponses := StoredResponseIdToStoredResponse{}
   197  		for bidderName, id := range bidderStoredResp {
   198  			if len(storedResponses[id]) == 0 {
   199  				errs = append(errs, fmt.Errorf("failed to fetch stored bid response for impId = %s, bidder = %s and storedBidResponse id = %s", impId, bidderName, id))
   200  			} else {
   201  				bidderStoredResponses[bidderName] = storedResponses[id]
   202  			}
   203  		}
   204  		impBidderToStoredBidResponse[impId] = bidderStoredResponses
   205  	}
   206  	return impIdToStoredResp, impBidderToStoredBidResponse, errs
   207  }