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

     1  package rtbhouse
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  
    10  	"github.com/buger/jsonparser"
    11  	"github.com/prebid/openrtb/v20/openrtb2"
    12  	"github.com/prebid/prebid-server/v2/adapters"
    13  	"github.com/prebid/prebid-server/v2/config"
    14  	"github.com/prebid/prebid-server/v2/errortypes"
    15  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    16  )
    17  
    18  const (
    19  	BidderCurrency string = "USD"
    20  )
    21  
    22  // RTBHouseAdapter implements the Bidder interface.
    23  type RTBHouseAdapter struct {
    24  	endpoint string
    25  }
    26  
    27  // Builder builds a new instance of the RTBHouse adapter for the given bidder with the given config.
    28  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    29  	bidder := &RTBHouseAdapter{
    30  		endpoint: config.Endpoint,
    31  	}
    32  	return bidder, nil
    33  }
    34  
    35  // MakeRequests prepares the HTTP requests which should be made to fetch bids.
    36  func (adapter *RTBHouseAdapter) MakeRequests(
    37  	openRTBRequest *openrtb2.BidRequest,
    38  	reqInfo *adapters.ExtraRequestInfo,
    39  ) (
    40  	requestsToBidder []*adapters.RequestData,
    41  	errs []error,
    42  ) {
    43  
    44  	reqCopy := *openRTBRequest
    45  	reqCopy.Imp = []openrtb2.Imp{}
    46  	for _, imp := range openRTBRequest.Imp {
    47  		var bidFloorCur = imp.BidFloorCur
    48  		var bidFloor = imp.BidFloor
    49  		if bidFloorCur == "" && bidFloor == 0 {
    50  			rtbhouseExt, err := getImpressionExt(imp)
    51  			if err != nil {
    52  				return nil, []error{err}
    53  			}
    54  			if rtbhouseExt.BidFloor > 0 {
    55  				bidFloor = rtbhouseExt.BidFloor
    56  				bidFloorCur = BidderCurrency
    57  				if len(reqCopy.Cur) > 0 {
    58  					bidFloorCur = reqCopy.Cur[0]
    59  				}
    60  			}
    61  		}
    62  
    63  		// Check if imp comes with bid floor amount defined in a foreign currency
    64  		if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != BidderCurrency {
    65  			// Convert to US dollars
    66  			convertedValue, err := reqInfo.ConvertCurrency(bidFloor, bidFloorCur, BidderCurrency)
    67  			if err != nil {
    68  				return nil, []error{err}
    69  			}
    70  
    71  			bidFloorCur = BidderCurrency
    72  			bidFloor = convertedValue
    73  		}
    74  
    75  		if bidFloor > 0 && bidFloorCur == BidderCurrency {
    76  			// Update after conversion. All imp elements inside request.Imp are shallow copies
    77  			// therefore, their non-pointer values are not shared memory and are safe to modify.
    78  			imp.BidFloorCur = bidFloorCur
    79  			imp.BidFloor = bidFloor
    80  		}
    81  
    82  		// Set the CUR of bid to BIDDER_CURRENCY after converting all floors
    83  		reqCopy.Cur = []string{BidderCurrency}
    84  		reqCopy.Imp = append(reqCopy.Imp, imp)
    85  	}
    86  
    87  	openRTBRequestJSON, err := json.Marshal(reqCopy)
    88  	if err != nil {
    89  		errs = append(errs, err)
    90  		return nil, errs
    91  	}
    92  
    93  	headers := http.Header{}
    94  	headers.Add("Content-Type", "application/json;charset=utf-8")
    95  	requestToBidder := &adapters.RequestData{
    96  		Method:  "POST",
    97  		Uri:     adapter.endpoint,
    98  		Body:    openRTBRequestJSON,
    99  		Headers: headers,
   100  		ImpIDs:  openrtb_ext.GetImpIDs(reqCopy.Imp),
   101  	}
   102  	requestsToBidder = append(requestsToBidder, requestToBidder)
   103  
   104  	return requestsToBidder, errs
   105  }
   106  
   107  func getImpressionExt(imp openrtb2.Imp) (*openrtb_ext.ExtImpRTBHouse, error) {
   108  	var bidderExt adapters.ExtImpBidder
   109  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   110  		return nil, &errortypes.BadInput{
   111  			Message: "Bidder extension not provided or can't be unmarshalled",
   112  		}
   113  	}
   114  
   115  	var rtbhouseExt openrtb_ext.ExtImpRTBHouse
   116  	if err := json.Unmarshal(bidderExt.Bidder, &rtbhouseExt); err != nil {
   117  		return nil, &errortypes.BadInput{
   118  			Message: "Error while unmarshaling bidder extension",
   119  		}
   120  	}
   121  
   122  	return &rtbhouseExt, nil
   123  }
   124  
   125  const unexpectedStatusCodeFormat = "" +
   126  	"Unexpected status code: %d. Run with request.debug = 1 for more info"
   127  
   128  // MakeBids unpacks the server's response into Bids.
   129  func (adapter *RTBHouseAdapter) MakeBids(
   130  	openRTBRequest *openrtb2.BidRequest,
   131  	requestToBidder *adapters.RequestData,
   132  	bidderRawResponse *adapters.ResponseData,
   133  ) (
   134  	bidderResponse *adapters.BidderResponse,
   135  	errs []error,
   136  ) {
   137  	switch bidderRawResponse.StatusCode {
   138  	case http.StatusOK:
   139  		break
   140  	case http.StatusNoContent:
   141  		return nil, nil
   142  	case http.StatusBadRequest:
   143  		err := &errortypes.BadInput{
   144  			Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode),
   145  		}
   146  		return nil, []error{err}
   147  	default:
   148  		err := &errortypes.BadServerResponse{
   149  			Message: fmt.Sprintf(unexpectedStatusCodeFormat, bidderRawResponse.StatusCode),
   150  		}
   151  		return nil, []error{err}
   152  	}
   153  
   154  	var openRTBBidderResponse openrtb2.BidResponse
   155  	if err := json.Unmarshal(bidderRawResponse.Body, &openRTBBidderResponse); err != nil {
   156  		return nil, []error{err}
   157  	}
   158  
   159  	bidsCapacity := len(openRTBBidderResponse.SeatBid[0].Bid)
   160  	bidderResponse = adapters.NewBidderResponseWithBidsCapacity(bidsCapacity)
   161  	var typedBid *adapters.TypedBid
   162  	for _, seatBid := range openRTBBidderResponse.SeatBid {
   163  		for _, bid := range seatBid.Bid {
   164  			bid := bid // pin! -> https://github.com/kyoh86/scopelint#whats-this
   165  			bidType, err := getMediaTypeForBid(bid)
   166  			if err != nil {
   167  				errs = append(errs, err)
   168  				continue
   169  			} else {
   170  				typedBid = &adapters.TypedBid{
   171  					Bid:     &bid,
   172  					BidType: bidType,
   173  				}
   174  
   175  				// for native bid responses fix Adm field
   176  				if typedBid.BidType == openrtb_ext.BidTypeNative {
   177  					bid.AdM, err = getNativeAdm(bid.AdM)
   178  					if err != nil {
   179  						errs = append(errs, err)
   180  						continue
   181  					}
   182  				}
   183  
   184  				bidderResponse.Bids = append(bidderResponse.Bids, typedBid)
   185  			}
   186  		}
   187  	}
   188  
   189  	bidderResponse.Currency = BidderCurrency
   190  
   191  	return bidderResponse, errs
   192  
   193  }
   194  
   195  func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
   196  	switch bid.MType {
   197  	case openrtb2.MarkupBanner:
   198  		return openrtb_ext.BidTypeBanner, nil
   199  	case openrtb2.MarkupNative:
   200  		return openrtb_ext.BidTypeNative, nil
   201  	default:
   202  		return "", fmt.Errorf("unrecognized bid type in response from rtbhouse for bid %s", bid.ImpID)
   203  	}
   204  }
   205  
   206  func getNativeAdm(adm string) (string, error) {
   207  	nativeAdm := make(map[string]interface{})
   208  	err := json.Unmarshal([]byte(adm), &nativeAdm)
   209  	if err != nil {
   210  		return adm, errors.New("unable to unmarshal native adm")
   211  	}
   212  
   213  	// move bid.adm.native to bid.adm
   214  	if _, ok := nativeAdm["native"]; ok {
   215  		//using jsonparser to avoid marshaling, encode escape, etc.
   216  		value, dataType, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative))
   217  		if err != nil || dataType != jsonparser.Object {
   218  			return adm, errors.New("unable to get native adm")
   219  		}
   220  		adm = string(value)
   221  	}
   222  
   223  	return adm, nil
   224  }