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

     1  package triplelift_native
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/prebid/openrtb/v19/openrtb2"
     9  	"github.com/prebid/prebid-server/adapters"
    10  	"github.com/prebid/prebid-server/config"
    11  	"github.com/prebid/prebid-server/errortypes"
    12  	"github.com/prebid/prebid-server/openrtb_ext"
    13  )
    14  
    15  type TripleliftNativeAdapter struct {
    16  	endpoint string
    17  	extInfo  TripleliftNativeExtInfo
    18  }
    19  
    20  type TripleliftInnerExt struct {
    21  	Format int `json:"format"`
    22  }
    23  
    24  type TripleliftRespExt struct {
    25  	Triplelift TripleliftInnerExt `json:"triplelift_pb"`
    26  }
    27  
    28  type TripleliftNativeExtInfo struct {
    29  	// Array is used for deserialization.
    30  	PublisherWhitelist []string `json:"publisher_whitelist"`
    31  
    32  	// Map is used for optimized memory access and should be constructed after deserialization.
    33  	PublisherWhitelistMap map[string]struct{}
    34  }
    35  
    36  func getBidType(ext TripleliftRespExt) openrtb_ext.BidType {
    37  	return openrtb_ext.BidTypeNative
    38  }
    39  
    40  func processImp(imp *openrtb2.Imp) error {
    41  	// get the triplelift extension
    42  	var ext adapters.ExtImpBidder
    43  	var tlext openrtb_ext.ExtImpTriplelift
    44  	if err := json.Unmarshal(imp.Ext, &ext); err != nil {
    45  		return err
    46  	}
    47  	if err := json.Unmarshal(ext.Bidder, &tlext); err != nil {
    48  		return err
    49  	}
    50  	if imp.Native == nil {
    51  		return fmt.Errorf("no native object specified")
    52  	}
    53  	if tlext.InvCode == "" {
    54  		return fmt.Errorf("no inv_code specified")
    55  	}
    56  	imp.TagID = tlext.InvCode
    57  	// floor is optional
    58  	if tlext.Floor == nil {
    59  		return nil
    60  	}
    61  	imp.BidFloor = *tlext.Floor
    62  	// no error
    63  	return nil
    64  }
    65  
    66  // Returns the effective publisher ID
    67  func effectivePubID(pub *openrtb2.Publisher) string {
    68  	if pub != nil {
    69  		if pub.Ext != nil {
    70  			var pubExt openrtb_ext.ExtPublisher
    71  			err := json.Unmarshal(pub.Ext, &pubExt)
    72  			if err == nil && pubExt.Prebid != nil && pubExt.Prebid.ParentAccount != nil && *pubExt.Prebid.ParentAccount != "" {
    73  				return *pubExt.Prebid.ParentAccount
    74  			}
    75  		}
    76  		if pub.ID != "" {
    77  			return pub.ID
    78  		}
    79  	}
    80  	return "unknown"
    81  }
    82  
    83  func (a *TripleliftNativeAdapter) MakeRequests(request *openrtb2.BidRequest, extra *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    84  	errs := make([]error, 0, len(request.Imp)+1)
    85  	reqs := make([]*adapters.RequestData, 0, 1)
    86  	// copy the request, because we are going to mutate it
    87  	tlRequest := *request
    88  	// this will contain all the valid impressions
    89  	var validImps []openrtb2.Imp
    90  	// pre-process the imps
    91  	for _, imp := range tlRequest.Imp {
    92  		if err := processImp(&imp); err == nil {
    93  			validImps = append(validImps, imp)
    94  		} else {
    95  			errs = append(errs, err)
    96  		}
    97  	}
    98  	publisher := getPublisher(request)
    99  	publisherID := effectivePubID(publisher)
   100  	if _, exists := a.extInfo.PublisherWhitelistMap[publisherID]; !exists {
   101  		err := fmt.Errorf("Unsupported publisher for triplelift_native")
   102  		return nil, []error{err}
   103  	}
   104  	if len(validImps) == 0 {
   105  		err := fmt.Errorf("No valid impressions for triplelift")
   106  		errs = append(errs, err)
   107  		return nil, errs
   108  	}
   109  	tlRequest.Imp = validImps
   110  	reqJSON, err := json.Marshal(tlRequest)
   111  	if err != nil {
   112  		errs = append(errs, err)
   113  		return nil, errs
   114  	}
   115  	headers := http.Header{}
   116  	headers.Add("Content-Type", "application/json;charset=utf-8")
   117  	headers.Add("Accept", "application/json")
   118  	ad := a.endpoint
   119  	reqs = append(reqs, &adapters.RequestData{
   120  		Method:  "POST",
   121  		Uri:     ad,
   122  		Body:    reqJSON,
   123  		Headers: headers})
   124  	return reqs, errs
   125  }
   126  
   127  func getPublisher(request *openrtb2.BidRequest) *openrtb2.Publisher {
   128  	if request.App != nil {
   129  		return request.App.Publisher
   130  	}
   131  	return request.Site.Publisher
   132  }
   133  
   134  func getBidCount(bidResponse openrtb2.BidResponse) int {
   135  	c := 0
   136  	for _, sb := range bidResponse.SeatBid {
   137  		c = c + len(sb.Bid)
   138  	}
   139  	return c
   140  }
   141  
   142  func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   143  	if response.StatusCode == http.StatusNoContent {
   144  		return nil, nil
   145  	}
   146  
   147  	if response.StatusCode == http.StatusBadRequest {
   148  		return nil, []error{&errortypes.BadInput{
   149  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   150  		}}
   151  	}
   152  
   153  	if response.StatusCode != http.StatusOK {
   154  		return nil, []error{&errortypes.BadServerResponse{Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}}
   155  	}
   156  	var bidResp openrtb2.BidResponse
   157  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   158  		return nil, []error{err}
   159  	}
   160  	var errs []error
   161  	count := getBidCount(bidResp)
   162  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(count)
   163  
   164  	for _, sb := range bidResp.SeatBid {
   165  		for i := 0; i < len(sb.Bid); i++ {
   166  			bid := sb.Bid[i]
   167  			var bidExt TripleliftRespExt
   168  			bidType := getBidType(bidExt)
   169  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   170  				Bid:     &bid,
   171  				BidType: bidType,
   172  			})
   173  		}
   174  	}
   175  	return bidResponse, errs
   176  }
   177  
   178  // Builder builds a new instance of the TripleliftNative adapter for the given bidder with the given config.
   179  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   180  	extraInfo, err := getExtraInfo(config.ExtraAdapterInfo)
   181  	if err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	// Populate map for faster memory access
   186  	extraInfo.PublisherWhitelistMap = make(map[string]struct{}, len(extraInfo.PublisherWhitelist))
   187  	for _, v := range extraInfo.PublisherWhitelist {
   188  		extraInfo.PublisherWhitelistMap[v] = struct{}{}
   189  	}
   190  
   191  	bidder := &TripleliftNativeAdapter{
   192  		endpoint: config.Endpoint,
   193  		extInfo:  extraInfo,
   194  	}
   195  	return bidder, nil
   196  }
   197  
   198  func getExtraInfo(v string) (TripleliftNativeExtInfo, error) {
   199  	if len(v) == 0 {
   200  		return getDefaultExtraInfo(), nil
   201  	}
   202  
   203  	var extraInfo TripleliftNativeExtInfo
   204  	if err := json.Unmarshal([]byte(v), &extraInfo); err != nil {
   205  		return extraInfo, fmt.Errorf("invalid extra info: %v", err)
   206  	}
   207  
   208  	return extraInfo, nil
   209  }
   210  
   211  func getDefaultExtraInfo() TripleliftNativeExtInfo {
   212  	return TripleliftNativeExtInfo{
   213  		PublisherWhitelist: []string{},
   214  	}
   215  }