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

     1  package sovrn
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  
    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  	"github.com/prebid/openrtb/v20/openrtb2"
    17  )
    18  
    19  type SovrnAdapter struct {
    20  	URI string
    21  }
    22  
    23  type sovrnImpExt struct {
    24  	Bidder     openrtb_ext.ExtImpSovrn `json:"bidder"`
    25  	AdUnitCode string                  `json:"adunitcode,omitempty"`
    26  }
    27  
    28  func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    29  	headers := http.Header{}
    30  	headers.Add("Content-Type", "application/json")
    31  	if request.Device != nil {
    32  		addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA)
    33  		addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP)
    34  		addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language)
    35  		if request.Device.DNT != nil {
    36  			addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(*request.Device.DNT)))
    37  		}
    38  	}
    39  
    40  	if request.User != nil {
    41  		userID := strings.TrimSpace(request.User.BuyerUID)
    42  		if len(userID) > 0 {
    43  			headers.Add("Cookie", fmt.Sprintf("%s=%s", "ljt_reader", userID))
    44  		}
    45  	}
    46  
    47  	errs := make([]error, 0, len(request.Imp))
    48  	var err error
    49  	validImps := make([]openrtb2.Imp, 0, len(request.Imp))
    50  
    51  	for _, imp := range request.Imp {
    52  		var bidderExt adapters.ExtImpBidder
    53  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    54  			errs = append(errs, &errortypes.BadInput{
    55  				Message: err.Error(),
    56  			})
    57  			continue
    58  		}
    59  
    60  		var sovrnExt openrtb_ext.ExtImpSovrn
    61  		if err := json.Unmarshal(bidderExt.Bidder, &sovrnExt); err != nil {
    62  			errs = append(errs, &errortypes.BadInput{
    63  				Message: err.Error(),
    64  			})
    65  			continue
    66  		}
    67  
    68  		tagId := getTagId(sovrnExt)
    69  		if tagId == "" {
    70  			errs = append(errs, &errortypes.BadInput{
    71  				Message: "Missing required parameter 'tagid'",
    72  			})
    73  			continue
    74  		}
    75  
    76  		imp.TagID = tagId
    77  
    78  		if imp.BidFloor == 0 && sovrnExt.BidFloor > 0 {
    79  			imp.BidFloor = sovrnExt.BidFloor
    80  		}
    81  
    82  		var impExtBuffer []byte
    83  		impExtBuffer, err = json.Marshal(&sovrnImpExt{
    84  			Bidder:     sovrnExt,
    85  			AdUnitCode: sovrnExt.AdUnitCode,
    86  		})
    87  		if err != nil {
    88  			errs = append(errs, &errortypes.BadInput{
    89  				Message: err.Error(),
    90  			})
    91  			continue
    92  		}
    93  
    94  		imp.Ext = impExtBuffer
    95  
    96  		// Validate video params if appropriate
    97  		video := imp.Video
    98  		if video != nil {
    99  			if video.MIMEs == nil ||
   100  				video.MaxDuration == 0 ||
   101  				video.Protocols == nil {
   102  				errs = append(errs, &errortypes.BadInput{
   103  					Message: "Missing required video parameter",
   104  				})
   105  				continue
   106  			}
   107  		}
   108  
   109  		validImps = append(validImps, imp)
   110  	}
   111  
   112  	if len(validImps) == 0 {
   113  		return nil, errs
   114  	}
   115  
   116  	request.Imp = validImps
   117  
   118  	reqJSON, err := json.Marshal(request)
   119  	if err != nil {
   120  		errs = append(errs, err)
   121  		return nil, errs
   122  	}
   123  
   124  	return []*adapters.RequestData{{
   125  		Method:  "POST",
   126  		Uri:     s.URI,
   127  		Body:    reqJSON,
   128  		Headers: headers,
   129  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
   130  	}}, errs
   131  }
   132  
   133  func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) {
   134  	if len(headerValue) > 0 {
   135  		headers.Add(headerName, headerValue)
   136  	}
   137  }
   138  
   139  func (s *SovrnAdapter) MakeBids(request *openrtb2.BidRequest, bidderRequest *adapters.RequestData, bidderResponse *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   140  	if bidderResponse.StatusCode == http.StatusNoContent {
   141  		return nil, nil
   142  	}
   143  
   144  	if bidderResponse.StatusCode == http.StatusBadRequest {
   145  		return nil, []error{&errortypes.BadInput{
   146  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
   147  		}}
   148  	}
   149  
   150  	if bidderResponse.StatusCode != http.StatusOK {
   151  		return nil, []error{&errortypes.BadServerResponse{
   152  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", bidderResponse.StatusCode),
   153  		}}
   154  	}
   155  
   156  	var bidResponse openrtb2.BidResponse
   157  	if err := json.Unmarshal(bidderResponse.Body, &bidResponse); err != nil {
   158  		return nil, []error{&errortypes.BadServerResponse{
   159  			Message: err.Error(),
   160  		}}
   161  	}
   162  
   163  	response := adapters.NewBidderResponseWithBidsCapacity(5)
   164  	errs := make([]error, 0)
   165  
   166  	for _, sb := range bidResponse.SeatBid {
   167  		for _, bid := range sb.Bid {
   168  			adm, err := url.QueryUnescape(bid.AdM)
   169  			if err == nil {
   170  				bid.AdM = adm
   171  
   172  				bidType := openrtb_ext.BidTypeBanner
   173  
   174  				impIdx, impIdErr := getImpIdx(bid.ImpID, request)
   175  				if impIdErr != nil {
   176  					errs = append(errs, impIdErr)
   177  					continue
   178  				} else if request.Imp[impIdx].Video != nil {
   179  					bidType = openrtb_ext.BidTypeVideo
   180  				}
   181  
   182  				response.Bids = append(response.Bids, &adapters.TypedBid{
   183  					Bid:     &bid,
   184  					BidType: bidType,
   185  				})
   186  			}
   187  		}
   188  	}
   189  
   190  	return response, errs
   191  }
   192  
   193  func getTagId(sovrnExt openrtb_ext.ExtImpSovrn) string {
   194  	if len(sovrnExt.Tagid) > 0 {
   195  		return sovrnExt.Tagid
   196  	} else {
   197  		return sovrnExt.TagId
   198  	}
   199  }
   200  
   201  func getImpIdx(impId string, request *openrtb2.BidRequest) (int, error) {
   202  	for idx, imp := range request.Imp {
   203  		if imp.ID == impId {
   204  			return idx, nil
   205  		}
   206  	}
   207  
   208  	return -1, &errortypes.BadInput{
   209  		Message: fmt.Sprintf("Imp ID %s in bid didn't match with any imp in the original request", impId),
   210  	}
   211  }
   212  
   213  // Builder builds a new instance of the Sovrn adapter for the given bidder with the given config.
   214  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   215  	bidder := &SovrnAdapter{
   216  		URI: config.Endpoint,
   217  	}
   218  	return bidder, nil
   219  }