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

     1  package improvedigital
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/prebid/openrtb/v19/openrtb2"
    11  	"github.com/prebid/prebid-server/adapters"
    12  	"github.com/prebid/prebid-server/config"
    13  	"github.com/prebid/prebid-server/errortypes"
    14  	"github.com/prebid/prebid-server/openrtb_ext"
    15  )
    16  
    17  const (
    18  	buyingTypeRTB                    = "rtb"
    19  	isRewardedInventory              = "is_rewarded_inventory"
    20  	stateRewardedInventoryEnable     = "1"
    21  	consentProvidersSettingsInputKey = "ConsentedProvidersSettings"
    22  	consentProvidersSettingsOutKey   = "consented_providers_settings"
    23  	consentedProvidersKey            = "consented_providers"
    24  	publisherEndpointParam           = "{PublisherId}"
    25  )
    26  
    27  type ImprovedigitalAdapter struct {
    28  	endpoint string
    29  }
    30  
    31  // BidExt represents Improved Digital bid extension with line item ID and buying type values
    32  type BidExt struct {
    33  	Improvedigital struct {
    34  		LineItemID int    `json:"line_item_id"`
    35  		BuyingType string `json:"buying_type"`
    36  	}
    37  }
    38  
    39  // ImpExtBidder represents Improved Digital bid extension with Publisher ID
    40  type ImpExtBidder struct {
    41  	Bidder struct {
    42  		PublisherID int `json:"publisherId"`
    43  	}
    44  }
    45  
    46  // MakeRequests makes the HTTP requests which should be made to fetch bids.
    47  func (a *ImprovedigitalAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    48  	numRequests := len(request.Imp)
    49  	errors := make([]error, 0)
    50  	adapterRequests := make([]*adapters.RequestData, 0, numRequests)
    51  
    52  	// Split multi-imp request into multiple ad server requests. SRA is currently not recommended.
    53  	for i := 0; i < numRequests; i++ {
    54  		if adapterReq, err := a.makeRequest(*request, request.Imp[i]); err == nil {
    55  			adapterRequests = append(adapterRequests, adapterReq)
    56  		} else {
    57  			errors = append(errors, err)
    58  		}
    59  	}
    60  
    61  	return adapterRequests, errors
    62  }
    63  
    64  func (a *ImprovedigitalAdapter) makeRequest(request openrtb2.BidRequest, imp openrtb2.Imp) (*adapters.RequestData, error) {
    65  	// Handle Rewarded Inventory
    66  	impExt, err := getImpExtWithRewardedInventory(imp)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	if impExt != nil {
    71  		imp.Ext = impExt
    72  	}
    73  
    74  	request.Imp = []openrtb2.Imp{imp}
    75  
    76  	userExtAddtlConsent, err := a.getAdditionalConsentProvidersUserExt(request)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  
    81  	if len(userExtAddtlConsent) > 0 {
    82  		userCopy := *request.User
    83  		userCopy.Ext = userExtAddtlConsent
    84  		request.User = &userCopy
    85  	}
    86  
    87  	reqJSON, err := json.Marshal(request)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	headers := http.Header{}
    93  	headers.Add("Content-Type", "application/json;charset=utf-8")
    94  
    95  	return &adapters.RequestData{
    96  		Method:  "POST",
    97  		Uri:     a.buildEndpointURL(imp),
    98  		Body:    reqJSON,
    99  		Headers: headers,
   100  	}, nil
   101  }
   102  
   103  // MakeBids unpacks the server's response into Bids.
   104  func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   105  	if response.StatusCode == http.StatusNoContent {
   106  		return nil, nil
   107  	}
   108  
   109  	if response.StatusCode == http.StatusBadRequest {
   110  		return nil, []error{&errortypes.BadInput{
   111  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   112  		}}
   113  	}
   114  
   115  	if response.StatusCode != http.StatusOK {
   116  		return nil, []error{&errortypes.BadServerResponse{
   117  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   118  		}}
   119  	}
   120  
   121  	var bidResp openrtb2.BidResponse
   122  	if err := json.Unmarshal(response.Body, &bidResp); err != nil {
   123  		return nil, []error{err}
   124  	}
   125  
   126  	if len(bidResp.SeatBid) == 0 {
   127  		return nil, nil
   128  	}
   129  
   130  	if len(bidResp.SeatBid) > 1 {
   131  		return nil, []error{&errortypes.BadServerResponse{
   132  			Message: fmt.Sprintf("Unexpected SeatBid! Must be only one but have: %d", len(bidResp.SeatBid)),
   133  		}}
   134  	}
   135  
   136  	seatBid := bidResp.SeatBid[0]
   137  
   138  	if len(seatBid.Bid) == 0 {
   139  		return nil, nil
   140  	}
   141  
   142  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid))
   143  	bidResponse.Currency = bidResp.Cur
   144  
   145  	for i := range seatBid.Bid {
   146  		bid := seatBid.Bid[i]
   147  
   148  		bidType, err := getMediaTypeForImp(bid.ImpID, internalRequest.Imp)
   149  		if err != nil {
   150  			return nil, []error{err}
   151  		}
   152  
   153  		if bid.Ext != nil {
   154  			var bidExt BidExt
   155  			err = json.Unmarshal(bid.Ext, &bidExt)
   156  			if err != nil {
   157  				return nil, []error{err}
   158  			}
   159  
   160  			bidExtImprovedigital := bidExt.Improvedigital
   161  			if bidExtImprovedigital.LineItemID != 0 && bidExtImprovedigital.BuyingType != "" && bidExtImprovedigital.BuyingType != buyingTypeRTB {
   162  				bid.DealID = strconv.Itoa(bidExtImprovedigital.LineItemID)
   163  			}
   164  		}
   165  
   166  		bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   167  			Bid:     &bid,
   168  			BidType: bidType,
   169  		})
   170  	}
   171  	return bidResponse, nil
   172  
   173  }
   174  
   175  // Builder builds a new instance of the Improvedigital adapter for the given bidder with the given config.
   176  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
   177  	bidder := &ImprovedigitalAdapter{
   178  		endpoint: config.Endpoint,
   179  	}
   180  	return bidder, nil
   181  }
   182  
   183  func getMediaTypeForImp(impID string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
   184  	for _, imp := range imps {
   185  		if imp.ID == impID {
   186  			if imp.Banner != nil {
   187  				return openrtb_ext.BidTypeBanner, nil
   188  			}
   189  
   190  			if imp.Video != nil {
   191  				return openrtb_ext.BidTypeVideo, nil
   192  			}
   193  
   194  			if imp.Native != nil {
   195  				return openrtb_ext.BidTypeNative, nil
   196  			}
   197  
   198  			return "", &errortypes.BadServerResponse{
   199  				Message: fmt.Sprintf("Unknown impression type for ID: \"%s\"", impID),
   200  			}
   201  		}
   202  	}
   203  
   204  	// This shouldnt happen. Lets handle it just incase by returning an error.
   205  	return "", &errortypes.BadServerResponse{
   206  		Message: fmt.Sprintf("Failed to find impression for ID: \"%s\"", impID),
   207  	}
   208  }
   209  
   210  // This method responsible to clone request and convert additional consent providers string to array when additional consent provider found
   211  func (a *ImprovedigitalAdapter) getAdditionalConsentProvidersUserExt(request openrtb2.BidRequest) ([]byte, error) {
   212  	var cpStr string
   213  
   214  	// If user/user.ext not defined, no need to parse additional consent
   215  	if request.User == nil || request.User.Ext == nil {
   216  		return nil, nil
   217  	}
   218  
   219  	// Start validating additional consent
   220  	// Check key exist user.ext.ConsentedProvidersSettings
   221  	var userExtMap = make(map[string]json.RawMessage)
   222  	if err := json.Unmarshal(request.User.Ext, &userExtMap); err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	cpsMapValue, cpsJSONFound := userExtMap[consentProvidersSettingsInputKey]
   227  	if !cpsJSONFound {
   228  		return nil, nil
   229  	}
   230  
   231  	// Check key exist user.ext.ConsentedProvidersSettings.consented_providers
   232  	var cpMap = make(map[string]json.RawMessage)
   233  	if err := json.Unmarshal(cpsMapValue, &cpMap); err != nil {
   234  		return nil, err
   235  	}
   236  
   237  	cpMapValue, cpJSONFound := cpMap[consentedProvidersKey]
   238  	if !cpJSONFound {
   239  		return nil, nil
   240  	}
   241  	// End validating additional consent
   242  
   243  	// Check if string contain ~, then substring after ~ to end of string
   244  	consentStr := string(cpMapValue)
   245  	var tildaPosition int
   246  	if tildaPosition = strings.Index(consentStr, "~"); tildaPosition == -1 {
   247  		return nil, nil
   248  	}
   249  	cpStr = consentStr[tildaPosition+1 : len(consentStr)-1]
   250  
   251  	// Prepare consent providers string
   252  	cpStr = fmt.Sprintf("[%s]", strings.Replace(cpStr, ".", ",", -1))
   253  	cpMap[consentedProvidersKey] = json.RawMessage(cpStr)
   254  
   255  	cpJSON, err := json.Marshal(cpMap)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	userExtMap[consentProvidersSettingsOutKey] = cpJSON
   260  
   261  	extJson, err := json.Marshal(userExtMap)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  
   266  	return extJson, nil
   267  }
   268  
   269  func getImpExtWithRewardedInventory(imp openrtb2.Imp) ([]byte, error) {
   270  	var ext = make(map[string]json.RawMessage)
   271  	if err := json.Unmarshal(imp.Ext, &ext); err != nil {
   272  		return nil, err
   273  	}
   274  
   275  	prebidJSONValue, prebidJSONFound := ext["prebid"]
   276  	if !prebidJSONFound {
   277  		return nil, nil
   278  	}
   279  
   280  	var prebidMap = make(map[string]json.RawMessage)
   281  	if err := json.Unmarshal(prebidJSONValue, &prebidMap); err != nil {
   282  		return nil, err
   283  	}
   284  
   285  	if rewardedInventory, foundRewardedInventory := prebidMap[isRewardedInventory]; foundRewardedInventory && string(rewardedInventory) == stateRewardedInventoryEnable {
   286  		ext[isRewardedInventory] = json.RawMessage(`true`)
   287  		impExt, err := json.Marshal(ext)
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  
   292  		return impExt, nil
   293  	}
   294  
   295  	return nil, nil
   296  }
   297  
   298  func (a *ImprovedigitalAdapter) buildEndpointURL(imp openrtb2.Imp) string {
   299  	publisherEndpoint := ""
   300  	var impBidder ImpExtBidder
   301  
   302  	err := json.Unmarshal(imp.Ext, &impBidder)
   303  	if err == nil && impBidder.Bidder.PublisherID != 0 {
   304  		publisherEndpoint = strconv.Itoa(impBidder.Bidder.PublisherID) + "/"
   305  	}
   306  
   307  	return strings.Replace(a.endpoint, publisherEndpointParam, publisherEndpoint, -1)
   308  }