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

     1  package telaria
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/adapters"
    11  	"github.com/prebid/prebid-server/v2/config"
    12  	"github.com/prebid/prebid-server/v2/errortypes"
    13  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    14  )
    15  
    16  const Endpoint = "https://ads.tremorhub.com/ad/rtb/prebid"
    17  
    18  type TelariaAdapter struct {
    19  	URI string
    20  }
    21  
    22  // This will be part of imp[i].ext when this adapter calls out the Telaria Ad Server
    23  type ImpressionExtOut struct {
    24  	OriginalTagID       string `json:"originalTagid"`
    25  	OriginalPublisherID string `json:"originalPublisherid"`
    26  }
    27  
    28  type telariaBidExt struct {
    29  	Extra json.RawMessage `json:"extra,omitempty"`
    30  }
    31  
    32  // Endpoint for Telaria Ad server
    33  func (a *TelariaAdapter) FetchEndpoint() string {
    34  	return a.URI
    35  }
    36  
    37  // Checker method to ensure len(request.Imp) > 0
    38  func (a *TelariaAdapter) CheckHasImps(request *openrtb2.BidRequest) error {
    39  	if len(request.Imp) == 0 {
    40  		err := &errortypes.BadInput{
    41  			Message: "Telaria: Missing Imp Object",
    42  		}
    43  		return err
    44  	}
    45  	return nil
    46  }
    47  
    48  // Checking if Imp[i].Video exists and Imp[i].Banner doesn't exist
    49  func (a *TelariaAdapter) CheckHasVideoObject(request *openrtb2.BidRequest) error {
    50  	hasVideoObject := false
    51  
    52  	for _, imp := range request.Imp {
    53  		if imp.Banner != nil {
    54  			return &errortypes.BadInput{
    55  				Message: "Telaria: Banner not supported",
    56  			}
    57  		}
    58  
    59  		hasVideoObject = hasVideoObject || imp.Video != nil
    60  	}
    61  
    62  	if !hasVideoObject {
    63  		return &errortypes.BadInput{
    64  			Message: "Telaria: Only Supports Video",
    65  		}
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // Fetches the populated header object
    72  func GetHeaders(request *openrtb2.BidRequest) *http.Header {
    73  	headers := http.Header{}
    74  	headers.Add("Content-Type", "application/json;charset=utf-8")
    75  	headers.Add("Accept", "application/json")
    76  	headers.Add("X-Openrtb-Version", "2.5")
    77  
    78  	if request.Device != nil {
    79  		if len(request.Device.UA) > 0 {
    80  			headers.Add("User-Agent", request.Device.UA)
    81  		}
    82  
    83  		if len(request.Device.IP) > 0 {
    84  			headers.Add("X-Forwarded-For", request.Device.IP)
    85  		}
    86  
    87  		if len(request.Device.Language) > 0 {
    88  			headers.Add("Accept-Language", request.Device.Language)
    89  		}
    90  
    91  		if request.Device.DNT != nil {
    92  			headers.Add("Dnt", strconv.Itoa(int(*request.Device.DNT)))
    93  		}
    94  	}
    95  
    96  	return &headers
    97  }
    98  
    99  // Checks the imp[i].ext object and returns a imp.ext object as per ExtImpTelaria format
   100  func (a *TelariaAdapter) FetchTelariaExtImpParams(imp *openrtb2.Imp) (*openrtb_ext.ExtImpTelaria, error) {
   101  	var bidderExt adapters.ExtImpBidder
   102  	err := json.Unmarshal(imp.Ext, &bidderExt)
   103  
   104  	if err != nil {
   105  		err = &errortypes.BadInput{
   106  			Message: "Telaria: ext.bidder not provided",
   107  		}
   108  
   109  		return nil, err
   110  	}
   111  
   112  	var telariaExt openrtb_ext.ExtImpTelaria
   113  	err = json.Unmarshal(bidderExt.Bidder, &telariaExt)
   114  
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	if telariaExt.SeatCode == "" {
   120  		return nil, &errortypes.BadInput{Message: "Telaria: Seat Code required"}
   121  	}
   122  
   123  	return &telariaExt, nil
   124  }
   125  
   126  // Method to fetch the original publisher ID. Note that this method must be called
   127  // before we replace publisher.ID with seatCode
   128  func (a *TelariaAdapter) FetchOriginalPublisherID(request *openrtb2.BidRequest) string {
   129  
   130  	if request.Site != nil && request.Site.Publisher != nil {
   131  		return request.Site.Publisher.ID
   132  	} else if request.App != nil && request.App.Publisher != nil {
   133  		return request.App.Publisher.ID
   134  	}
   135  
   136  	return ""
   137  }
   138  
   139  // Method to do a deep copy of the publisher object. It also adds the seatCode as publisher.ID
   140  func (a *TelariaAdapter) MakePublisherObject(seatCode string, publisher *openrtb2.Publisher) *openrtb2.Publisher {
   141  	var pub = &openrtb2.Publisher{ID: seatCode}
   142  
   143  	if publisher != nil {
   144  		pub.Domain = publisher.Domain
   145  		pub.Name = publisher.Name
   146  		pub.Cat = publisher.Cat
   147  		pub.Ext = publisher.Ext
   148  	}
   149  
   150  	return pub
   151  }
   152  
   153  // This method changes <site/app>.publisher.id to the seatCode
   154  func (a *TelariaAdapter) PopulatePublisherId(request *openrtb2.BidRequest, seatCode string) (*openrtb2.Site, *openrtb2.App) {
   155  	if request.Site != nil {
   156  		siteCopy := *request.Site
   157  		siteCopy.Publisher = a.MakePublisherObject(seatCode, request.Site.Publisher)
   158  		return &siteCopy, nil
   159  	} else if request.App != nil {
   160  		appCopy := *request.App
   161  		appCopy.Publisher = a.MakePublisherObject(seatCode, request.App.Publisher)
   162  		return nil, &appCopy
   163  	}
   164  	return nil, nil
   165  }
   166  
   167  func (a *TelariaAdapter) MakeRequests(requestIn *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
   168  
   169  	// make a copy of the incoming request
   170  	request := *requestIn
   171  
   172  	// ensure that the request has Impressions
   173  	if noImps := a.CheckHasImps(&request); noImps != nil {
   174  		return nil, []error{noImps}
   175  	}
   176  
   177  	// ensure that the request has a Video object
   178  	if noVideoObjectError := a.CheckHasVideoObject(&request); noVideoObjectError != nil {
   179  		return nil, []error{noVideoObjectError}
   180  	}
   181  
   182  	var seatCode string
   183  	originalPublisherID := a.FetchOriginalPublisherID(&request)
   184  
   185  	var telariaImpExt *openrtb_ext.ExtImpTelaria
   186  	var err error
   187  
   188  	var imp = request.Imp[0]
   189  	// fetch adCode & seatCode from imp[i].ext
   190  	telariaImpExt, err = a.FetchTelariaExtImpParams(&imp)
   191  	if err != nil {
   192  		return nil, []error{err}
   193  	}
   194  
   195  	seatCode = telariaImpExt.SeatCode
   196  
   197  	// move the original tagId and the original publisher.id into the imp[i].ext object
   198  	imp.Ext, err = json.Marshal(&ImpressionExtOut{imp.TagID, originalPublisherID})
   199  	if err != nil {
   200  		return nil, []error{err}
   201  	}
   202  
   203  	// Swap the tagID with adCode
   204  	imp.TagID = telariaImpExt.AdCode
   205  
   206  	// Add the Extra from Imp to the top level Ext
   207  	if telariaImpExt != nil && telariaImpExt.Extra != nil {
   208  		request.Ext, err = json.Marshal(&telariaBidExt{Extra: telariaImpExt.Extra})
   209  		if err != nil {
   210  			return nil, []error{err}
   211  		}
   212  	}
   213  	request.Imp = []openrtb2.Imp{imp}
   214  
   215  	// Add seatCode to <Site/App>.Publisher.ID
   216  	siteObject, appObject := a.PopulatePublisherId(&request, seatCode)
   217  
   218  	request.Site = siteObject
   219  	request.App = appObject
   220  
   221  	reqJSON, err := json.Marshal(request)
   222  	if err != nil {
   223  		return nil, []error{err}
   224  	}
   225  
   226  	return []*adapters.RequestData{{
   227  		Method:  "POST",
   228  		Uri:     a.FetchEndpoint(),
   229  		Body:    reqJSON,
   230  		Headers: *GetHeaders(&request),
   231  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
   232  	}}, nil
   233  }
   234  
   235  func (a *TelariaAdapter) CheckResponseStatusCodes(response *adapters.ResponseData) error {
   236  	if response.StatusCode == http.StatusNoContent {
   237  		return &errortypes.BadInput{Message: "Telaria: Invalid Bid Request received by the server"}
   238  	}
   239  
   240  	if response.StatusCode == http.StatusBadRequest {
   241  		return &errortypes.BadInput{
   242  			Message: fmt.Sprintf("Telaria: Unexpected status code: [ %d ] ", response.StatusCode),
   243  		}
   244  	}
   245  
   246  	if response.StatusCode == http.StatusServiceUnavailable {
   247  		return &errortypes.BadInput{
   248  			Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
   249  		}
   250  	}
   251  
   252  	if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices {
   253  		return &errortypes.BadInput{
   254  			Message: fmt.Sprintf("Telaria: Something went wrong, please contact your Account Manager. Status Code: [ %d ] ", response.StatusCode),
   255  		}
   256  	}
   257  
   258  	return nil
   259  }
   260  
   261  func (a *TelariaAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   262  
   263  	httpStatusError := a.CheckResponseStatusCodes(response)
   264  	if httpStatusError != nil {
   265  		return nil, []error{httpStatusError}
   266  	}
   267  
   268  	responseBody := response.Body
   269  
   270  	var bidResp openrtb2.BidResponse
   271  	if err := json.Unmarshal(responseBody, &bidResp); err != nil {
   272  		return nil, []error{&errortypes.BadServerResponse{
   273  			Message: "Telaria: Bad Server Response",
   274  		}}
   275  	}
   276  
   277  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
   278  	sb := bidResp.SeatBid[0]
   279  
   280  	for i := range sb.Bid {
   281  		bid := sb.Bid[i]
   282  		if i >= len(internalRequest.Imp) {
   283  			break
   284  		}
   285  		bid.ImpID = internalRequest.Imp[i].ID
   286  		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   287  			Bid:     &bid,
   288  			BidType: openrtb_ext.BidTypeVideo,
   289  		})
   290  	}
   291  	return bidResponse, nil
   292  }
   293  
   294  // Builder builds a new instance of the Telaria adapter for the given bidder with the given config.
   295  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   296  	endpoint := config.Endpoint
   297  	if endpoint == "" {
   298  		endpoint = Endpoint // Hardcoded default
   299  	}
   300  
   301  	bidder := &TelariaAdapter{
   302  		URI: endpoint,
   303  	}
   304  	return bidder, nil
   305  }