github.com/prebid/prebid-server@v0.275.0/adapters/gumgum/gumgum.go (about)

     1  package gumgum
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/prebid/openrtb/v19/openrtb2"
    11  	"github.com/prebid/prebid-server/adapters"
    12  	"github.com/prebid/prebid-server/config"
    13  	"github.com/prebid/prebid-server/errortypes"
    14  	"github.com/prebid/prebid-server/openrtb_ext"
    15  )
    16  
    17  // GumGumAdapter implements Bidder interface.
    18  type GumGumAdapter struct {
    19  	URI string
    20  }
    21  
    22  // MakeRequests makes the HTTP requests which should be made to fetch bids.
    23  func (g *GumGumAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    24  	var validImps []openrtb2.Imp
    25  	var siteCopy openrtb2.Site
    26  	if request.Site != nil {
    27  		siteCopy = *request.Site
    28  	}
    29  
    30  	numRequests := len(request.Imp)
    31  	errs := make([]error, 0, numRequests)
    32  
    33  	for i := 0; i < numRequests; i++ {
    34  		imp := request.Imp[i]
    35  		gumgumExt, err := preprocess(&imp)
    36  		if err != nil {
    37  			errs = append(errs, err)
    38  		} else {
    39  			if gumgumExt.Zone != "" {
    40  				siteCopy.ID = gumgumExt.Zone
    41  			}
    42  
    43  			if gumgumExt.PubID != 0 {
    44  				if siteCopy.Publisher != nil {
    45  					siteCopy.Publisher.ID = strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64)
    46  				} else {
    47  					siteCopy.Publisher = &openrtb2.Publisher{ID: strconv.FormatFloat(gumgumExt.PubID, 'f', -1, 64)}
    48  				}
    49  			}
    50  
    51  			validImps = append(validImps, imp)
    52  		}
    53  	}
    54  
    55  	if len(validImps) == 0 {
    56  		return nil, errs
    57  	}
    58  
    59  	request.Imp = validImps
    60  
    61  	if request.Site != nil {
    62  		request.Site = &siteCopy
    63  	}
    64  
    65  	reqJSON, err := json.Marshal(request)
    66  	if err != nil {
    67  		errs = append(errs, err)
    68  		return nil, errs
    69  	}
    70  
    71  	headers := http.Header{}
    72  	headers.Add("Content-Type", "application/json;charset=utf-8")
    73  	headers.Add("Accept", "application/json")
    74  	return []*adapters.RequestData{{
    75  		Method:  "POST",
    76  		Uri:     g.URI,
    77  		Body:    reqJSON,
    78  		Headers: headers,
    79  	}}, errs
    80  }
    81  
    82  // MakeBids unpacks the server's response into Bids.
    83  func (g *GumGumAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
    84  	if response.StatusCode == http.StatusNoContent {
    85  		return nil, nil
    86  	}
    87  
    88  	if response.StatusCode == http.StatusBadRequest {
    89  		return nil, []error{&errortypes.BadInput{
    90  			Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode),
    91  		}}
    92  	}
    93  
    94  	if response.StatusCode != http.StatusOK {
    95  		return nil, []error{&errortypes.BadServerResponse{
    96  			Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode),
    97  		}}
    98  	}
    99  	var bidResp openrtb2.BidResponse
   100  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   101  		return nil, []error{&errortypes.BadServerResponse{
   102  			Message: fmt.Sprintf("Bad server response: %d. ", err),
   103  		}}
   104  	}
   105  
   106  	var errs []error
   107  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   108  
   109  	for _, sb := range bidResp.SeatBid {
   110  		for i := range sb.Bid {
   111  			mediaType := getMediaTypeForImpID(sb.Bid[i].ImpID, internalRequest.Imp)
   112  			if mediaType == openrtb_ext.BidTypeVideo {
   113  				price := strconv.FormatFloat(sb.Bid[i].Price, 'f', -1, 64)
   114  				sb.Bid[i].AdM = strings.Replace(sb.Bid[i].AdM, "${AUCTION_PRICE}", price, -1)
   115  			}
   116  
   117  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   118  				Bid:     &sb.Bid[i],
   119  				BidType: mediaType,
   120  			})
   121  		}
   122  	}
   123  
   124  	return bidResponse, errs
   125  }
   126  
   127  func preprocess(imp *openrtb2.Imp) (*openrtb_ext.ExtImpGumGum, error) {
   128  	var bidderExt adapters.ExtImpBidder
   129  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   130  		err = &errortypes.BadInput{
   131  			Message: err.Error(),
   132  		}
   133  		return nil, err
   134  	}
   135  
   136  	var gumgumExt openrtb_ext.ExtImpGumGum
   137  	if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil {
   138  		err = &errortypes.BadInput{
   139  			Message: err.Error(),
   140  		}
   141  		return nil, err
   142  	}
   143  
   144  	if imp.Banner != nil && imp.Banner.W == nil && imp.Banner.H == nil && len(imp.Banner.Format) > 0 {
   145  		bannerCopy := *imp.Banner
   146  		format := bannerCopy.Format[0]
   147  		bannerCopy.W = &(format.W)
   148  		bannerCopy.H = &(format.H)
   149  
   150  		if gumgumExt.Slot != 0 {
   151  			var err error
   152  			bannerExt := getBiggerFormat(bannerCopy.Format, gumgumExt.Slot)
   153  			bannerCopy.Ext, err = json.Marshal(&bannerExt)
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  		}
   158  
   159  		imp.Banner = &bannerCopy
   160  	}
   161  
   162  	if imp.Video != nil {
   163  		err := validateVideoParams(imp.Video)
   164  		if err != nil {
   165  			return nil, err
   166  		}
   167  
   168  		if gumgumExt.IrisID != "" {
   169  			videoCopy := *imp.Video
   170  			videoExt := openrtb_ext.ExtImpGumGumVideo{IrisID: gumgumExt.IrisID}
   171  			videoCopy.Ext, err = json.Marshal(&videoExt)
   172  			if err != nil {
   173  				return nil, err
   174  			}
   175  			imp.Video = &videoCopy
   176  		}
   177  	}
   178  
   179  	return &gumgumExt, nil
   180  }
   181  
   182  func getBiggerFormat(formatList []openrtb2.Format, slot float64) openrtb_ext.ExtImpGumGumBanner {
   183  	maxw := int64(0)
   184  	maxh := int64(0)
   185  	greatestVal := int64(0)
   186  	for _, size := range formatList {
   187  		var biggerSide int64
   188  		if size.W > size.H {
   189  			biggerSide = size.W
   190  		} else {
   191  			biggerSide = size.H
   192  		}
   193  
   194  		if biggerSide > greatestVal || (biggerSide == greatestVal && size.W >= maxw && size.H >= maxh) {
   195  			greatestVal = biggerSide
   196  			maxh = size.H
   197  			maxw = size.W
   198  		}
   199  	}
   200  
   201  	bannerExt := openrtb_ext.ExtImpGumGumBanner{Si: slot, MaxW: float64(maxw), MaxH: float64(maxh)}
   202  
   203  	return bannerExt
   204  }
   205  
   206  func getMediaTypeForImpID(impID string, imps []openrtb2.Imp) openrtb_ext.BidType {
   207  	for _, imp := range imps {
   208  		if imp.ID == impID && imp.Banner != nil {
   209  			return openrtb_ext.BidTypeBanner
   210  		}
   211  	}
   212  	return openrtb_ext.BidTypeVideo
   213  }
   214  
   215  func validateVideoParams(video *openrtb2.Video) (err error) {
   216  	if video.W == 0 || video.H == 0 || video.MinDuration == 0 || video.MaxDuration == 0 || video.Placement == 0 || video.Linearity == 0 {
   217  		return &errortypes.BadInput{
   218  			Message: "Invalid or missing video field(s)",
   219  		}
   220  	}
   221  	return nil
   222  }
   223  
   224  // Builder builds a new instance of the GumGum adapter for the given bidder with the given config.
   225  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   226  	bidder := &GumGumAdapter{
   227  		URI: config.Endpoint,
   228  	}
   229  	return bidder, nil
   230  }