Skip to content

Commit

Permalink
Stored auction response
Browse files Browse the repository at this point in the history
  • Loading branch information
VeronikaSolovei9 committed Feb 7, 2022
1 parent ec1df96 commit d17d316
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 13 deletions.
2 changes: 1 addition & 1 deletion endpoints/openrtb2/amp_auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ func (deps *endpointDeps) parseAmpRequest(httpRequest *http.Request) (req *openr
}

// At this point, we should have a valid request that definitely has Targeting and Cache turned on
e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true)
e = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: req}, true, false)
errs = append(errs, e...)
return
}
Expand Down
75 changes: 65 additions & 10 deletions endpoints/openrtb2/auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
deps.analytics.LogAuctionObject(&ao)
}()

req, impExtInfoMap, errL := deps.parseRequest(r)
req, impExtInfoMap, storedAuctionResponses, errL := deps.parseRequest(r)
if errortypes.ContainsFatalError(errL) && writeError(errL, w, &labels) {
return
}
Expand Down Expand Up @@ -213,6 +213,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
GlobalPrivacyControlHeader: secGPC,
ImpExtInfoMap: impExtInfoMap,
FirstPartyData: resolvedFPD,
StoredAuctionResponses: storedAuctionResponses,
}

response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
Expand Down Expand Up @@ -255,7 +256,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
// possible, it will return errors with messages that suggest improvements.
//
// If the errors list has at least one element, then no guarantees are made about the returned request.
func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, errs []error) {
func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_ext.RequestWrapper, impExtInfoMap map[string]exchange.ImpExtInfo, storedAuctionResponses map[string]json.RawMessage, errs []error) {
req = &openrtb_ext.RequestWrapper{}
req.BidRequest = &openrtb2.BidRequest{}
errs = nil
Expand Down Expand Up @@ -284,14 +285,20 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_

impInfo, errs := parseImpInfo(requestJson)
if len(errs) > 0 {
return nil, nil, errs
return nil, nil, nil, errs
}

// Fetch the Stored Request data and merge it into the HTTP request.
if requestJson, impExtInfoMap, errs = deps.processStoredRequests(ctx, requestJson, impInfo); len(errs) > 0 {
return
}

//Stored auction responses should be processed after stored requests due to possible impression modification
storedAuctionResponses, errs = deps.processStoredAuctionResponses(ctx, requestJson)
if len(errs) > 0 {
return nil, nil, nil, errs
}

if err := json.Unmarshal(requestJson, req.BidRequest); err != nil {
errs = []error{err}
return
Expand All @@ -312,7 +319,7 @@ func (deps *endpointDeps) parseRequest(httpRequest *http.Request) (req *openrtb_

lmt.ModifyForIOS(req.BidRequest)

errL := deps.validateRequest(req, false)
errL := deps.validateRequest(req, false, len(storedAuctionResponses) > 0)
if len(errL) > 0 {
errs = append(errs, errL...)
}
Expand Down Expand Up @@ -469,7 +476,7 @@ func addMissingReqExtParamsInImpExt(impExtByBidder map[string]json.RawMessage, r
return anyModified, nil
}

func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool) []error {
func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp bool, hasStoredResponses bool) []error {
errL := []error{}
if req.ID == "" {
return []error{errors.New("request missing required field: \"id\"")}
Expand Down Expand Up @@ -580,7 +587,7 @@ func (deps *endpointDeps) validateRequest(req *openrtb_ext.RequestWrapper, isAmp
errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, index, imp.ID))
}
impIDs[imp.ID] = index
errs := deps.validateImp(imp, aliases, index)
errs := deps.validateImp(imp, aliases, index, hasStoredResponses)
if len(errs) > 0 {
errL = append(errL, errs...)
}
Expand Down Expand Up @@ -670,7 +677,7 @@ func validateBidders(bidders []string, knownBidders map[string]openrtb_ext.Bidde
return nil
}

func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int) []error {
func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]string, index int, hasStoredResponses bool) []error {
if imp.ID == "" {
return []error{fmt.Errorf("request.imp[%d] missing required field: \"id\"", index)}
}
Expand Down Expand Up @@ -703,7 +710,7 @@ func (deps *endpointDeps) validateImp(imp *openrtb2.Imp, aliases map[string]stri
return []error{err}
}

errL := deps.validateImpExt(imp, aliases, index)
errL := deps.validateImpExt(imp, aliases, index, hasStoredResponses)
if len(errL) != 0 {
return errL
}
Expand Down Expand Up @@ -1122,7 +1129,7 @@ func validatePmp(pmp *openrtb2.PMP, impIndex int) error {
return nil
}

func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int) []error {
func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int, hasStoredResponses bool) []error {
errL := []error{}
if len(imp.Ext) == 0 {
return []error{fmt.Errorf("request.imp[%d].ext is required", impIndex)}
Expand All @@ -1135,8 +1142,8 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s

// Prefer bidder params from request.imp.ext.prebid.bidder.BIDDER over request.imp.ext.BIDDER
// to avoid confusion beteween prebid specific adapter config and other ext protocols.
var extPrebid openrtb_ext.ExtImpPrebid
if extPrebidJSON, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok {
var extPrebid openrtb_ext.ExtImpPrebid
if err := json.Unmarshal(extPrebidJSON, &extPrebid); err == nil && extPrebid.Bidder != nil {
for bidder, ext := range extPrebid.Bidder {
if ext == nil {
Expand All @@ -1146,6 +1153,9 @@ func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]s
}
}
}
if hasStoredResponses && (extPrebid.StoredAuctionResponse == nil || len(extPrebid.StoredAuctionResponse.ID) == 0) {
return []error{fmt.Errorf("request validation failed. The StoredAuctionResponse.ID field must be completely present with, or completely absent from, all impressions in request. No StoredAuctionResponse data found for request.imp[%d].ext.prebid \n", impIndex)}
}

/* Process all the bidder exts in the request */
disabledBidders := []string{}
Expand Down Expand Up @@ -1507,6 +1517,51 @@ func getJsonSyntaxError(testJSON []byte) (bool, string) {
return false, ""
}

func (deps *endpointDeps) processStoredAuctionResponses(ctx context.Context, requestJson []byte) (map[string]json.RawMessage, []error) {
// Input request may have stored request or stored imps that will modify imps from incoming request
// Cases:
// - there is only stored request id in incoming request
// - there is at least one stored imp with stored auction response
// Need to update ImpExtPrebid after stored responses/impressions applied to incoming request
impInfo, errs := parseImpInfo(requestJson)
if len(errs) > 0 {
return nil, errs
}

storedAuctionResponseIds := make([]string, 0, 0) //all stored responses ids from all imps
//because of bulk fetch responses we need to map imp id to stored resp body
impIdToRespId := make(map[string]string) //imp id to stored resp id
impIdToStoredResp := make(map[string]json.RawMessage) //imp id to stored resp body
for index, impData := range impInfo {

if impData.ImpExtPrebid.StoredAuctionResponse != nil {
if len(impData.ImpExtPrebid.StoredAuctionResponse.ID) == 0 {
return nil, []error{fmt.Errorf("request.imp[%d] has ext.prebid.storedauctionresponse specified, but \"id\" field is missing ", index)}
}
storedAuctionResponseIds = append(storedAuctionResponseIds, impData.ImpExtPrebid.StoredAuctionResponse.ID)

impId, err := jsonparser.GetString(impData.Imp, "id")
if err != nil {
return nil, []error{err}
}

impIdToRespId[impId] = impData.ImpExtPrebid.StoredAuctionResponse.ID

}
}
if len(storedAuctionResponseIds) > 0 {
storedAuctionResponses, errs := deps.storedRespFetcher.FetchResponses(ctx, storedAuctionResponseIds)
if len(errs) > 0 {
return nil, errs
}
for impId, respId := range impIdToRespId {
impIdToStoredResp[impId] = storedAuctionResponses[respId]
}
return impIdToStoredResp, nil
}
return nil, nil
}

func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson []byte, impInfo []ImpExtPrebidData) ([]byte, map[string]exchange.ImpExtInfo, []error) {
// Parse the Stored Request IDs from the BidRequest and Imps.
storedBidRequestId, hasStoredBidRequest, err := getStoredRequestId(requestJson)
Expand Down
2 changes: 1 addition & 1 deletion endpoints/openrtb2/video_auction.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
// Populate any "missing" OpenRTB fields with info from other sources, (e.g. HTTP request headers).
deps.setFieldsImplicitly(r, bidReq) // move after merge

errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false)
errL = deps.validateRequest(&openrtb_ext.RequestWrapper{BidRequest: bidReq}, false, false)
if errortypes.ContainsFatalError(errL) {
handleError(&labels, w, errL, &vo, &debugLog)
return
Expand Down
53 changes: 52 additions & 1 deletion exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ type AuctionRequest struct {
// in HoldAuction until we get to factoring it away. Do not use for anything new.
LegacyLabels metrics.Labels
FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
// map of imp id to stored response
StoredAuctionResponses map[string]json.RawMessage
}

// BidderRequest holds the bidder specific request and all other
Expand Down Expand Up @@ -211,7 +213,20 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
// Get currency rates conversions for the auction
conversions := e.getAuctionCurrencyRates(requestExt.Prebid.CurrencyConversions)

adapterBids, adapterExtra, anyBidsReturned := e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride)
var adapterBids map[openrtb_ext.BidderName]*pbsOrtbSeatBid
var adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra
var anyBidsReturned bool

if len(r.StoredAuctionResponses) > 0 {
adapterBids, liveAdapters, err = buildStoreAuctionResponse(r.StoredAuctionResponses)
if err != nil {
return nil, err
}
anyBidsReturned = true

} else {
adapterBids, adapterExtra, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride)
}

