github.com/prebid/prebid-server/v2@v2.18.0/adapters/relevantdigital/relevantdigital.go (about)

     1  package relevantdigital
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"net/http"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/buger/jsonparser"
    12  	"github.com/prebid/openrtb/v20/openrtb2"
    13  	"github.com/prebid/prebid-server/v2/adapters"
    14  	"github.com/prebid/prebid-server/v2/config"
    15  	"github.com/prebid/prebid-server/v2/errortypes"
    16  	"github.com/prebid/prebid-server/v2/macros"
    17  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    18  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    19  )
    20  
    21  type adapter struct {
    22  	endpoint *template.Template
    23  	name     string
    24  }
    25  
    26  const relevant_domain = ".relevant-digital.com"
    27  const default_timeout = 1000
    28  const default_bufffer_ms = 250
    29  
    30  type prebidExt struct {
    31  	StoredRequest struct {
    32  		Id string `json:"id"`
    33  	} `json:"storedrequest"`
    34  	Debug bool `json:"debug"`
    35  }
    36  
    37  type relevantExt struct {
    38  	Relevant struct {
    39  		Count       int    `json:"count"`
    40  		AdapterType string `json:"adapterType"`
    41  	} `json:"relevant"`
    42  	Prebid prebidExt `json:"prebid"`
    43  }
    44  
    45  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    46  	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
    47  	if err != nil {
    48  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    49  	}
    50  	return &adapter{
    51  		endpoint: template,
    52  		name:     bidderName.String(),
    53  	}, nil
    54  }
    55  
    56  func patchBidRequestExt(prebidBidRequest *openrtb2.BidRequest, id string) error {
    57  	var bidRequestExt relevantExt
    58  	if len(prebidBidRequest.Ext) != 0 {
    59  		if err := json.Unmarshal(prebidBidRequest.Ext, &bidRequestExt); err != nil {
    60  			return &errortypes.FailedToRequestBids{
    61  				Message: fmt.Sprintf("failed to unmarshal ext, %s", prebidBidRequest.Ext),
    62  			}
    63  		}
    64  	}
    65  
    66  	count := bidRequestExt.Relevant.Count
    67  	if bidRequestExt.Relevant.Count >= 5 {
    68  		return &errortypes.FailedToRequestBids{
    69  			Message: "too many requests",
    70  		}
    71  	} else {
    72  		count = count + 1
    73  	}
    74  
    75  	bidRequestExt.Relevant.Count = count
    76  	bidRequestExt.Relevant.AdapterType = "server"
    77  	bidRequestExt.Prebid.StoredRequest.Id = id
    78  
    79  	ext, err := json.Marshal(bidRequestExt)
    80  	if err != nil {
    81  		return &errortypes.FailedToRequestBids{
    82  			Message: "failed to marshal",
    83  		}
    84  	}
    85  
    86  	if len(prebidBidRequest.Ext) == 0 {
    87  		prebidBidRequest.Ext = ext
    88  		return nil
    89  	}
    90  
    91  	patchedExt, err := jsonpatch.MergePatch(prebidBidRequest.Ext, ext)
    92  	if err != nil {
    93  		return &errortypes.FailedToRequestBids{
    94  			Message: fmt.Sprintf("failed patch ext, %s", err),
    95  		}
    96  	}
    97  	prebidBidRequest.Ext = patchedExt
    98  	return nil
    99  }
   100  
   101  func patchBidImpExt(imp *openrtb2.Imp, id string) {
   102  	imp.Ext = []byte(fmt.Sprintf("{\"prebid\":{\"storedrequest\":{\"id\":\"%s\"}}}", id))
   103  }
   104  
   105  func setTMax(prebidBidRequest *openrtb2.BidRequest, pbsBufferMs int) {
   106  	timeout := float64(prebidBidRequest.TMax)
   107  	if timeout <= 0 {
   108  		timeout = default_timeout
   109  	}
   110  	buffer := float64(pbsBufferMs)
   111  	prebidBidRequest.TMax = int64(math.Min(math.Max(timeout-buffer, buffer), timeout))
   112  }
   113  
   114  func createBidRequest(prebidBidRequest *openrtb2.BidRequest, params []*openrtb_ext.ExtRelevantDigital) ([]byte, error) {
   115  	bidRequestCopy := *prebidBidRequest
   116  
   117  	err := patchBidRequestExt(&bidRequestCopy, params[0].AccountId)
   118  	if err != nil {
   119  		return nil, &errortypes.BadInput{
   120  			Message: fmt.Sprintf("failed to create bidRequest, error: %s", err),
   121  		}
   122  	}
   123  
   124  	setTMax(&bidRequestCopy, params[0].PbsBufferMs)
   125  
   126  	for idx := range bidRequestCopy.Imp {
   127  		patchBidImpExt(&bidRequestCopy.Imp[idx], params[idx].PlacementId)
   128  	}
   129  
   130  	return createJSONRequest(&bidRequestCopy)
   131  }
   132  
   133  func createJSONRequest(bidRequest *openrtb2.BidRequest) ([]byte, error) {
   134  	reqJSON, err := json.Marshal(bidRequest)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	// Scrub previous ext data from relevant, if any
   140  	// imp[].ext.context.relevant
   141  	// imp[].[banner/native/video/audio].ext.relevant
   142  	impKeyTypes := []string{"banner", "video", "native", "audio"}
   143  	for idx := range bidRequest.Imp {
   144  		for _, key := range impKeyTypes {
   145  			reqJSON = jsonparser.Delete(reqJSON, "imp", fmt.Sprintf("[%d]", idx), key, "ext", "relevant")
   146  		}
   147  		reqJSON = jsonparser.Delete(reqJSON, "imp", fmt.Sprintf("[%d]", idx), "ext", "context", "relevant")
   148  	}
   149  
   150  	// Scrub previous prebid data (to not set cache on wrong servers)
   151  	// ext.prebid.[cache/targeting/aliases]
   152  	prebidKeyTypes := []string{"cache", "targeting", "aliases"}
   153  	for _, key := range prebidKeyTypes {
   154  		reqJSON = jsonparser.Delete(reqJSON, "ext", "prebid", key)
   155  	}
   156  	return reqJSON, nil
   157  }
   158  
   159  func getImpressionExt(imp *openrtb2.Imp) (*openrtb_ext.ExtRelevantDigital, error) {
   160  	var bidderExt adapters.ExtImpBidder
   161  	if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
   162  		return nil, &errortypes.BadInput{
   163  			Message: "imp.ext not provided",
   164  		}
   165  	}
   166  	relevantExt := openrtb_ext.ExtRelevantDigital{PbsBufferMs: default_bufffer_ms}
   167  	if err := json.Unmarshal(bidderExt.Bidder, &relevantExt); err != nil {
   168  		return nil, &errortypes.BadInput{
   169  			Message: "ext.bidder not provided",
   170  		}
   171  	}
   172  	return &relevantExt, nil
   173  }
   174  
   175  func (a *adapter) buildEndpointURL(params *openrtb_ext.ExtRelevantDigital) (string, error) {
   176  	params.Host = strings.ReplaceAll(params.Host, "http://", "")
   177  	params.Host = strings.ReplaceAll(params.Host, "https://", "")
   178  	params.Host = strings.ReplaceAll(params.Host, relevant_domain, "")
   179  
   180  	endpointParams := macros.EndpointTemplateParams{Host: params.Host}
   181  	return macros.ResolveMacros(a.endpoint, endpointParams)
   182  }
   183  
   184  func (a *adapter) buildAdapterRequest(prebidBidRequest *openrtb2.BidRequest, params []*openrtb_ext.ExtRelevantDigital) (*adapters.RequestData, error) {
   185  	reqJSON, err := createBidRequest(prebidBidRequest, params)
   186  
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	url, err := a.buildEndpointURL(params[0])
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  	return &adapters.RequestData{
   197  		Method:  "POST",
   198  		Uri:     url,
   199  		Body:    reqJSON,
   200  		Headers: getHeaders(prebidBidRequest),
   201  		ImpIDs:  openrtb_ext.GetImpIDs(prebidBidRequest.Imp),
   202  	}, nil
   203  }
   204  
   205  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
   206  	impParams, errs := getImpressionsInfo(request.Imp)
   207  	if len(errs) > 0 {
   208  		return nil, errs
   209  	}
   210  
   211  	bidRequest, err := a.buildAdapterRequest(request, impParams)
   212  	if err != nil {
   213  		errs = []error{err}
   214  	}
   215  
   216  	if bidRequest != nil {
   217  		return []*adapters.RequestData{bidRequest}, errs
   218  	}
   219  	return nil, errs
   220  }
   221  
   222  func getImpressionsInfo(imps []openrtb2.Imp) (resImps []*openrtb_ext.ExtRelevantDigital, errors []error) {
   223  	for _, imp := range imps {
   224  		impExt, err := getImpressionExt(&imp)
   225  		if err != nil {
   226  			errors = append(errors, err)
   227  			continue
   228  		}
   229  		resImps = append(resImps, impExt)
   230  	}
   231  	return
   232  }
   233  
   234  func getHeaders(request *openrtb2.BidRequest) http.Header {
   235  	headers := http.Header{}
   236  	headers.Add("Content-Type", "application/json;charset=utf-8")
   237  	headers.Add("Accept", "application/json")
   238  	headers.Add("X-Openrtb-Version", "2.5")
   239  
   240  	if request.Device != nil {
   241  		if len(request.Device.UA) > 0 {
   242  			headers.Add("User-Agent", request.Device.UA)
   243  		}
   244  		if len(request.Device.IPv6) > 0 {
   245  			headers.Add("X-Forwarded-For", request.Device.IPv6)
   246  		}
   247  		if len(request.Device.IP) > 0 {
   248  			headers.Add("X-Forwarded-For", request.Device.IP)
   249  		}
   250  	}
   251  	return headers
   252  }
   253  
   254  func getMediaTypeForBidFromExt(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
   255  	if bid.Ext != nil {
   256  		var bidExt openrtb_ext.ExtBid
   257  		err := json.Unmarshal(bid.Ext, &bidExt)
   258  		if err == nil && bidExt.Prebid != nil {
   259  			return openrtb_ext.ParseBidType(string(bidExt.Prebid.Type))
   260  		}
   261  	}
   262  	return "", fmt.Errorf("failed to parse bid type, missing ext: %s", bid.ImpID)
   263  }
   264  
   265  func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
   266  	switch bid.MType {
   267  	case openrtb2.MarkupBanner:
   268  		return openrtb_ext.BidTypeBanner, nil
   269  	case openrtb2.MarkupVideo:
   270  		return openrtb_ext.BidTypeVideo, nil
   271  	case openrtb2.MarkupAudio:
   272  		return openrtb_ext.BidTypeAudio, nil
   273  	case openrtb2.MarkupNative:
   274  		return openrtb_ext.BidTypeNative, nil
   275  	default:
   276  		return getMediaTypeForBidFromExt(bid)
   277  	}
   278  }
   279  
   280  func isSupportedMediaType(bidType openrtb_ext.BidType) error {
   281  	switch bidType {
   282  	case openrtb_ext.BidTypeBanner:
   283  		fallthrough
   284  	case openrtb_ext.BidTypeVideo:
   285  		fallthrough
   286  	case openrtb_ext.BidTypeAudio:
   287  		fallthrough
   288  	case openrtb_ext.BidTypeNative:
   289  		return nil
   290  	}
   291  	return fmt.Errorf("bid type not supported %s", bidType)
   292  }
   293  
   294  func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   295  	if adapters.IsResponseStatusCodeNoContent(responseData) {
   296  		return nil, nil
   297  	}
   298  
   299  	if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
   300  		return nil, []error{err}
   301  	}
   302  
   303  	var response openrtb2.BidResponse
   304  	if err := json.Unmarshal(responseData.Body, &response); err != nil {
   305  		return nil, []error{err}
   306  	}
   307  
   308  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(response.SeatBid))
   309  	bidResponse.Currency = response.Cur
   310  	var errs []error
   311  	for _, seatBid := range response.SeatBid {
   312  		for i, bid := range seatBid.Bid {
   313  			bidType, err := getMediaTypeForBid(bid)
   314  
   315  			if err != nil {
   316  				errs = append(errs, err)
   317  				continue
   318  			}
   319  			if err := isSupportedMediaType(bidType); err != nil {
   320  				errs = append(errs, err)
   321  			} else {
   322  				b := &adapters.TypedBid{
   323  					Bid:     &seatBid.Bid[i],
   324  					BidType: bidType,
   325  				}
   326  				bidResponse.Bids = append(bidResponse.Bids, b)
   327  			}
   328  		}
   329  	}
   330  	return bidResponse, errs
   331  }