github.com/prebid/prebid-server/v2@v2.18.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/v20/openrtb2"
    12  	"github.com/prebid/prebid-server/v2/adapters"
    13  	"github.com/prebid/prebid-server/v2/config"
    14  	"github.com/prebid/prebid-server/v2/errortypes"
    15  	"github.com/prebid/prebid-server/v2/macros"
    16  	"github.com/prebid/prebid-server/v2/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{Message: 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{Message: e})
    87  			continue
    88  		}
    89  		data := &adapters.RequestData{
    90  			Method:  "POST",
    91  			Uri:     uri,
    92  			Body:    body,
    93  			Headers: bidHeaders,
    94  			ImpIDs:  openrtb_ext.GetImpIDs(r.Imp),
    95  		}
    96  		datas = append(datas, data)
    97  	}
    98  
    99  	return datas, errs
   100  }
   101  
   102  func (ads *AdopplerAdapter) MakeBids(
   103  	intReq *openrtb2.BidRequest,
   104  	extReq *adapters.RequestData,
   105  	resp *adapters.ResponseData,
   106  ) (
   107  	*adapters.BidderResponse,
   108  	[]error,
   109  ) {
   110  	if resp.StatusCode == http.StatusNoContent {
   111  		return nil, nil
   112  	}
   113  	if resp.StatusCode == http.StatusBadRequest {
   114  		return nil, []error{&errortypes.BadInput{Message: "bad request"}}
   115  	}
   116  	if resp.StatusCode != http.StatusOK {
   117  		err := &errortypes.BadServerResponse{
   118  			Message: fmt.Sprintf("unexpected status: %d", resp.StatusCode),
   119  		}
   120  		return nil, []error{err}
   121  	}
   122  
   123  	var bidResp openrtb2.BidResponse
   124  	err := json.Unmarshal(resp.Body, &bidResp)
   125  	if err != nil {
   126  		err := &errortypes.BadServerResponse{
   127  			Message: fmt.Sprintf("invalid body: %s", err.Error()),
   128  		}
   129  		return nil, []error{err}
   130  	}
   131  
   132  	impTypes := make(map[string]openrtb_ext.BidType)
   133  	for _, imp := range intReq.Imp {
   134  		if _, ok := impTypes[imp.ID]; ok {
   135  			return nil, []error{&errortypes.BadInput{
   136  				Message: fmt.Sprintf("duplicate $.imp.id %s", imp.ID),
   137  			}}
   138  		}
   139  		if imp.Banner != nil {
   140  			impTypes[imp.ID] = openrtb_ext.BidTypeBanner
   141  		} else if imp.Video != nil {
   142  			impTypes[imp.ID] = openrtb_ext.BidTypeVideo
   143  		} else if imp.Audio != nil {
   144  			impTypes[imp.ID] = openrtb_ext.BidTypeAudio
   145  		} else if imp.Native != nil {
   146  			impTypes[imp.ID] = openrtb_ext.BidTypeNative
   147  		} else {
   148  			return nil, []error{&errortypes.BadInput{
   149  				Message: "one of $.imp.banner, $.imp.video, $.imp.audio and $.imp.native field required",
   150  			}}
   151  		}
   152  	}
   153  
   154  	var bids []*adapters.TypedBid
   155  	for _, seatBid := range bidResp.SeatBid {
   156  		for _, bid := range seatBid.Bid {
   157  			tp, ok := impTypes[bid.ImpID]
   158  			if !ok {
   159  				err := &errortypes.BadServerResponse{
   160  					Message: fmt.Sprintf("unknown impid: %s", bid.ImpID),
   161  				}
   162  				return nil, []error{err}
   163  			}
   164  
   165  			var bidVideo *openrtb_ext.ExtBidPrebidVideo
   166  			if tp == openrtb_ext.BidTypeVideo {
   167  				adsExt, err := unmarshalAdsExt(bid.Ext)
   168  				if err != nil {
   169  					return nil, []error{&errortypes.BadServerResponse{Message: err.Error()}}
   170  				}
   171  				if adsExt == nil || adsExt.Video == nil {
   172  					return nil, []error{&errortypes.BadServerResponse{
   173  						Message: "$.seatbid.bid.ext.ads.video required",
   174  					}}
   175  				}
   176  				bidVideo = &openrtb_ext.ExtBidPrebidVideo{
   177  					Duration:        adsExt.Video.Duration,
   178  					PrimaryCategory: head(bid.Cat),
   179  				}
   180  			}
   181  			bids = append(bids, &adapters.TypedBid{
   182  				Bid:      &bid,
   183  				BidType:  tp,
   184  				BidVideo: bidVideo,
   185  			})
   186  		}
   187  	}
   188  
   189  	adsResp := adapters.NewBidderResponseWithBidsCapacity(len(bids))
   190  	adsResp.Bids = bids
   191  
   192  	return adsResp, nil
   193  }
   194  
   195  func (ads *AdopplerAdapter) bidUri(ext *openrtb_ext.ExtImpAdoppler) (string, error) {
   196  	params := macros.EndpointTemplateParams{}
   197  	params.AdUnit = url.PathEscape(ext.AdUnit)
   198  	if ext.Client == "" {
   199  		params.AccountID = DefaultClient
   200  	} else {
   201  		params.AccountID = url.PathEscape(ext.Client)
   202  	}
   203  
   204  	return macros.ResolveMacros(ads.endpoint, params)
   205  }
   206  
   207  func unmarshalExt(ext json.RawMessage) (*openrtb_ext.ExtImpAdoppler, error) {
   208  	var bext adapters.ExtImpBidder
   209  	err := json.Unmarshal(ext, &bext)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	var adsExt openrtb_ext.ExtImpAdoppler
   215  	err = json.Unmarshal(bext.Bidder, &adsExt)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	if adsExt.AdUnit == "" {
   221  		return nil, errors.New("$.imp.ext.adoppler.adunit required")
   222  	}
   223  
   224  	return &adsExt, nil
   225  }
   226  
   227  func unmarshalAdsExt(ext json.RawMessage) (*adsImpExt, error) {
   228  	var e struct {
   229  		Ads *adsImpExt `json:"ads"`
   230  	}
   231  	err := json.Unmarshal(ext, &e)
   232  
   233  	return e.Ads, err
   234  }
   235  
   236  func head(s []string) string {
   237  	if len(s) == 0 {
   238  		return ""
   239  	}
   240  
   241  	return s[0]
   242  }