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

     1  package liftoff
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/prebid/openrtb/v20/openrtb2"
    11  	"github.com/prebid/prebid-server/v2/adapters"
    12  	"github.com/prebid/prebid-server/v2/config"
    13  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    14  )
    15  
    16  const SupportedCurrency = "USD"
    17  
    18  type adapter struct {
    19  	Endpoint string
    20  }
    21  
    22  type liftoffImpressionExt struct {
    23  	*adapters.ExtImpBidder
    24  	// Ext represents the vungle extension.
    25  	Ext openrtb_ext.ImpExtLiftoff `json:"vungle"`
    26  }
    27  
    28  // Builder builds a new instance of the Liftoff adapter for the given bidder with the given config.
    29  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    30  	return &adapter{Endpoint: config.Endpoint}, nil
    31  }
    32  
    33  // MakeRequests split impressions into bid requests and change them into the form that liftoff can handle.
    34  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    35  	var requests []*adapters.RequestData
    36  	var errs []error
    37  	requestCopy := *request
    38  	for _, imp := range request.Imp {
    39  		// Check if imp comes with bid floor amount defined in a foreign currency
    40  		if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != SupportedCurrency {
    41  			// Convert to US dollars
    42  			convertedValue, err := requestInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, SupportedCurrency)
    43  			if err != nil {
    44  				errs = append(errs, fmt.Errorf("failed to convert currency (err)%s", err.Error()))
    45  				continue
    46  			}
    47  
    48  			// Update after conversion. All imp elements inside request.Imp are shallow copies
    49  			// therefore, their non-pointer values are not shared memory and are safe to modify.
    50  			imp.BidFloorCur = SupportedCurrency
    51  			imp.BidFloor = convertedValue
    52  		}
    53  
    54  		var impExt liftoffImpressionExt
    55  		if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
    56  			errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error()))
    57  			continue
    58  		}
    59  
    60  		// get placement_reference_id & pub_app_store_id
    61  		var bidderImpExt openrtb_ext.ImpExtLiftoff
    62  		if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil {
    63  			errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error()))
    64  			continue
    65  		}
    66  
    67  		bidderImpExt.BidToken = requestCopy.User.BuyerUID
    68  		impExt.Ext = bidderImpExt
    69  		if newImpExt, err := json.Marshal(impExt); err == nil {
    70  			imp.Ext = newImpExt
    71  		} else {
    72  			errs = append(errs, errors.New("failed re-marshalling imp ext"))
    73  			continue
    74  		}
    75  
    76  		imp.TagID = bidderImpExt.PlacementRefID
    77  		requestCopy.Imp = []openrtb2.Imp{imp}
    78  		// must make a shallow copy for pointers.
    79  		// If it is site object, need to construct an app with pub_store_id.
    80  		var requestAppCopy openrtb2.App
    81  		if request.App != nil {
    82  			requestAppCopy = *request.App
    83  			requestAppCopy.ID = bidderImpExt.PubAppStoreID
    84  		} else if request.Site != nil {
    85  			requestCopy.Site = nil
    86  			requestAppCopy = openrtb2.App{
    87  				ID: bidderImpExt.PubAppStoreID,
    88  			}
    89  		} else {
    90  			errs = append(errs, errors.New("failed constructing app, must have app or site object in bid request"))
    91  			continue
    92  		}
    93  
    94  		requestCopy.App = &requestAppCopy
    95  		requestJSON, err := json.Marshal(requestCopy)
    96  		if err != nil {
    97  			errs = append(errs, err)
    98  			continue
    99  		}
   100  
   101  		requestData := &adapters.RequestData{
   102  			Method: "POST",
   103  			Uri:    a.Endpoint,
   104  			Body:   requestJSON,
   105  			Headers: http.Header{
   106  				"Content-Type":      []string{"application/json"},
   107  				"Accept":            []string{"application/json"},
   108  				"X-OpenRTB-Version": []string{"2.5"},
   109  			},
   110  			ImpIDs: openrtb_ext.GetImpIDs(requestCopy.Imp),
   111  		}
   112  
   113  		requests = append(requests, requestData)
   114  	}
   115  
   116  	return requests, errs
   117  }
   118  
   119  // MakeBids collect bid response from liftoff and change them into the form that Prebid Server can handle.
   120  func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   121  	if adapters.IsResponseStatusCodeNoContent(responseData) {
   122  		return nil, nil
   123  	}
   124  
   125  	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
   126  		return nil, []error{err}
   127  	}
   128  
   129  	var response openrtb2.BidResponse
   130  	if err := json.Unmarshal(responseData.Body, &response); err != nil {
   131  		return nil, []error{err}
   132  	}
   133  
   134  	var errs []error
   135  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
   136  	bidResponse.Currency = response.Cur
   137  	for _, seatBid := range response.SeatBid {
   138  		for i := range seatBid.Bid {
   139  			b := &adapters.TypedBid{
   140  				Bid:     &seatBid.Bid[i],
   141  				BidType: openrtb_ext.BidTypeVideo,
   142  				Seat:    openrtb_ext.BidderName(seatBid.Seat),
   143  			}
   144  
   145  			bidResponse.Bids = append(bidResponse.Bids, b)
   146  		}
   147  	}
   148  
   149  	return bidResponse, errs
   150  }