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

     1  package adquery
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/prebid/openrtb/v20/openrtb2"
    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  )
    17  
    18  const (
    19  	defaultCurrency string = "PLN"
    20  	bidderName      string = "adquery"
    21  	prebidVersion   string = "server"
    22  )
    23  
    24  type adapter struct {
    25  	endpoint string
    26  }
    27  
    28  // Builder builds a new instance of the Adquery adapter for the given bidder with the given config.
    29  func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
    30  	bidder := &adapter{
    31  		endpoint: config.Endpoint,
    32  	}
    33  	return bidder, nil
    34  }
    35  
    36  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    37  	headers := buildHeaders(request)
    38  
    39  	var result []*adapters.RequestData
    40  	var errs []error
    41  	for _, imp := range request.Imp {
    42  		ext, err := parseExt(imp.Ext)
    43  		if err != nil {
    44  			errs = append(errs, &errortypes.BadInput{Message: err.Error()})
    45  			continue
    46  		}
    47  
    48  		requestJSON, err := json.Marshal(buildRequest(request, &imp, ext))
    49  		if err != nil {
    50  			return nil, append(errs, err)
    51  		}
    52  
    53  		data := &adapters.RequestData{
    54  			Method:  "POST",
    55  			Uri:     a.endpoint,
    56  			Body:    requestJSON,
    57  			Headers: headers,
    58  			ImpIDs:  []string{imp.ID},
    59  		}
    60  		result = append(result, data)
    61  	}
    62  
    63  	return result, errs
    64  }
    65  
    66  func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
    67  	if adapters.IsResponseStatusCodeNoContent(responseData) {
    68  		return nil, nil
    69  	}
    70  
    71  	err := adapters.CheckResponseStatusCodeForErrors(responseData)
    72  	if err != nil {
    73  		return nil, []error{err}
    74  	}
    75  
    76  	respData, price, width, height, errs := parseResponseJson(responseData.Body)
    77  	if len(errs) > 0 {
    78  		return nil, errs
    79  	}
    80  
    81  	if respData == nil {
    82  		return adapters.NewBidderResponse(), nil
    83  	}
    84  
    85  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
    86  	if respData.Currency != "" {
    87  		bidResponse.Currency = respData.Currency
    88  	} else {
    89  		bidResponse.Currency = defaultCurrency
    90  	}
    91  
    92  	var bidReqIdRegex = regexp.MustCompile(`^` + request.ID)
    93  	impId := bidReqIdRegex.ReplaceAllLiteralString(respData.ReqID, "")
    94  
    95  	bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
    96  		Bid: &openrtb2.Bid{
    97  			// There's much more possible fields to be added here, see OpenRTB docs for reference (type: Bid)
    98  			ID:      respData.ReqID,
    99  			ImpID:   impId,
   100  			Price:   price,
   101  			AdM:     fmt.Sprintf("<script src=\"%s\"></script>%s", respData.AdQLib, respData.Tag),
   102  			ADomain: respData.ADomains,
   103  			CrID:    fmt.Sprintf("%d", respData.CrID),
   104  			W:       width,
   105  			H:       height,
   106  		},
   107  		BidType: respData.MediaType.Name,
   108  	})
   109  
   110  	return bidResponse, nil
   111  }
   112  
   113  func buildHeaders(bidReq *openrtb2.BidRequest) http.Header {
   114  	headers := http.Header{}
   115  
   116  	headers.Add("Content-Type", "application/json;charset=utf-8")
   117  	headers.Add("Accept", "application/json")
   118  	headers.Add("X-Openrtb-Version", "2.5")
   119  
   120  	if bidReq.Device != nil && len(bidReq.Device.IP) > 0 {
   121  		headers.Add("X-Forwarded-For", bidReq.Device.IP)
   122  	}
   123  
   124  	return headers
   125  }
   126  
   127  func buildRequest(bidReq *openrtb2.BidRequest, imp *openrtb2.Imp, ext *openrtb_ext.ImpExtAdQuery) *BidderRequest {
   128  	userId := ""
   129  	if bidReq.User != nil {
   130  		userId = bidReq.User.ID
   131  	}
   132  
   133  	bidderRequest := &BidderRequest{
   134  		V:                   prebidVersion,
   135  		PlacementCode:       ext.PlacementID,
   136  		AuctionId:           "",
   137  		BidType:             ext.Type,
   138  		AdUnitCode:          imp.TagID,
   139  		BidQid:              userId,
   140  		BidId:               fmt.Sprintf("%s%s", bidReq.ID, imp.ID),
   141  		Bidder:              bidderName,
   142  		BidderRequestId:     bidReq.ID,
   143  		BidRequestsCount:    1,
   144  		BidderRequestsCount: 1,
   145  		Sizes:               getImpSizes(imp),
   146  	}
   147  
   148  	if bidReq.Device != nil {
   149  		bidderRequest.BidIp = bidReq.Device.IP
   150  		bidderRequest.BidIpv6 = bidReq.Device.IPv6
   151  		bidderRequest.BidUa = bidReq.Device.UA
   152  	}
   153  
   154  	if bidReq.Site != nil {
   155  		bidderRequest.BidPageUrl = bidReq.Site.Page
   156  	}
   157  
   158  	return bidderRequest
   159  }
   160  
   161  func parseExt(ext json.RawMessage) (*openrtb_ext.ImpExtAdQuery, error) {
   162  	var bext adapters.ExtImpBidder
   163  	err := json.Unmarshal(ext, &bext)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	var adsExt openrtb_ext.ImpExtAdQuery
   169  	err = json.Unmarshal(bext.Bidder, &adsExt)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  
   174  	// not validating, because it should have been done earlier by the server
   175  	return &adsExt, nil
   176  }
   177  
   178  func parseResponseJson(respBody []byte) (*ResponseData, float64, int64, int64, []error) {
   179  	var response ResponseAdQuery
   180  	if err := json.Unmarshal(respBody, &response); err != nil {
   181  		return nil, 0, 0, 0, []error{err}
   182  	}
   183  
   184  	if response.Data == nil {
   185  		return nil, 0, 0, 0, nil
   186  	}
   187  
   188  	var errs []error
   189  	price, err := strconv.ParseFloat(response.Data.CPM, 64)
   190  	if err != nil {
   191  		errs = append(errs, err)
   192  	}
   193  	width, err := strconv.ParseInt(response.Data.MediaType.Width, 10, 64)
   194  	if err != nil {
   195  		errs = append(errs, err)
   196  	}
   197  	height, err := strconv.ParseInt(response.Data.MediaType.Height, 10, 64)
   198  	if err != nil {
   199  		errs = append(errs, err)
   200  	}
   201  
   202  	if response.Data.MediaType.Name != openrtb_ext.BidTypeBanner {
   203  		return nil, 0, 0, 0, []error{fmt.Errorf("unsupported MediaType: %s", response.Data.MediaType.Name)}
   204  	}
   205  
   206  	if len(errs) > 0 {
   207  		return nil, 0, 0, 0, errs
   208  	}
   209  	return response.Data, price, width, height, nil
   210  }
   211  
   212  func getImpSizes(imp *openrtb2.Imp) string {
   213  	if imp.Banner == nil {
   214  		return ""
   215  	}
   216  
   217  	if len(imp.Banner.Format) > 0 {
   218  		sizes := make([]string, len(imp.Banner.Format))
   219  		for i, format := range imp.Banner.Format {
   220  			sizes[i] = strconv.FormatInt(format.W, 10) + "x" + strconv.FormatInt(format.H, 10)
   221  		}
   222  
   223  		return strings.Join(sizes, ",")
   224  	}
   225  
   226  	if imp.Banner.W != nil && imp.Banner.H != nil {
   227  		return strconv.FormatInt(*imp.Banner.W, 10) + "x" + strconv.FormatInt(*imp.Banner.H, 10)
   228  	}
   229  
   230  	return ""
   231  }