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 }