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

     1  package pubnative
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     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/errortypes"
    14  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    15  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    16  )
    17  
    18  type PubnativeAdapter struct {
    19  	URI string
    20  }
    21  
    22  func (a *PubnativeAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    23  	impCount := len(request.Imp)
    24  	requestData := make([]*adapters.RequestData, 0, impCount)
    25  	errs := []error{}
    26  
    27  	headers := http.Header{}
    28  	headers.Add("Content-Type", "application/json;charset=utf-8")
    29  	headers.Add("Accept", "application/json")
    30  
    31  	err := checkRequest(request)
    32  	if err != nil {
    33  		errs = append(errs, err)
    34  		return nil, errs
    35  	}
    36  
    37  	for _, imp := range request.Imp {
    38  		requestCopy := *request
    39  		var bidderExt adapters.ExtImpBidder
    40  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    41  			errs = append(errs, err)
    42  			continue
    43  		}
    44  
    45  		var pubnativeExt openrtb_ext.ExtImpPubnative
    46  		if err := json.Unmarshal(bidderExt.Bidder, &pubnativeExt); err != nil {
    47  			errs = append(errs, err)
    48  			continue
    49  		}
    50  
    51  		err := convertImpression(&imp)
    52  		if err != nil {
    53  			errs = append(errs, err)
    54  			continue
    55  		}
    56  
    57  		requestCopy.Imp = []openrtb2.Imp{imp}
    58  		reqJSON, err := json.Marshal(&requestCopy)
    59  		if err != nil {
    60  			errs = append(errs, err)
    61  			return nil, errs
    62  		}
    63  
    64  		queryParams := url.Values{}
    65  		queryParams.Add("apptoken", pubnativeExt.AppAuthToken)
    66  		queryParams.Add("zoneid", strconv.Itoa(pubnativeExt.ZoneID))
    67  		queryString := queryParams.Encode()
    68  
    69  		reqData := &adapters.RequestData{
    70  			Method:  "POST",
    71  			Uri:     fmt.Sprintf("%s?%s", a.URI, queryString),
    72  			Body:    reqJSON,
    73  			Headers: headers,
    74  			ImpIDs:  openrtb_ext.GetImpIDs(requestCopy.Imp),
    75  		}
    76  
    77  		requestData = append(requestData, reqData)
    78  	}
    79  	return requestData, errs
    80  }
    81  
    82  func checkRequest(request *openrtb2.BidRequest) error {
    83  	if request.Device == nil || len(request.Device.OS) == 0 {
    84  		return &errortypes.BadInput{
    85  			Message: "Impression is missing device OS information",
    86  		}
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  func convertImpression(imp *openrtb2.Imp) error {
    93  	if imp.Banner == nil && imp.Video == nil && imp.Native == nil {
    94  		return &errortypes.BadInput{
    95  			Message: "Pubnative only supports banner, video or native ads.",
    96  		}
    97  	}
    98  	if imp.Banner != nil {
    99  		bannerCopy, err := convertBanner(imp.Banner)
   100  		if err != nil {
   101  			return err
   102  		}
   103  		imp.Banner = bannerCopy
   104  	}
   105  
   106  	return nil
   107  }
   108  
   109  // make sure that banner has openrtb 2.3-compatible size information
   110  func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
   111  	if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
   112  		if len(banner.Format) > 0 {
   113  			f := banner.Format[0]
   114  
   115  			bannerCopy := *banner
   116  
   117  			bannerCopy.W = ptrutil.ToPtr(f.W)
   118  			bannerCopy.H = ptrutil.ToPtr(f.H)
   119  
   120  			return &bannerCopy, nil
   121  		} else {
   122  			return nil, &errortypes.BadInput{
   123  				Message: "Size information missing for banner",
   124  			}
   125  		}
   126  	}
   127  	return banner, nil
   128  }
   129  
   130  func (a *PubnativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   131  	if response.StatusCode == http.StatusNoContent {
   132  		return nil, nil
   133  	}
   134  
   135  	if response.StatusCode == http.StatusBadRequest {
   136  		return nil, []error{&errortypes.BadInput{
   137  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   138  		}}
   139  	}
   140  
   141  	if response.StatusCode != http.StatusOK {
   142  		return nil, []error{&errortypes.BadServerResponse{
   143  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   144  		}}
   145  	}
   146  
   147  	var parsedResponse openrtb2.BidResponse
   148  	if err := json.Unmarshal(response.Body, &parsedResponse); err != nil {
   149  		return nil, []error{&errortypes.BadServerResponse{
   150  			Message: err.Error(),
   151  		}}
   152  	}
   153  
   154  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   155  
   156  	for _, sb := range parsedResponse.SeatBid {
   157  		for i := 0; i < len(sb.Bid); i++ {
   158  			bid := sb.Bid[i]
   159  			if bid.Price != 0 {
   160  				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   161  					Bid:     &bid,
   162  					BidType: getMediaTypeForImp(bid.ImpID, internalRequest.Imp),
   163  				})
   164  			}
   165  		}
   166  	}
   167  	return bidResponse, nil
   168  }
   169  
   170  // Builder builds a new instance of the Pubnative adapter for the given bidder with the given config.
   171  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   172  	bidder := &PubnativeAdapter{
   173  		URI: config.Endpoint,
   174  	}
   175  	return bidder, nil
   176  }
   177  
   178  func getMediaTypeForImp(impId string, imps []openrtb2.Imp) openrtb_ext.BidType {
   179  	mediaType := openrtb_ext.BidTypeBanner
   180  	for _, imp := range imps {
   181  		if imp.ID == impId {
   182  			if imp.Video != nil {
   183  				mediaType = openrtb_ext.BidTypeVideo
   184  			} else if imp.Native != nil {
   185  				mediaType = openrtb_ext.BidTypeNative
   186  			}
   187  			return mediaType
   188  		}
   189  	}
   190  	return mediaType
   191  }