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

     1  package ttx
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"github.com/prebid/openrtb/v20/adcom1"
     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  type TtxAdapter struct {
    17  	endpoint string
    18  }
    19  
    20  type Ext struct {
    21  	Ttx impTtxExt `json:"ttx"`
    22  }
    23  
    24  type impTtxExt struct {
    25  	Prod   string `json:"prod"`
    26  	Zoneid string `json:"zoneid,omitempty"`
    27  }
    28  
    29  type reqExt struct {
    30  	Ttx *reqTtxExt `json:"ttx,omitempty"`
    31  }
    32  
    33  type reqTtxExt struct {
    34  	Caller []TtxCaller `json:"caller,omitempty"`
    35  }
    36  
    37  type TtxCaller struct {
    38  	Name    string `json:"name,omitempty"`
    39  	Version string `json:"version,omitempty"`
    40  }
    41  
    42  // CALLER Info used to track Prebid Server
    43  // as one of the hops in the request to exchange
    44  var CALLER = TtxCaller{"Prebid-Server", "n/a"}
    45  
    46  type bidExt struct {
    47  	Ttx bidTtxExt `json:"ttx,omitempty"`
    48  }
    49  
    50  type bidTtxExt struct {
    51  	MediaType string `json:"mediaType,omitempty"`
    52  }
    53  
    54  // MakeRequests create the object for TTX Reqeust.
    55  func (a *TtxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    56  	var errs []error
    57  	var adapterRequests []*adapters.RequestData
    58  	var groupedImps = make(map[string][]openrtb2.Imp)
    59  
    60  	// Construct request extension common to all imps
    61  	// NOTE: not blocking adapter requests on errors
    62  	// since request extension is optional.
    63  	reqExt, err := makeReqExt(request)
    64  	if err != nil {
    65  		errs = append(errs, err)
    66  	}
    67  	request.Ext = reqExt
    68  
    69  	// We only support SRA for requests containing same prod and
    70  	// zoneID, therefore group all imps accordingly and create a http
    71  	// request for each such group
    72  	for i := 0; i < len(request.Imp); i++ {
    73  		if impCopy, err := makeImps(request.Imp[i]); err == nil {
    74  			var impExt Ext
    75  
    76  			// Skip over imps whose extensions cannot be read since
    77  			// we cannot glean Prod or ZoneID which are required to
    78  			// group together. However let's not block request creation.
    79  			if err := json.Unmarshal(impCopy.Ext, &impExt); err == nil {
    80  				impKey := impExt.Ttx.Prod + impExt.Ttx.Zoneid
    81  				groupedImps[impKey] = append(groupedImps[impKey], impCopy)
    82  			} else {
    83  				errs = append(errs, err)
    84  			}
    85  		} else {
    86  			errs = append(errs, err)
    87  		}
    88  	}
    89  
    90  	for _, impList := range groupedImps {
    91  		if adapterReq, err := a.makeRequest(*request, impList); err == nil {
    92  			adapterRequests = append(adapterRequests, adapterReq)
    93  		} else {
    94  			errs = append(errs, err)
    95  		}
    96  	}
    97  	return adapterRequests, errs
    98  }
    99  
   100  func (a *TtxAdapter) makeRequest(request openrtb2.BidRequest, impList []openrtb2.Imp) (*adapters.RequestData, error) {
   101  	request.Imp = impList
   102  
   103  	// Last Step
   104  	reqJSON, err := json.Marshal(request)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	headers := http.Header{}
   110  	headers.Add("Content-Type", "application/json;charset=utf-8")
   111  
   112  	return &adapters.RequestData{
   113  		Method:  "POST",
   114  		Uri:     a.endpoint,
   115  		Body:    reqJSON,
   116  		Headers: headers,
   117  		ImpIDs:  openrtb_ext.GetImpIDs(request.Imp),
   118  	}, nil
   119  }
   120  
   121  func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) {
   122  	if imp.Banner == nil && imp.Video == nil {
   123  		return openrtb2.Imp{}, &errortypes.BadInput{
   124  			Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID),
   125  		}
   126  	}
   127  
   128  	var bidderExt adapters.ExtImpBidder
   129  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   130  		return openrtb2.Imp{}, &errortypes.BadInput{
   131  			Message: err.Error(),
   132  		}
   133  	}
   134  
   135  	var ttxExt openrtb_ext.ExtImp33across
   136  	if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil {
   137  		return openrtb2.Imp{}, &errortypes.BadInput{
   138  			Message: err.Error(),
   139  		}
   140  	}
   141  
   142  	var impExt Ext
   143  	impExt.Ttx.Prod = ttxExt.ProductId
   144  
   145  	impExt.Ttx.Zoneid = ttxExt.SiteId
   146  
   147  	if len(ttxExt.ZoneId) > 0 {
   148  		impExt.Ttx.Zoneid = ttxExt.ZoneId
   149  	}
   150  
   151  	impExtJSON, err := json.Marshal(impExt)
   152  	if err != nil {
   153  		return openrtb2.Imp{}, &errortypes.BadInput{
   154  			Message: err.Error(),
   155  		}
   156  	}
   157  
   158  	imp.Ext = impExtJSON
   159  
   160  	// Validate Video if it exists
   161  	if imp.Video != nil {
   162  		videoCopy, err := validateVideoParams(imp.Video, impExt.Ttx.Prod)
   163  
   164  		imp.Video = videoCopy
   165  
   166  		if err != nil {
   167  			return openrtb2.Imp{}, &errortypes.BadInput{
   168  				Message: err.Error(),
   169  			}
   170  		}
   171  	}
   172  
   173  	return imp, nil
   174  }
   175  
   176  func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) {
   177  	var reqExt reqExt
   178  
   179  	if len(request.Ext) > 0 {
   180  		if err := json.Unmarshal(request.Ext, &reqExt); err != nil {
   181  			return nil, err
   182  		}
   183  	}
   184  
   185  	if reqExt.Ttx == nil {
   186  		reqExt.Ttx = &reqTtxExt{}
   187  	}
   188  
   189  	if reqExt.Ttx.Caller == nil {
   190  		reqExt.Ttx.Caller = make([]TtxCaller, 0)
   191  	}
   192  
   193  	reqExt.Ttx.Caller = append(reqExt.Ttx.Caller, CALLER)
   194  
   195  	return json.Marshal(reqExt)
   196  }
   197  
   198  // MakeBids make the bids for the bid response.
   199  func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   200  	if response.StatusCode == http.StatusNoContent {
   201  		return nil, nil
   202  	}
   203  
   204  	if response.StatusCode == http.StatusBadRequest {
   205  		return nil, []error{&errortypes.BadInput{
   206  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   207  		}}
   208  	}
   209  
   210  	if response.StatusCode != http.StatusOK {
   211  		return nil, []error{&errortypes.BadServerResponse{
   212  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   213  		}}
   214  	}
   215  
   216  	var bidResp openrtb2.BidResponse
   217  
   218  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   219  		return nil, []error{err}
   220  	}
   221  
   222  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   223  
   224  	for _, sb := range bidResp.SeatBid {
   225  		for i := range sb.Bid {
   226  			var bidExt bidExt
   227  			var bidType openrtb_ext.BidType
   228  
   229  			if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil {
   230  				bidType = openrtb_ext.BidTypeBanner
   231  			} else {
   232  				bidType = getBidType(bidExt)
   233  			}
   234  
   235  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   236  				Bid:     &sb.Bid[i],
   237  				BidType: bidType,
   238  			})
   239  		}
   240  	}
   241  	return bidResponse, nil
   242  
   243  }
   244  
   245  func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) {
   246  	videoCopy := *video
   247  	if (videoCopy.W == nil || *videoCopy.W == 0) ||
   248  		(videoCopy.H == nil || *videoCopy.H == 0) ||
   249  		videoCopy.Protocols == nil ||
   250  		videoCopy.MIMEs == nil ||
   251  		videoCopy.PlaybackMethod == nil {
   252  
   253  		return nil, &errortypes.BadInput{
   254  			Message: "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod",
   255  		}
   256  	}
   257  
   258  	if videoCopy.Placement == 0 {
   259  		videoCopy.Placement = 2
   260  	}
   261  
   262  	if prod == "instream" {
   263  		videoCopy.Placement = 1
   264  
   265  		if videoCopy.StartDelay == nil {
   266  			videoCopy.StartDelay = adcom1.StartDelay.Ptr(0)
   267  		}
   268  	}
   269  
   270  	return &videoCopy, nil
   271  }
   272  
   273  func getBidType(ext bidExt) openrtb_ext.BidType {
   274  	if ext.Ttx.MediaType == "video" {
   275  		return openrtb_ext.BidTypeVideo
   276  	}
   277  
   278  	return openrtb_ext.BidTypeBanner
   279  }
   280  
   281  // Builder builds a new instance of the 33Across adapter for the given bidder with the given config.
   282  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   283  	bidder := &TtxAdapter{
   284  		endpoint: config.Endpoint,
   285  	}
   286  	return bidder, nil
   287  }