github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2"
     9  	"github.com/prebid/prebid-server/v2/adapters"
    10  	"github.com/prebid/prebid-server/v2/config"
    11  	"github.com/prebid/prebid-server/v2/errortypes"
    12  	"github.com/prebid/prebid-server/v2/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  		ImpIDs:  openrtb_ext.GetImpIDs(tlRequest.Imp)})
   125  	return reqs, errs
   126  }
   127  
   128  func getPublisher(request *openrtb2.BidRequest) *openrtb2.Publisher {
   129  	if request.App != nil {
   130  		return request.App.Publisher
   131  	}
   132  	return request.Site.Publisher
   133  }
   134  
   135  func getBidCount(bidResponse openrtb2.BidResponse) int {
   136  	c := 0
   137  	for _, sb := range bidResponse.SeatBid {
   138  		c = c + len(sb.Bid)
   139  	}
   140  	return c
   141  }
   142  
   143  func (a *TripleliftNativeAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   144  	if response.StatusCode == http.StatusNoContent {
   145  		return nil, nil
   146  	}
   147  
   148  	if response.StatusCode == http.StatusBadRequest {
   149  		return nil, []error{&errortypes.BadInput{
   150  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   151  		}}
   152  	}
   153  
   154  	if response.StatusCode != http.StatusOK {
   155  		return nil, []error{&errortypes.BadServerResponse{Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}}
   156  	}
   157  	var bidResp openrtb2.BidResponse
   158  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   159  		return nil, []error{err}
   160  	}
   161  	var errs []error
   162  	count := getBidCount(bidResp)
   163  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(count)
   164  
   165  	for _, sb := range bidResp.SeatBid {
   166  		for i := 0; i < len(sb.Bid); i++ {
   167  			bid := sb.Bid[i]
   168  			var bidExt TripleliftRespExt
   169  			bidType := getBidType(bidExt)
   170  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   171  				Bid:     &bid,
   172  				BidType: bidType,
   173  			})
   174  		}
   175  	}
   176  	return bidResponse, errs
   177  }
   178  
   179  // Builder builds a new instance of the TripleliftNative adapter for the given bidder with the given config.
   180  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   181  	extraInfo, err := getExtraInfo(config.ExtraAdapterInfo)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	// Populate map for faster memory access
   187  	extraInfo.PublisherWhitelistMap = make(map[string]struct{}, len(extraInfo.PublisherWhitelist))
   188  	for _, v := range extraInfo.PublisherWhitelist {
   189  		extraInfo.PublisherWhitelistMap[v] = struct{}{}
   190  	}
   191  
   192  	bidder := &TripleliftNativeAdapter{
   193  		endpoint: config.Endpoint,
   194  		extInfo:  extraInfo,
   195  	}
   196  	return bidder, nil
   197  }
   198  
   199  func getExtraInfo(v string) (TripleliftNativeExtInfo, error) {
   200  	if len(v) == 0 {
   201  		return getDefaultExtraInfo(), nil
   202  	}
   203  
   204  	var extraInfo TripleliftNativeExtInfo
   205  	if err := json.Unmarshal([]byte(v), &extraInfo); err != nil {
   206  		return extraInfo, fmt.Errorf("invalid extra info: %v", err)
   207  	}
   208  
   209  	return extraInfo, nil
   210  }
   211  
   212  func getDefaultExtraInfo() TripleliftNativeExtInfo {
   213  	return TripleliftNativeExtInfo{
   214  		PublisherWhitelist: []string{},
   215  	}
   216  }