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