github.com/prebid/prebid-server@v0.275.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/v19/adcom1"
     9  	"github.com/prebid/openrtb/v19/openrtb2"
    10  	"github.com/prebid/prebid-server/adapters"
    11  	"github.com/prebid/prebid-server/config"
    12  	"github.com/prebid/prebid-server/errortypes"
    13  	"github.com/prebid/prebid-server/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  	}, nil
   118  }
   119  
   120  func makeImps(imp openrtb2.Imp) (openrtb2.Imp, error) {
   121  	if imp.Banner == nil && imp.Video == nil {
   122  		return openrtb2.Imp{}, &errortypes.BadInput{
   123  			Message: fmt.Sprintf("Imp ID %s must have at least one of [Banner, Video] defined", imp.ID),
   124  		}
   125  	}
   126  
   127  	var bidderExt adapters.ExtImpBidder
   128  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   129  		return openrtb2.Imp{}, &errortypes.BadInput{
   130  			Message: err.Error(),
   131  		}
   132  	}
   133  
   134  	var ttxExt openrtb_ext.ExtImp33across
   135  	if err := json.Unmarshal(bidderExt.Bidder, &ttxExt); err != nil {
   136  		return openrtb2.Imp{}, &errortypes.BadInput{
   137  			Message: err.Error(),
   138  		}
   139  	}
   140  
   141  	var impExt Ext
   142  	impExt.Ttx.Prod = ttxExt.ProductId
   143  
   144  	impExt.Ttx.Zoneid = ttxExt.SiteId
   145  
   146  	if len(ttxExt.ZoneId) > 0 {
   147  		impExt.Ttx.Zoneid = ttxExt.ZoneId
   148  	}
   149  
   150  	impExtJSON, err := json.Marshal(impExt)
   151  	if err != nil {
   152  		return openrtb2.Imp{}, &errortypes.BadInput{
   153  			Message: err.Error(),
   154  		}
   155  	}
   156  
   157  	imp.Ext = impExtJSON
   158  
   159  	// Validate Video if it exists
   160  	if imp.Video != nil {
   161  		videoCopy, err := validateVideoParams(imp.Video, impExt.Ttx.Prod)
   162  
   163  		imp.Video = videoCopy
   164  
   165  		if err != nil {
   166  			return openrtb2.Imp{}, &errortypes.BadInput{
   167  				Message: err.Error(),
   168  			}
   169  		}
   170  	}
   171  
   172  	return imp, nil
   173  }
   174  
   175  func makeReqExt(request *openrtb2.BidRequest) ([]byte, error) {
   176  	var reqExt reqExt
   177  
   178  	if len(request.Ext) > 0 {
   179  		if err := json.Unmarshal(request.Ext, &reqExt); err != nil {
   180  			return nil, err
   181  		}
   182  	}
   183  
   184  	if reqExt.Ttx == nil {
   185  		reqExt.Ttx = &reqTtxExt{}
   186  	}
   187  
   188  	if reqExt.Ttx.Caller == nil {
   189  		reqExt.Ttx.Caller = make([]TtxCaller, 0)
   190  	}
   191  
   192  	reqExt.Ttx.Caller = append(reqExt.Ttx.Caller, CALLER)
   193  
   194  	return json.Marshal(reqExt)
   195  }
   196  
   197  // MakeBids make the bids for the bid response.
   198  func (a *TtxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   199  	if response.StatusCode == http.StatusNoContent {
   200  		return nil, nil
   201  	}
   202  
   203  	if response.StatusCode == http.StatusBadRequest {
   204  		return nil, []error{&errortypes.BadInput{
   205  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   206  		}}
   207  	}
   208  
   209  	if response.StatusCode != http.StatusOK {
   210  		return nil, []error{&errortypes.BadServerResponse{
   211  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   212  		}}
   213  	}
   214  
   215  	var bidResp openrtb2.BidResponse
   216  
   217  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   218  		return nil, []error{err}
   219  	}
   220  
   221  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   222  
   223  	for _, sb := range bidResp.SeatBid {
   224  		for i := range sb.Bid {
   225  			var bidExt bidExt
   226  			var bidType openrtb_ext.BidType
   227  
   228  			if err := json.Unmarshal(sb.Bid[i].Ext, &bidExt); err != nil {
   229  				bidType = openrtb_ext.BidTypeBanner
   230  			} else {
   231  				bidType = getBidType(bidExt)
   232  			}
   233  
   234  			bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   235  				Bid:     &sb.Bid[i],
   236  				BidType: bidType,
   237  			})
   238  		}
   239  	}
   240  	return bidResponse, nil
   241  
   242  }
   243  
   244  func validateVideoParams(video *openrtb2.Video, prod string) (*openrtb2.Video, error) {
   245  	videoCopy := *video
   246  	if videoCopy.W == 0 ||
   247  		videoCopy.H == 0 ||
   248  		videoCopy.Protocols == nil ||
   249  		videoCopy.MIMEs == nil ||
   250  		videoCopy.PlaybackMethod == nil {
   251  
   252  		return nil, &errortypes.BadInput{
   253  			Message: "One or more invalid or missing video field(s) w, h, protocols, mimes, playbackmethod",
   254  		}
   255  	}
   256  
   257  	if videoCopy.Placement == 0 {
   258  		videoCopy.Placement = 2
   259  	}
   260  
   261  	if prod == "instream" {
   262  		videoCopy.Placement = 1
   263  
   264  		if videoCopy.StartDelay == nil {
   265  			videoCopy.StartDelay = adcom1.StartDelay.Ptr(0)
   266  		}
   267  	}
   268  
   269  	return &videoCopy, nil
   270  }
   271  
   272  func getBidType(ext bidExt) openrtb_ext.BidType {
   273  	if ext.Ttx.MediaType == "video" {
   274  		return openrtb_ext.BidTypeVideo
   275  	}
   276  
   277  	return openrtb_ext.BidTypeBanner
   278  }
   279  
   280  // Builder builds a new instance of the 33Across adapter for the given bidder with the given config.
   281  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   282  	bidder := &TtxAdapter{
   283  		endpoint: config.Endpoint,
   284  	}
   285  	return bidder, nil
   286  }