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

     1  package pubmatic
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/buger/jsonparser"
    13  	"github.com/prebid/openrtb/v19/openrtb2"
    14  	"github.com/prebid/prebid-server/adapters"
    15  	"github.com/prebid/prebid-server/config"
    16  	"github.com/prebid/prebid-server/errortypes"
    17  	"github.com/prebid/prebid-server/openrtb_ext"
    18  )
    19  
    20  const MAX_IMPRESSIONS_PUBMATIC = 30
    21  
    22  type PubmaticAdapter struct {
    23  	URI string
    24  }
    25  
    26  type pubmaticBidExt struct {
    27  	BidType            *int                 `json:"BidType,omitempty"`
    28  	VideoCreativeInfo  *pubmaticBidExtVideo `json:"video,omitempty"`
    29  	Marketplace        string               `json:"marketplace,omitempty"`
    30  	PrebidDealPriority int                  `json:"prebiddealpriority,omitempty"`
    31  }
    32  
    33  type pubmaticWrapperExt struct {
    34  	ProfileID int `json:"profile,omitempty"`
    35  	VersionID int `json:"version,omitempty"`
    36  }
    37  
    38  type pubmaticBidExtVideo struct {
    39  	Duration *int `json:"duration,omitempty"`
    40  }
    41  
    42  type ExtImpBidderPubmatic struct {
    43  	adapters.ExtImpBidder
    44  	Data json.RawMessage `json:"data,omitempty"`
    45  }
    46  
    47  type ExtAdServer struct {
    48  	Name   string `json:"name"`
    49  	AdSlot string `json:"adslot"`
    50  }
    51  
    52  type marketplaceReqExt struct {
    53  	AllowedBidders []string `json:"allowedbidders,omitempty"`
    54  }
    55  
    56  type extRequestAdServer struct {
    57  	Wrapper     *pubmaticWrapperExt `json:"wrapper,omitempty"`
    58  	Acat        []string            `json:"acat,omitempty"`
    59  	Marketplace *marketplaceReqExt  `json:"marketplace,omitempty"`
    60  	openrtb_ext.ExtRequest
    61  }
    62  
    63  const (
    64  	dctrKeyName        = "key_val"
    65  	pmZoneIDKeyName    = "pmZoneId"
    66  	pmZoneIDKeyNameOld = "pmZoneID"
    67  	ImpExtAdUnitKey    = "dfp_ad_unit_code"
    68  	AdServerGAM        = "gam"
    69  	AdServerKey        = "adserver"
    70  	PBAdslotKey        = "pbadslot"
    71  )
    72  
    73  func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    74  	errs := make([]error, 0, len(request.Imp))
    75  
    76  	pubID := ""
    77  	extractWrapperExtFromImp := true
    78  	extractPubIDFromImp := true
    79  
    80  	newReqExt, err := extractPubmaticExtFromRequest(request)
    81  	if err != nil {
    82  		return nil, []error{err}
    83  	}
    84  	wrapperExt := newReqExt.Wrapper
    85  	if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
    86  		extractWrapperExtFromImp = false
    87  	}
    88  
    89  	for i := 0; i < len(request.Imp); i++ {
    90  		wrapperExtFromImp, pubIDFromImp, err := parseImpressionObject(&request.Imp[i], extractWrapperExtFromImp, extractPubIDFromImp)
    91  
    92  		// If the parsing is failed, remove imp and add the error.
    93  		if err != nil {
    94  			errs = append(errs, err)
    95  			request.Imp = append(request.Imp[:i], request.Imp[i+1:]...)
    96  			i--
    97  			continue
    98  		}
    99  
   100  		if extractWrapperExtFromImp {
   101  			if wrapperExtFromImp != nil {
   102  				if wrapperExt == nil {
   103  					wrapperExt = &pubmaticWrapperExt{}
   104  				}
   105  				if wrapperExt.ProfileID == 0 {
   106  					wrapperExt.ProfileID = wrapperExtFromImp.ProfileID
   107  				}
   108  				if wrapperExt.VersionID == 0 {
   109  					wrapperExt.VersionID = wrapperExtFromImp.VersionID
   110  				}
   111  
   112  				if wrapperExt != nil && wrapperExt.ProfileID != 0 && wrapperExt.VersionID != 0 {
   113  					extractWrapperExtFromImp = false
   114  				}
   115  			}
   116  		}
   117  
   118  		if extractPubIDFromImp && pubIDFromImp != "" {
   119  			pubID = pubIDFromImp
   120  			extractPubIDFromImp = false
   121  		}
   122  	}
   123  
   124  	// If all the requests are invalid, Call to adaptor is skipped
   125  	if len(request.Imp) == 0 {
   126  		return nil, errs
   127  	}
   128  
   129  	newReqExt.Wrapper = wrapperExt
   130  	rawExt, err := json.Marshal(newReqExt)
   131  	if err != nil {
   132  		return nil, []error{err}
   133  	}
   134  	request.Ext = rawExt
   135  
   136  	if request.Site != nil {
   137  		siteCopy := *request.Site
   138  		if siteCopy.Publisher != nil {
   139  			publisherCopy := *siteCopy.Publisher
   140  			publisherCopy.ID = pubID
   141  			siteCopy.Publisher = &publisherCopy
   142  		} else {
   143  			siteCopy.Publisher = &openrtb2.Publisher{ID: pubID}
   144  		}
   145  		request.Site = &siteCopy
   146  	} else if request.App != nil {
   147  		appCopy := *request.App
   148  		if appCopy.Publisher != nil {
   149  			publisherCopy := *appCopy.Publisher
   150  			publisherCopy.ID = pubID
   151  			appCopy.Publisher = &publisherCopy
   152  		} else {
   153  			appCopy.Publisher = &openrtb2.Publisher{ID: pubID}
   154  		}
   155  		request.App = &appCopy
   156  	}
   157  
   158  	reqJSON, err := json.Marshal(request)
   159  	if err != nil {
   160  		errs = append(errs, err)
   161  		return nil, errs
   162  	}
   163  
   164  	headers := http.Header{}
   165  	headers.Add("Content-Type", "application/json;charset=utf-8")
   166  	headers.Add("Accept", "application/json")
   167  	return []*adapters.RequestData{{
   168  		Method:  "POST",
   169  		Uri:     a.URI,
   170  		Body:    reqJSON,
   171  		Headers: headers,
   172  	}}, errs
   173  }
   174  
   175  // validateAdslot validate the optional adslot string
   176  // valid formats are 'adslot@WxH', 'adslot' and no adslot
   177  func validateAdSlot(adslot string, imp *openrtb2.Imp) error {
   178  	adSlotStr := strings.TrimSpace(adslot)
   179  
   180  	if len(adSlotStr) == 0 {
   181  		return nil
   182  	}
   183  
   184  	if !strings.Contains(adSlotStr, "@") {
   185  		imp.TagID = adSlotStr
   186  		return nil
   187  	}
   188  
   189  	adSlot := strings.Split(adSlotStr, "@")
   190  	if len(adSlot) == 2 && adSlot[0] != "" && adSlot[1] != "" {
   191  		imp.TagID = strings.TrimSpace(adSlot[0])
   192  
   193  		adSize := strings.Split(strings.ToLower(adSlot[1]), "x")
   194  		if len(adSize) != 2 {
   195  			return fmt.Errorf("Invalid size provided in adSlot %v", adSlotStr)
   196  		}
   197  
   198  		width, err := strconv.Atoi(strings.TrimSpace(adSize[0]))
   199  		if err != nil {
   200  			return fmt.Errorf("Invalid width provided in adSlot %v", adSlotStr)
   201  		}
   202  
   203  		heightStr := strings.Split(adSize[1], ":")
   204  		height, err := strconv.Atoi(strings.TrimSpace(heightStr[0]))
   205  		if err != nil {
   206  			return fmt.Errorf("Invalid height provided in adSlot %v", adSlotStr)
   207  		}
   208  
   209  		//In case of video, size could be derived from the player size
   210  		if imp.Banner != nil {
   211  			imp.Banner = assignBannerWidthAndHeight(imp.Banner, int64(width), int64(height))
   212  		}
   213  	} else {
   214  		return fmt.Errorf("Invalid adSlot %v", adSlotStr)
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  func assignBannerSize(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
   221  	if banner.W != nil && banner.H != nil {
   222  		return banner, nil
   223  	}
   224  
   225  	return assignBannerWidthAndHeight(banner, banner.Format[0].W, banner.Format[0].H), nil
   226  }
   227  
   228  func assignBannerWidthAndHeight(banner *openrtb2.Banner, w, h int64) *openrtb2.Banner {
   229  	bannerCopy := *banner
   230  	bannerCopy.W = openrtb2.Int64Ptr(w)
   231  	bannerCopy.H = openrtb2.Int64Ptr(h)
   232  	return &bannerCopy
   233  }
   234  
   235  // parseImpressionObject parse the imp to get it ready to send to pubmatic
   236  func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractPubIDFromImp bool) (*pubmaticWrapperExt, string, error) {
   237  	var wrapExt *pubmaticWrapperExt
   238  	var pubID string
   239  
   240  	// PubMatic supports banner and video impressions.
   241  	if imp.Banner == nil && imp.Video == nil && imp.Native == nil {
   242  		return wrapExt, pubID, fmt.Errorf("invalid MediaType. PubMatic only supports Banner, Video and Native. Ignoring ImpID=%s", imp.ID)
   243  	}
   244  
   245  	if imp.Audio != nil {
   246  		imp.Audio = nil
   247  	}
   248  
   249  	var bidderExt ExtImpBidderPubmatic
   250  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   251  		return wrapExt, pubID, err
   252  	}
   253  
   254  	var pubmaticExt openrtb_ext.ExtImpPubmatic
   255  	if err := json.Unmarshal(bidderExt.Bidder, &pubmaticExt); err != nil {
   256  		return wrapExt, pubID, err
   257  	}
   258  
   259  	if extractPubIDFromImp {
   260  		pubID = strings.TrimSpace(pubmaticExt.PublisherId)
   261  	}
   262  
   263  	// Parse Wrapper Extension only once per request
   264  	if extractWrapperExtFromImp && len(pubmaticExt.WrapExt) != 0 {
   265  		err := json.Unmarshal([]byte(pubmaticExt.WrapExt), &wrapExt)
   266  		if err != nil {
   267  			return wrapExt, pubID, fmt.Errorf("Error in Wrapper Parameters = %v  for ImpID = %v WrapperExt = %v", err.Error(), imp.ID, string(pubmaticExt.WrapExt))
   268  		}
   269  	}
   270  
   271  	if err := validateAdSlot(strings.TrimSpace(pubmaticExt.AdSlot), imp); err != nil {
   272  		return wrapExt, pubID, err
   273  	}
   274  
   275  	if imp.Banner != nil {
   276  		bannerCopy, err := assignBannerSize(imp.Banner)
   277  		if err != nil {
   278  			return wrapExt, pubID, err
   279  		}
   280  		imp.Banner = bannerCopy
   281  	}
   282  
   283  	if pubmaticExt.Kadfloor != "" {
   284  		bidfloor, err := strconv.ParseFloat(strings.TrimSpace(pubmaticExt.Kadfloor), 64)
   285  		if err == nil {
   286  			// In case of valid kadfloor, select maximum of original imp.bidfloor and kadfloor
   287  			imp.BidFloor = math.Max(bidfloor, imp.BidFloor)
   288  		}
   289  	}
   290  
   291  	extMap := make(map[string]interface{}, 0)
   292  	if pubmaticExt.Keywords != nil && len(pubmaticExt.Keywords) != 0 {
   293  		addKeywordsToExt(pubmaticExt.Keywords, extMap)
   294  	}
   295  	//Give preference to direct values of 'dctr' & 'pmZoneId' params in extension
   296  	if pubmaticExt.Dctr != "" {
   297  		extMap[dctrKeyName] = pubmaticExt.Dctr
   298  	}
   299  	if pubmaticExt.PmZoneID != "" {
   300  		extMap[pmZoneIDKeyName] = pubmaticExt.PmZoneID
   301  	}
   302  
   303  	if len(bidderExt.Data) > 0 {
   304  		populateFirstPartyDataImpAttributes(bidderExt.Data, extMap)
   305  	}
   306  
   307  	imp.Ext = nil
   308  	if len(extMap) > 0 {
   309  		ext, err := json.Marshal(extMap)
   310  		if err == nil {
   311  			imp.Ext = ext
   312  		}
   313  	}
   314  
   315  	return wrapExt, pubID, nil
   316  }
   317  
   318  // extractPubmaticExtFromRequest parse the req.ext to fetch wrapper and acat params
   319  func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (extRequestAdServer, error) {
   320  	// req.ext.prebid would always be there and Less nil cases to handle, more safe!
   321  	var pmReqExt extRequestAdServer
   322  
   323  	if request == nil || len(request.Ext) == 0 {
   324  		return pmReqExt, nil
   325  	}
   326  
   327  	reqExt := &openrtb_ext.ExtRequest{}
   328  	err := json.Unmarshal(request.Ext, &reqExt)
   329  	if err != nil {
   330  		return pmReqExt, fmt.Errorf("error decoding Request.ext : %s", err.Error())
   331  	}
   332  	pmReqExt.ExtRequest = *reqExt
   333  
   334  	reqExtBidderParams := make(map[string]json.RawMessage)
   335  	if reqExt.Prebid.BidderParams != nil {
   336  		err = json.Unmarshal(reqExt.Prebid.BidderParams, &reqExtBidderParams)
   337  		if err != nil {
   338  			return pmReqExt, err
   339  		}
   340  	}
   341  
   342  	//get request ext bidder params
   343  	if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 {
   344  		wrpExt := &pubmaticWrapperExt{}
   345  		err = json.Unmarshal(wrapperObj, wrpExt)
   346  		if err != nil {
   347  			return pmReqExt, err
   348  		}
   349  		pmReqExt.Wrapper = wrpExt
   350  	}
   351  
   352  	if acatBytes, ok := reqExtBidderParams["acat"]; ok {
   353  		var acat []string
   354  		err = json.Unmarshal(acatBytes, &acat)
   355  		if err != nil {
   356  			return pmReqExt, err
   357  		}
   358  		for i := 0; i < len(acat); i++ {
   359  			acat[i] = strings.TrimSpace(acat[i])
   360  		}
   361  		pmReqExt.Acat = acat
   362  	}
   363  
   364  	if allowedBidders := getAlternateBidderCodesFromRequestExt(reqExt); allowedBidders != nil {
   365  		pmReqExt.Marketplace = &marketplaceReqExt{AllowedBidders: allowedBidders}
   366  	}
   367  
   368  	return pmReqExt, nil
   369  }
   370  
   371  func getAlternateBidderCodesFromRequestExt(reqExt *openrtb_ext.ExtRequest) []string {
   372  	if reqExt == nil || reqExt.Prebid.AlternateBidderCodes == nil {
   373  		return nil
   374  	}
   375  
   376  	allowedBidders := []string{"pubmatic"}
   377  	if reqExt.Prebid.AlternateBidderCodes.Enabled {
   378  		if pmABC, ok := reqExt.Prebid.AlternateBidderCodes.Bidders["pubmatic"]; ok && pmABC.Enabled {
   379  			if pmABC.AllowedBidderCodes == nil || (len(pmABC.AllowedBidderCodes) == 1 && pmABC.AllowedBidderCodes[0] == "*") {
   380  				return []string{"all"}
   381  			}
   382  			return append(allowedBidders, pmABC.AllowedBidderCodes...)
   383  		}
   384  	}
   385  
   386  	return allowedBidders
   387  }
   388  
   389  func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) {
   390  	for _, keyVal := range keywords {
   391  		if len(keyVal.Values) == 0 {
   392  			continue
   393  		} else {
   394  			key := keyVal.Key
   395  			if keyVal.Key == pmZoneIDKeyNameOld {
   396  				key = pmZoneIDKeyName
   397  			}
   398  			extMap[key] = strings.Join(keyVal.Values[:], ",")
   399  		}
   400  	}
   401  }
   402  
   403  func (a *PubmaticAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   404  	if response.StatusCode == http.StatusNoContent {
   405  		return nil, nil
   406  	}
   407  
   408  	if response.StatusCode == http.StatusBadRequest {
   409  		return nil, []error{&errortypes.BadInput{
   410  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   411  		}}
   412  	}
   413  
   414  	if response.StatusCode != http.StatusOK {
   415  		return nil, []error{fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)}
   416  	}
   417  
   418  	var bidResp openrtb2.BidResponse
   419  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   420  		return nil, []error{err}
   421  	}
   422  
   423  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(5)
   424  
   425  	var errs []error
   426  	for _, sb := range bidResp.SeatBid {
   427  		for i := 0; i < len(sb.Bid); i++ {
   428  			bid := sb.Bid[i]
   429  			if len(bid.Cat) > 1 {
   430  				bid.Cat = bid.Cat[0:1]
   431  			}
   432  
   433  			typedBid := &adapters.TypedBid{
   434  				Bid:      &bid,
   435  				BidType:  openrtb_ext.BidTypeBanner,
   436  				BidVideo: &openrtb_ext.ExtBidPrebidVideo{},
   437  			}
   438  
   439  			var bidExt *pubmaticBidExt
   440  			err := json.Unmarshal(bid.Ext, &bidExt)
   441  			if err != nil {
   442  				errs = append(errs, err)
   443  			} else if bidExt != nil {
   444  				typedBid.Seat = openrtb_ext.BidderName(bidExt.Marketplace)
   445  				typedBid.BidType = getBidType(bidExt)
   446  				if bidExt.PrebidDealPriority > 0 {
   447  					typedBid.DealPriority = bidExt.PrebidDealPriority
   448  				}
   449  
   450  				if bidExt.VideoCreativeInfo != nil && bidExt.VideoCreativeInfo.Duration != nil {
   451  					typedBid.BidVideo.Duration = *bidExt.VideoCreativeInfo.Duration
   452  				}
   453  			}
   454  
   455  			if typedBid.BidType == openrtb_ext.BidTypeNative {
   456  				bid.AdM, err = getNativeAdm(bid.AdM)
   457  				if err != nil {
   458  					errs = append(errs, err)
   459  				}
   460  			}
   461  
   462  			bidResponse.Bids = append(bidResponse.Bids, typedBid)
   463  		}
   464  	}
   465  	if bidResp.Cur != "" {
   466  		bidResponse.Currency = bidResp.Cur
   467  	}
   468  	return bidResponse, errs
   469  }
   470  
   471  func getNativeAdm(adm string) (string, error) {
   472  	var err error
   473  	nativeAdm := make(map[string]interface{})
   474  	err = json.Unmarshal([]byte(adm), &nativeAdm)
   475  	if err != nil {
   476  		return adm, errors.New("unable to unmarshal native adm")
   477  	}
   478  
   479  	// move bid.adm.native to bid.adm
   480  	if _, ok := nativeAdm["native"]; ok {
   481  		//using jsonparser to avoid marshaling, encode escape, etc.
   482  		value, _, _, err := jsonparser.Get([]byte(adm), string(openrtb_ext.BidTypeNative))
   483  		if err != nil {
   484  			return adm, errors.New("unable to get native adm")
   485  		}
   486  		adm = string(value)
   487  	}
   488  
   489  	return adm, nil
   490  }
   491  
   492  // getMapFromJSON converts JSON to map
   493  func getMapFromJSON(source json.RawMessage) map[string]interface{} {
   494  	if source != nil {
   495  		dataMap := make(map[string]interface{})
   496  		err := json.Unmarshal(source, &dataMap)
   497  		if err == nil {
   498  			return dataMap
   499  		}
   500  	}
   501  	return nil
   502  }
   503  
   504  // populateFirstPartyDataImpAttributes will parse imp.ext.data and populate imp extMap
   505  func populateFirstPartyDataImpAttributes(data json.RawMessage, extMap map[string]interface{}) {
   506  
   507  	dataMap := getMapFromJSON(data)
   508  
   509  	if dataMap == nil {
   510  		return
   511  	}
   512  
   513  	populateAdUnitKey(data, dataMap, extMap)
   514  	populateDctrKey(dataMap, extMap)
   515  }
   516  
   517  // populateAdUnitKey parses data object to read and populate DFP adunit key
   518  func populateAdUnitKey(data json.RawMessage, dataMap, extMap map[string]interface{}) {
   519  
   520  	if name, err := jsonparser.GetString(data, "adserver", "name"); err == nil && name == AdServerGAM {
   521  		if adslot, err := jsonparser.GetString(data, "adserver", "adslot"); err == nil && adslot != "" {
   522  			extMap[ImpExtAdUnitKey] = adslot
   523  		}
   524  	}
   525  
   526  	//imp.ext.dfp_ad_unit_code is not set, then check pbadslot in imp.ext.data
   527  	if extMap[ImpExtAdUnitKey] == nil && dataMap[PBAdslotKey] != nil {
   528  		extMap[ImpExtAdUnitKey] = dataMap[PBAdslotKey].(string)
   529  	}
   530  }
   531  
   532  // populateDctrKey reads key-val pairs from imp.ext.data and add it in imp.ext.key_val
   533  func populateDctrKey(dataMap, extMap map[string]interface{}) {
   534  	var dctr strings.Builder
   535  
   536  	//append dctr key if already present in extMap
   537  	if extMap[dctrKeyName] != nil {
   538  		dctr.WriteString(extMap[dctrKeyName].(string))
   539  	}
   540  
   541  	for key, val := range dataMap {
   542  
   543  		//ignore 'pbaslot' and 'adserver' key as they are not targeting keys
   544  		if key == PBAdslotKey || key == AdServerKey {
   545  			continue
   546  		}
   547  
   548  		//separate key-val pairs in dctr string by pipe(|)
   549  		if dctr.Len() > 0 {
   550  			dctr.WriteString("|")
   551  		}
   552  
   553  		//trimming spaces from key
   554  		key = strings.TrimSpace(key)
   555  
   556  		switch typedValue := val.(type) {
   557  		case string:
   558  			if _, err := fmt.Fprintf(&dctr, "%s=%s", key, strings.TrimSpace(typedValue)); err != nil {
   559  				continue
   560  			}
   561  
   562  		case float64, bool:
   563  			if _, err := fmt.Fprintf(&dctr, "%s=%v", key, typedValue); err != nil {
   564  				continue
   565  			}
   566  
   567  		case []interface{}:
   568  			if valStrArr := getStringArray(typedValue); len(valStrArr) > 0 {
   569  				valStr := strings.Join(valStrArr[:], ",")
   570  				if _, err := fmt.Fprintf(&dctr, "%s=%s", key, valStr); err != nil {
   571  					continue
   572  				}
   573  			}
   574  		}
   575  	}
   576  
   577  	if dctrStr := dctr.String(); dctrStr != "" {
   578  		extMap[dctrKeyName] = strings.TrimSuffix(dctrStr, "|")
   579  	}
   580  }
   581  
   582  // getStringArray converts interface of type string array to string array
   583  func getStringArray(array []interface{}) []string {
   584  	aString := make([]string, len(array))
   585  	for i, v := range array {
   586  		if str, ok := v.(string); ok {
   587  			aString[i] = strings.TrimSpace(str)
   588  		} else {
   589  			return nil
   590  		}
   591  	}
   592  	return aString
   593  }
   594  
   595  // getBidType returns the bid type specified in the response bid.ext
   596  func getBidType(bidExt *pubmaticBidExt) openrtb_ext.BidType {
   597  	// setting "banner" as the default bid type
   598  	bidType := openrtb_ext.BidTypeBanner
   599  	if bidExt != nil && bidExt.BidType != nil {
   600  		switch *bidExt.BidType {
   601  		case 0:
   602  			bidType = openrtb_ext.BidTypeBanner
   603  		case 1:
   604  			bidType = openrtb_ext.BidTypeVideo
   605  		case 2:
   606  			bidType = openrtb_ext.BidTypeNative
   607  		default:
   608  			// default value is banner
   609  			bidType = openrtb_ext.BidTypeBanner
   610  		}
   611  	}
   612  	return bidType
   613  }
   614  
   615  // Builder builds a new instance of the Pubmatic adapter for the given bidder with the given config.
   616  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   617  	bidder := &PubmaticAdapter{
   618  		URI: config.Endpoint,
   619  	}
   620  	return bidder, nil
   621  }