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

     1  package alkimi
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/prebid/prebid-server/v2/errortypes"
    12  	"github.com/prebid/prebid-server/v2/floors"
    13  
    14  	"github.com/prebid/openrtb/v20/openrtb2"
    15  	"github.com/prebid/prebid-server/v2/adapters"
    16  	"github.com/prebid/prebid-server/v2/config"
    17  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    18  )
    19  
    20  const price_macro = "${AUCTION_PRICE}"
    21  
    22  type adapter struct {
    23  	endpoint string
    24  }
    25  
    26  type extObj struct {
    27  	AlkimiBidderExt openrtb_ext.ExtImpAlkimi `json:"bidder"`
    28  }
    29  
    30  // Builder builds a new instance of the Alkimi adapter for the given bidder with the given config.
    31  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    32  	endpointURL, err := url.Parse(config.Endpoint)
    33  	if err != nil || len(endpointURL.String()) == 0 {
    34  		return nil, fmt.Errorf("invalid endpoint: %v", err)
    35  	}
    36  
    37  	bidder := &adapter{
    38  		endpoint: endpointURL.String(),
    39  	}
    40  	return bidder, nil
    41  }
    42  
    43  // MakeRequests creates Alkimi adapter requests
    44  func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, req *adapters.ExtraRequestInfo) (reqsBidder []*adapters.RequestData, errs []error) {
    45  	reqCopy := *request
    46  
    47  	updated, errs := updateImps(reqCopy)
    48  	if len(errs) > 0 || len(reqCopy.Imp) != len(updated) {
    49  		return nil, errs
    50  	}
    51  
    52  	reqCopy.Imp = updated
    53  	encoded, err := json.Marshal(reqCopy)
    54  	if err != nil {
    55  		errs = append(errs, err)
    56  	} else {
    57  		reqBidder := buildBidderRequest(adapter, encoded, openrtb_ext.GetImpIDs(reqCopy.Imp))
    58  		reqsBidder = append(reqsBidder, reqBidder)
    59  	}
    60  	return
    61  }
    62  
    63  func updateImps(bidRequest openrtb2.BidRequest) ([]openrtb2.Imp, []error) {
    64  	var errs []error
    65  
    66  	updatedImps := make([]openrtb2.Imp, 0, len(bidRequest.Imp))
    67  	for _, imp := range bidRequest.Imp {
    68  
    69  		var bidderExt adapters.ExtImpBidder
    70  		var extImpAlkimi openrtb_ext.ExtImpAlkimi
    71  
    72  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    73  			errs = append(errs, err)
    74  			continue
    75  		}
    76  
    77  		if err := json.Unmarshal(bidderExt.Bidder, &extImpAlkimi); err != nil {
    78  			errs = append(errs, err)
    79  			continue
    80  		}
    81  
    82  		var bidFloorPrice floors.Price
    83  		bidFloorPrice.FloorMinCur = imp.BidFloorCur
    84  		bidFloorPrice.FloorMin = imp.BidFloor
    85  
    86  		if len(bidFloorPrice.FloorMinCur) > 0 && bidFloorPrice.FloorMin > 0 {
    87  			imp.BidFloor = bidFloorPrice.FloorMin
    88  		} else {
    89  			imp.BidFloor = extImpAlkimi.BidFloor
    90  		}
    91  		imp.Instl = extImpAlkimi.Instl
    92  		imp.Exp = extImpAlkimi.Exp
    93  
    94  		temp := extObj{AlkimiBidderExt: extImpAlkimi}
    95  		temp.AlkimiBidderExt.AdUnitCode = imp.ID
    96  
    97  		extJson, err := json.Marshal(temp)
    98  		if err != nil {
    99  			errs = append(errs, err)
   100  			continue
   101  		}
   102  		imp.Ext = extJson
   103  		updatedImps = append(updatedImps, imp)
   104  	}
   105  
   106  	return updatedImps, errs
   107  }
   108  
   109  func buildBidderRequest(adapter *adapter, encoded []byte, impIDs []string) *adapters.RequestData {
   110  	headers := http.Header{}
   111  	headers.Add("Content-Type", "application/json;charset=utf-8")
   112  	headers.Add("Accept", "application/json")
   113  
   114  	reqBidder := &adapters.RequestData{
   115  		Method:  "POST",
   116  		Uri:     adapter.endpoint,
   117  		Body:    encoded,
   118  		Headers: headers,
   119  		ImpIDs:  impIDs,
   120  	}
   121  	return reqBidder
   122  }
   123  
   124  // MakeBids will parse the bids from the Alkimi server
   125  func (adapter *adapter) MakeBids(request *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   126  	var errs []error
   127  
   128  	if adapters.IsResponseStatusCodeNoContent(response) {
   129  		return nil, nil
   130  	}
   131  
   132  	if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
   133  		return nil, []error{err}
   134  	}
   135  
   136  	var bidResp openrtb2.BidResponse
   137  	err := json.Unmarshal(response.Body, &bidResp)
   138  	if err != nil {
   139  		return nil, []error{err}
   140  	}
   141  
   142  	seatBidCount := len(bidResp.SeatBid)
   143  	if seatBidCount == 0 {
   144  		return nil, []error{&errortypes.BadServerResponse{
   145  			Message: "Empty SeatBid array",
   146  		}}
   147  	}
   148  
   149  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
   150  	for _, seatBid := range bidResp.SeatBid {
   151  		for _, bid := range seatBid.Bid {
   152  			copyBid := bid
   153  			resolveMacros(&copyBid)
   154  			impId := copyBid.ImpID
   155  			imp := request.Imp
   156  			bidType, err := getMediaTypeForImp(impId, imp)
   157  			if err != nil {
   158  				errs = append(errs, err)
   159  				continue
   160  			}
   161  			bidderBid := &adapters.TypedBid{
   162  				Bid:     &copyBid,
   163  				BidType: bidType,
   164  			}
   165  			bidResponse.Bids = append(bidResponse.Bids, bidderBid)
   166  		}
   167  	}
   168  	return bidResponse, errs
   169  }
   170  
   171  func resolveMacros(bid *openrtb2.Bid) {
   172  	strPrice := strconv.FormatFloat(bid.Price, 'f', -1, 64)
   173  	bid.NURL = strings.Replace(bid.NURL, price_macro, strPrice, -1)
   174  	bid.AdM = strings.Replace(bid.AdM, price_macro, strPrice, -1)
   175  }
   176  
   177  func getMediaTypeForImp(impId string, imps []openrtb2.Imp) (openrtb_ext.BidType, error) {
   178  	for _, imp := range imps {
   179  		if imp.ID == impId {
   180  			if imp.Banner != nil {
   181  				return openrtb_ext.BidTypeBanner, nil
   182  			}
   183  			if imp.Video != nil {
   184  				return openrtb_ext.BidTypeVideo, nil
   185  			}
   186  			if imp.Audio != nil {
   187  				return openrtb_ext.BidTypeAudio, nil
   188  			}
   189  		}
   190  	}
   191  	return "", &errortypes.BadInput{
   192  		Message: fmt.Sprintf("Failed to find imp \"%s\"", impId),
   193  	}
   194  }