var auc *auction
var cacheErrs []error
Expand Down Expand Up @@ -1076,3 +1091,39 @@ func listBiddersWithRequests(bidderRequests []BidderRequest) []openrtb_ext.Bidde

return liveAdapters
}

func buildStoreAuctionResponse(storedActionResponses map[string]json.RawMessage) (map[openrtb_ext.BidderName]*pbsOrtbSeatBid, []openrtb_ext.BidderName, error) {

adapterBids := make(map[openrtb_ext.BidderName]*pbsOrtbSeatBid, 0)
liveAdapters := make([]openrtb_ext.BidderName, 0)
for impId, storedResp := range storedActionResponses {
var seatBids []openrtb2.SeatBid

if err := json.Unmarshal(storedResp, &seatBids); err != nil {
return nil, nil, err
}
for _, seat := range seatBids {
var bidsToAdd []*pbsOrtbBid
//set imp id from request
for _, bid := range seat.Bid {
bid.ImpID = impId
bidsToAdd = append(bidsToAdd, &pbsOrtbBid{bid: &bid})
}

bidderName := openrtb_ext.BidderName(seat.Seat)

if _, ok := adapterBids[bidderName]; ok {
adapterBids[bidderName].bids = append(adapterBids[bidderName].bids, bidsToAdd...)

} else {
//create new seat bid and add it to live adapters
liveAdapters = append(liveAdapters, bidderName)
newSeatBid := pbsOrtbSeatBid{bidsToAdd, "", nil}
adapterBids[bidderName] = &newSeatBid

}
}
}

return adapterBids, liveAdapters, nil
}
8 changes: 8 additions & 0 deletions openrtb_ext/imp.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ type ExtImpPrebid struct {
// StoredRequest specifies which stored impression to use, if any.
StoredRequest *ExtStoredRequest `json:"storedrequest"`

// StoredResponse specifies which stored impression to use, if any.
StoredAuctionResponse *ExtStoredAuctionResponse `json:"storedauctionresponse"`

// IsRewardedInventory is a signal intended for video impressions. Must be 0 or 1.
IsRewardedInventory int8 `json:"is_rewarded_inventory"`

Expand All @@ -23,6 +26,11 @@ type ExtStoredRequest struct {
ID string `json:"id"`
}

// ExtStoredAuctionResponse defines the contract for bidrequest.imp[i].ext.prebid.storedauctionresponse
type ExtStoredAuctionResponse struct {
ID string `json:"id"`
}

type Options struct {
EchoVideoAttrs bool `json:"echovideoattrs"`
}

0 comments on commit d17d316

Please sign in to comment.