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

     1  package conversant
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/prebid/openrtb/v20/adcom1"
    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/errortypes"
    14  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    15  )
    16  
    17  type ConversantAdapter struct {
    18  	URI string
    19  }
    20  
    21  func (c *ConversantAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    22  	//Backend needs USD or it will reject the request
    23  	if len(request.Cur) > 0 && request.Cur[0] != "USD" {
    24  		request.Cur = []string{"USD"}
    25  	}
    26  	for i := 0; i < len(request.Imp); i++ {
    27  		var bidderExt adapters.ExtImpBidder
    28  		if err := json.Unmarshal(request.Imp[i].Ext, &bidderExt); err != nil {
    29  			return nil, []error{&errortypes.BadInput{
    30  				Message: fmt.Sprintf("Impression[%d] missing ext object", i),
    31  			}}
    32  		}
    33  
    34  		var cnvrExt openrtb_ext.ExtImpConversant
    35  		if err := json.Unmarshal(bidderExt.Bidder, &cnvrExt); err != nil {
    36  			return nil, []error{&errortypes.BadInput{
    37  				Message: fmt.Sprintf("Impression[%d] missing ext.bidder object", i),
    38  			}}
    39  		}
    40  
    41  		if cnvrExt.SiteID == "" {
    42  			return nil, []error{&errortypes.BadInput{
    43  				Message: fmt.Sprintf("Impression[%d] requires ext.bidder.site_id", i),
    44  			}}
    45  		}
    46  
    47  		if i == 0 {
    48  			if request.Site != nil {
    49  				tmpSite := *request.Site
    50  				request.Site = &tmpSite
    51  				request.Site.ID = cnvrExt.SiteID
    52  			} else if request.App != nil {
    53  				tmpApp := *request.App
    54  				request.App = &tmpApp
    55  				request.App.ID = cnvrExt.SiteID
    56  			}
    57  		}
    58  		err := parseCnvrParams(&request.Imp[i], cnvrExt, reqInfo)
    59  		if err != nil {
    60  			return nil, err
    61  		}
    62  	}
    63  
    64  	//create the request body
    65  	data, err := json.Marshal(request)
    66  	if err != nil {
    67  		return nil, []error{&errortypes.BadInput{
    68  			Message: "Error in packaging request to JSON",
    69  		}}
    70  	}
    71  	headers := http.Header{}
    72  	headers.Add("Content-Type", "application/json;charset=utf-8")
    73  	headers.Add("Accept", "application/json")
    74  
    75  	return []*adapters.RequestData{{
    76  		Method:  "POST",
    77  		Uri:     c.URI,
    78  		Body:    data,
    79  		Headers: headers,
    80  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
    81  	}}, nil
    82  }
    83  
    84  func parseCnvrParams(imp *openrtb2.Imp, cnvrExt openrtb_ext.ExtImpConversant, reqInfo *adapters.ExtraRequestInfo) []error {
    85  	imp.DisplayManager = "prebid-s2s"
    86  	imp.DisplayManagerVer = "2.0.0"
    87  
    88  	if imp.BidFloor <= 0 && cnvrExt.BidFloor > 0 {
    89  		imp.BidFloor = cnvrExt.BidFloor
    90  	}
    91  
    92  	if len(cnvrExt.TagID) > 0 {
    93  		imp.TagID = cnvrExt.TagID
    94  	}
    95  
    96  	// Take care not to override the global secure flag
    97  	if (imp.Secure == nil || *imp.Secure == 0) && cnvrExt.Secure != nil {
    98  		imp.Secure = cnvrExt.Secure
    99  	}
   100  
   101  	var position *adcom1.PlacementPosition
   102  	if cnvrExt.Position != nil {
   103  		position = adcom1.PlacementPosition(*cnvrExt.Position).Ptr()
   104  	}
   105  	if imp.Banner != nil {
   106  		tmpBanner := *imp.Banner
   107  		imp.Banner = &tmpBanner
   108  		imp.Banner.Pos = position
   109  
   110  	} else if imp.Video != nil {
   111  		tmpVideo := *imp.Video
   112  		imp.Video = &tmpVideo
   113  		imp.Video.Pos = position
   114  
   115  		if len(cnvrExt.API) > 0 {
   116  			imp.Video.API = make([]adcom1.APIFramework, 0, len(cnvrExt.API))
   117  			for _, api := range cnvrExt.API {
   118  				imp.Video.API = append(imp.Video.API, adcom1.APIFramework(api))
   119  			}
   120  		}
   121  
   122  		// Include protocols, mimes, and max duration if specified
   123  		// These properties can also be specified in ad unit's video object,
   124  		// but are overridden if the custom params object also contains them.
   125  
   126  		if len(cnvrExt.Protocols) > 0 {
   127  			imp.Video.Protocols = make([]adcom1.MediaCreativeSubtype, 0, len(cnvrExt.Protocols))
   128  			for _, protocol := range cnvrExt.Protocols {
   129  				imp.Video.Protocols = append(imp.Video.Protocols, adcom1.MediaCreativeSubtype(protocol))
   130  			}
   131  		}
   132  
   133  		if len(cnvrExt.MIMEs) > 0 {
   134  			imp.Video.MIMEs = make([]string, len(cnvrExt.MIMEs))
   135  			copy(imp.Video.MIMEs, cnvrExt.MIMEs)
   136  		}
   137  
   138  		if cnvrExt.MaxDuration != nil {
   139  			imp.Video.MaxDuration = *cnvrExt.MaxDuration
   140  		}
   141  	}
   142  	if imp.BidFloor > 0 && imp.BidFloorCur != "" && strings.ToUpper(imp.BidFloorCur) != "USD" {
   143  		floor, err := reqInfo.ConvertCurrency(imp.BidFloor, imp.BidFloorCur, "USD")
   144  		if err != nil {
   145  			return []error{&errortypes.BadInput{
   146  				Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", imp.BidFloorCur),
   147  			}}
   148  		}
   149  		imp.BidFloorCur = "USD"
   150  		imp.BidFloor = floor
   151  	}
   152  	return nil
   153  }
   154  
   155  func (c *ConversantAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   156  	if response.StatusCode == http.StatusNoContent {
   157  		return nil, nil // no bid response
   158  	}
   159  
   160  	if response.StatusCode != http.StatusOK {
   161  		return nil, []error{&errortypes.BadServerResponse{
   162  			Message: fmt.Sprintf("Unexpected status code: %d", response.StatusCode),
   163  		}}
   164  	}
   165  
   166  	var resp openrtb2.BidResponse
   167  	if err := json.Unmarshal(response.Body, &resp); err != nil {
   168  		return nil, []error{&errortypes.BadServerResponse{
   169  			Message: fmt.Sprintf("bad server response: %d. ", err),
   170  		}}
   171  	}
   172  
   173  	if len(resp.SeatBid) == 0 {
   174  		return nil, []error{&errortypes.BadServerResponse{
   175  			Message: "Empty bid request",
   176  		}}
   177  	}
   178  
   179  	bids := resp.SeatBid[0].Bid
   180  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bids))
   181  	for i := 0; i < len(bids); i++ {
   182  		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   183  			Bid:     &bids[i],
   184  			BidType: getBidType(bids[i].ImpID, internalRequest.Imp),
   185  		})
   186  	}
   187  	return bidResponse, nil
   188  }
   189  
   190  func getBidType(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
   191  	bidType := openrtb_ext.BidTypeBanner
   192  	for _, imp := range imps {
   193  		if imp.ID == impId {
   194  			if imp.Video != nil {
   195  				bidType = openrtb_ext.BidTypeVideo
   196  			}
   197  			break
   198  		}
   199  	}
   200  	return bidType
   201  }
   202  
   203  // Builder builds a new instance of the Conversant adapter for the given bidder with the given config.
   204  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   205  	bidder := &ConversantAdapter{
   206  		URI: config.Endpoint,
   207  	}
   208  	return bidder, nil
   209  }