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

     1  package adoppler
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"text/template"
    10  
    11  	"github.com/prebid/openrtb/v19/openrtb2"
    12  	"github.com/prebid/prebid-server/adapters"
    13  	"github.com/prebid/prebid-server/config"
    14  	"github.com/prebid/prebid-server/errortypes"
    15  	"github.com/prebid/prebid-server/macros"
    16  	"github.com/prebid/prebid-server/openrtb_ext"
    17  )
    18  
    19  const DefaultClient = "app"
    20  
    21  var bidHeaders http.Header = map[string][]string{
    22  	"Accept":            {"application/json"},
    23  	"Content-Type":      {"application/json;charset=utf-8"},
    24  	"X-OpenRTB-Version": {"2.5"},
    25  }
    26  
    27  type adsVideoExt struct {
    28  	Duration int `json:"duration"`
    29  }
    30  
    31  type adsImpExt struct {
    32  	Video *adsVideoExt `json:"video"`
    33  }
    34  
    35  type AdopplerAdapter struct {
    36  	endpoint *template.Template
    37  }
    38  
    39  // Builder builds a new instance of the Adoppler adapter for the given bidder with the given config.
    40  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    41  	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
    42  	if err != nil {
    43  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    44  	}
    45  
    46  	bidder := &AdopplerAdapter{
    47  		endpoint: template,
    48  	}
    49  	return bidder, nil
    50  }
    51  
    52  func (ads *AdopplerAdapter) MakeRequests(
    53  	req *openrtb2.BidRequest,
    54  	info *adapters.ExtraRequestInfo,
    55  ) (
    56  	[]*adapters.RequestData,
    57  	[]error,
    58  ) {
    59  	if len(req.Imp) == 0 {
    60  		return nil, nil
    61  	}
    62  
    63  	var datas []*adapters.RequestData
    64  	var errs []error
    65  	for _, imp := range req.Imp {
    66  		ext, err := unmarshalExt(imp.Ext)
    67  		if err != nil {
    68  			errs = append(errs, &errortypes.BadInput{err.Error()})
    69  			continue
    70  		}
    71  
    72  		var r openrtb2.BidRequest = *req
    73  		r.ID = req.ID + "-" + ext.AdUnit
    74  		r.Imp = []openrtb2.Imp{imp}
    75  
    76  		body, err := json.Marshal(r)
    77  		if err != nil {
    78  			errs = append(errs, err)
    79  			continue
    80  		}
    81  
    82  		uri, err := ads.bidUri(ext)
    83  		if err != nil {
    84  			e := fmt.Sprintf("Unable to build bid URI: %s",
    85  				err.Error())
    86  			errs = append(errs, &errortypes.BadInput{e})
    87  			continue
    88  		}
    89  		data := &adapters.RequestData{
    90  			Method:  "POST",
    91  			Uri:     uri,
    92  			Body:    body,
    93  			Headers: bidHeaders,
    94  		}
    95  		datas = append(datas, data)
    96  	}
    97  
    98  	return datas, errs
    99  }
   100  
   101  func (ads *AdopplerAdapter) MakeBids(
   102  	intReq *openrtb2.BidRequest,
   103  	extReq *adapters.RequestData,
   104  	resp *adapters.ResponseData,
   105  ) (
   106  	*adapters.BidderResponse,
   107  	[]error,
   108  ) {
   109  	if resp.StatusCode == http.StatusNoContent {
   110  		return nil, nil
   111  	}
   112  	if resp.StatusCode == http.StatusBadRequest {
   113  		return nil, []error{&errortypes.BadInput{"bad request"}}
   114  	}
   115  	if resp.StatusCode != http.StatusOK {
   116  		err := &errortypes.BadServerResponse{
   117  			fmt.Sprintf("unexpected status: %d", resp.StatusCode),
   118  		}
   119  		return nil, []error{err}
   120  	}
   121  
   122  	var bidResp openrtb2.BidResponse
   123  	err := json.Unmarshal(resp.Body, &bidResp)
   124  	if err != nil {
   125  		err := &errortypes.BadServerResponse{
   126  			fmt.Sprintf("invalid body: %s", err.Error()),
   127  		}
   128  		return nil, []error{err}
   129  	}
   130  
   131  	impTypes := make(map[string]openrtb_ext.BidType)
   132  	for _, imp := range intReq.Imp {
   133  		if _, ok := impTypes[imp.ID]; ok {
   134  			return nil, []error{&errortypes.BadInput{
   135  				fmt.Sprintf("duplicate $.imp.id %s", imp.ID),
   136  			}}
   137  		}
   138  		if imp.Banner != nil {
   139  			impTypes[imp.ID] = openrtb_ext.BidTypeBanner
   140  		} else if imp.Video != nil {
   141  			impTypes[imp.ID] = openrtb_ext.BidTypeVideo
   142  		} else if imp.Audio != nil {
   143  			impTypes[imp.ID] = openrtb_ext.BidTypeAudio
   144  		} else if imp.Native != nil {
   145  			impTypes[imp.ID] = openrtb_ext.BidTypeNative
   146  		} else {
   147  			return nil, []error{&errortypes.BadInput{
   148  				"one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required",
   149  			}}
   150  		}
   151  	}
   152  
   153  	var bids []*adapters.TypedBid
   154  	for _, seatBid := range bidResp.SeatBid {
   155  		for _, bid := range seatBid.Bid {
   156  			tp, ok := impTypes[bid.ImpID]
   157  			if !ok {
   158  				err := &errortypes.BadServerResponse{
   159  					fmt.Sprintf("unknown impid: %s", bid.ImpID),
   160  				}
   161  				return nil, []error{err}
   162  			}
   163  
   164  			var bidVideo *openrtb_ext.ExtBidPrebidVideo
   165  			if tp == openrtb_ext.BidTypeVideo {
   166  				adsExt, err := unmarshalAdsExt(bid.Ext)
   167  				if err != nil {
   168  					return nil, []error{&errortypes.BadServerResponse{err.Error()}}
   169  				}
   170  				if adsExt == nil || adsExt.Video == nil {
   171  					return nil, []error{&errortypes.BadServerResponse{
   172  						"$.seatbid.bid.ext.ads.video required",
   173  					}}
   174  				}
   175  				bidVideo = &openrtb_ext.ExtBidPrebidVideo{
   176  					Duration:        adsExt.Video.Duration,
   177  					PrimaryCategory: head(bid.Cat),
   178  				}
   179  			}
   180  			bids = append(bids, &adapters.TypedBid{
   181  				Bid:      &bid,
   182  				BidType:  tp,
   183  				BidVideo: bidVideo,
   184  			})
   185  		}
   186  	}
   187  
   188  	adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids))
   189  	adsResp.Bids = bids
   190  
   191  	return adsResp, nil
   192  }
   193  
   194  func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, error) {
   195  	params := macros.EndpointTemplateParams{}
   196  	params.AdUnit = url.PathEscape(ext.AdUnit)
   197  	if ext.Client == "" {
   198  		params.AccountID = DefaultClient
   199  	} else {
   200  		params.AccountID = url.PathEscape(ext.Client)
   201  	}
   202  
   203  	return macros.ResolveMacros(ads.endpoint, params)
   204  }
   205  
   206  func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) {
   207  	var bext adapters.ExtImpBidder
   208  	err := json.Unmarshal(ext, &bext)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	var adsExt openrtb_ext.ExtImpAdoppler
   214  	err = json.Unmarshal(bext.Bidder, &adsExt)
   215  	if err != nil {
   216  		return nil, err
   217  	}
   218  
   219  	if adsExt.AdUnit == "" {
   220  		return nil, errors.New("$.imp.ext.adoppler.adunit required")
   221  	}
   222  
   223  	return &adsExt, nil
   224  }
   225  
   226  func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) {
   227  	var e struct {
   228  		Ads *adsImpExt `json:"ads"`
   229  	}
   230  	err := json.Unmarshal(ext, &e)
   231  
   232  	return e.Ads, err
   233  }
   234  
   235  func head(s []string) string {
   236  	if len(s) == 0 {
   237  		return ""
   238  	}
   239  
   240  	return s[0]
   241  }