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 }