github.com/prebid/prebid-server@v0.275.0/stored_responses/stored_responses.go (about)

     1  package stored_responses
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/buger/jsonparser"
     9  	"github.com/prebid/openrtb/v19/openrtb2"
    10  	"github.com/prebid/prebid-server/openrtb_ext"
    11  	"github.com/prebid/prebid-server/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 []ImpExtPrebidData,
    60  	bidderMap map[string]openrtb_ext.BidderName) (
    61  	StoredResponseIDs,
    62  	ImpBiddersWithBidResponseIDs,
    63  	ImpsWithAuctionResponseIDs,
    64  	ImpBidderReplaceImpID,
    65  	error,
    66  ) {
    67  	// extractStoredResponsesIds returns:
    68  	// 1) all stored responses ids from all imps
    69  	allStoredResponseIDs := StoredResponseIDs{}
    70  	// 2) stored bid responses: imp id to bidder to stored response id
    71  	impBiddersWithBidResponseIDs := ImpBiddersWithBidResponseIDs{}
    72  	// 3) imp id to stored resp id
    73  	impAuctionResponseIDs := ImpsWithAuctionResponseIDs{}
    74  	// 4) imp id to bidder to bool replace imp in response
    75  	impBidderReplaceImp := ImpBidderReplaceImpID{}
    76  
    77  	for index, impData := range impInfo {
    78  		impId, err := jsonparser.GetString(impData.Imp, "id")
    79  		if err != nil {
    80  			return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)
    81  		}
    82  
    83  		if impData.ImpExtPrebid.StoredAuctionResponse != nil {
    84  			if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 {
    85  				return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index)
    86  			}
    87  			allStoredResponseIDs = append(allStoredResponseIDs, impData.ImpExtPrebid.StoredAuctionResponse.ID)
    88  
    89  			impAuctionResponseIDs[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID
    90  
    91  		}
    92  		if len(impData.ImpExtPrebid.StoredBidResponse) > 0 {
    93  
    94  			bidderStoredRespId := make(map[string]string)
    95  			bidderReplaceImpId := make(map[string]bool)
    96  			for _, bidderResp := range impData.ImpExtPrebid.StoredBidResponse {
    97  				if len(bidderResp.ID) == 0 || len(bidderResp.Bidder) == 0 {
    98  					return nil, nil, nil, nil, fmt.Errorf("request.imp[%d] has ext.prebid.storedbidresponse specified, but \"id\" or/and \"bidder\" fields are missing ", index)
    99  				}
   100  				//check if bidder is valid/exists
   101  				if _, isValid := bidderMap[bidderResp.Bidder]; !isValid {
   102  					return nil, nil, nil, nil, fmt.Errorf("request.imp[impId: %s].ext.prebid.bidder contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impId, bidderResp.Bidder)
   103  				}
   104  				// bidder is unique per one bid stored response
   105  				// if more than one bidder specified the last defined bidder id will take precedence
   106  				bidderStoredRespId[bidderResp.Bidder] = bidderResp.ID
   107  				impBiddersWithBidResponseIDs[impId] = bidderStoredRespId
   108  
   109  				// stored response config can specify if imp id should be replaced with imp id from request
   110  				replaceImpId := true
   111  				if bidderResp.ReplaceImpId != nil {
   112  					// replaceimpid is true if not specified
   113  					replaceImpId = *bidderResp.ReplaceImpId
   114  				}
   115  				bidderReplaceImpId[bidderResp.Bidder] = replaceImpId
   116  				impBidderReplaceImp[impId] = bidderReplaceImpId
   117  
   118  				//storedAuctionResponseIds are not unique, but fetch will return single data for repeated ids
   119  				allStoredResponseIDs = append(allStoredResponseIDs, bidderResp.ID)
   120  			}
   121  		}
   122  	}
   123  	return allStoredResponseIDs, impBiddersWithBidResponseIDs, impAuctionResponseIDs, impBidderReplaceImp, nil
   124  }
   125  
   126  // ProcessStoredResponses takes the incoming request as JSON with any
   127  // stored requests/imps already merged into it, scans it to find any stored auction response ids and stored bid response ids
   128  // in the request/imps and produces a map of imp IDs to stored auction responses and map of imp to bidder to stored response.
   129  // Note that processStoredResponses must be called after processStoredRequests
   130  // because stored imps and stored requests can contain stored auction responses and stored bid responses
   131  // so the stored requests/imps have to be merged into the incoming request prior to processing stored auction responses.
   132  func ProcessStoredResponses(ctx context.Context, requestJson []byte, storedRespFetcher stored_requests.Fetcher, bidderMap map[string]openrtb_ext.BidderName) (ImpsWithBidResponses, ImpBidderStoredResp, BidderImpReplaceImpID, []error) {
   133  	impInfo, errs := parseImpInfo(requestJson)
   134  	if len(errs) > 0 {
   135  		return nil, nil, nil, errs
   136  	}
   137  	storedResponsesIds, impBidderToStoredBidResponseId, impIdToRespId, impBidderReplaceImp, err := extractStoredResponsesIds(impInfo, bidderMap)
   138  	if err != nil {
   139  		return nil, nil, nil, append(errs, err)
   140  	}
   141  
   142  	if len(storedResponsesIds) > 0 {
   143  		storedResponses, errs := storedRespFetcher.FetchResponses(ctx, storedResponsesIds)
   144  		if len(errs) > 0 {
   145  			return nil, nil, nil, errs
   146  		}
   147  		bidderImpIdReplaceImp := flipMap(impBidderReplaceImp)
   148  
   149  		impIdToStoredResp, impBidderToStoredBidResponse, errs := buildStoredResponsesMaps(storedResponses, impBidderToStoredBidResponseId, impIdToRespId)
   150  
   151  		return impIdToStoredResp, impBidderToStoredBidResponse, bidderImpIdReplaceImp, errs
   152  	}
   153  	return nil, nil, nil, nil
   154  }
   155  
   156  // flipMap takes map[impID][bidderName]replaceImpId and modifies it to map[bidderName][impId]replaceImpId
   157  func flipMap(impBidderReplaceImpId ImpBidderReplaceImpID) BidderImpReplaceImpID {
   158  	flippedMap := BidderImpReplaceImpID{}
   159  	for impId, impData := range impBidderReplaceImpId {
   160  		for bidder, replaceImpId := range impData {
   161  			if _, ok := flippedMap[bidder]; !ok {
   162  				flippedMap[bidder] = make(map[string]bool)
   163  			}
   164  			flippedMap[bidder][impId] = replaceImpId
   165  		}
   166  	}
   167  	return flippedMap
   168  }
   169  
   170  func buildStoredResponsesMaps(storedResponses StoredResponseIdToStoredResponse, impBidderToStoredBidResponseId ImpBiddersWithBidResponseIDs, impIdToRespId ImpsWithAuctionResponseIDs) (ImpsWithBidResponses, ImpBidderStoredResp, []error) {
   171  	var errs []error
   172  	//imp id to stored resp body
   173  	impIdToStoredResp := ImpsWithBidResponses{}
   174  	//stored bid responses: imp id to bidder to stored response body
   175  	impBidderToStoredBidResponse := ImpBidderStoredResp{}
   176  
   177  	for impId, respId := range impIdToRespId {
   178  		if len(storedResponses[respId]) == 0 {
   179  			errs = append(errs, fmt.Errorf("failed to fetch stored auction response for impId = %s and storedAuctionResponse id = %s", impId, respId))
   180  		} else {
   181  			impIdToStoredResp[impId] = storedResponses[respId]
   182  		}
   183  	}
   184  
   185  	for impId, bidderStoredResp := range impBidderToStoredBidResponseId {
   186  		bidderStoredResponses := StoredResponseIdToStoredResponse{}
   187  		for bidderName, id := range bidderStoredResp {
   188  			if len(storedResponses[id]) == 0 {
   189  				errs = append(errs, fmt.Errorf("failed to fetch stored bid response for impId = %s, bidder = %s and storedBidResponse id = %s", impId, bidderName, id))
   190  			} else {
   191  				bidderStoredResponses[bidderName] = storedResponses[id]
   192  			}
   193  		}
   194  		impBidderToStoredBidResponse[impId] = bidderStoredResponses
   195  	}
   196  	return impIdToStoredResp, impBidderToStoredBidResponse, errs
   197  }
   198  
   199  // parseImpInfo parses the request JSON and returns the impressions with their unmarshalled imp.ext.prebid
   200  // copied from exchange to isolate stored responses code from auction dependencies
   201  func parseImpInfo(requestJson []byte) (impData []ImpExtPrebidData, errs []error) {
   202  
   203  	if impArray, dataType, _, err := jsonparser.Get(requestJson, "imp"); err == nil && dataType == jsonparser.Array {
   204  		_, err = jsonparser.ArrayEach(impArray, func(imp []byte, _ jsonparser.ValueType, _ int, err error) {
   205  			impExtData, _, _, err := jsonparser.Get(imp, "ext", "prebid")
   206  			var impExtPrebid openrtb_ext.ExtImpPrebid
   207  			if impExtData != nil {
   208  				if err := json.Unmarshal(impExtData, &impExtPrebid); err != nil {
   209  					errs = append(errs, err)
   210  				}
   211  			}
   212  			newImpData := ImpExtPrebidData{imp, impExtPrebid}
   213  			impData = append(impData, newImpData)
   214  		})
   215  	}
   216  	return
   217  }
   218  
   219  type ImpExtPrebidData struct {
   220  	Imp          json.RawMessage
   221  	ImpExtPrebid openrtb_ext.ExtImpPrebid
   222  }