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

     1  package smartrtb
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"text/template"
     8  
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/adapters"
    11  	"github.com/prebid/prebid-server/v2/config"
    12  	"github.com/prebid/prebid-server/v2/errortypes"
    13  	"github.com/prebid/prebid-server/v2/macros"
    14  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    15  )
    16  
    17  // Base adapter structure.
    18  type SmartRTBAdapter struct {
    19  	EndpointTemplate *template.Template
    20  }
    21  
    22  // Bid request extension appended to downstream request.
    23  // PubID are non-empty iff request.{App,Site} or
    24  // request.{App,Site}.Publisher are nil, respectively.
    25  type bidRequestExt struct {
    26  	PubID    string `json:"pub_id,omitempty"`
    27  	ZoneID   string `json:"zone_id,omitempty"`
    28  	ForceBid bool   `json:"force_bid,omitempty"`
    29  }
    30  
    31  // bidExt.CreativeType values.
    32  // nolint: staticcheck // staticcheck SA9004: only the first constant in this group has an explicit type
    33  const (
    34  	creativeTypeBanner string = "BANNER"
    35  	creativeTypeVideo         = "VIDEO"
    36  	creativeTypeNative        = "NATIVE"
    37  	creativeTypeAudio         = "AUDIO"
    38  )
    39  
    40  // Bid response extension from downstream.
    41  type bidExt struct {
    42  	CreativeType string `json:"format"`
    43  }
    44  
    45  // Builder builds a new instance of the SmartRTB adapter for the given bidder with the given config.
    46  func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
    47  	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
    48  	if err != nil {
    49  		return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
    50  	}
    51  
    52  	bidder := &SmartRTBAdapter{
    53  		EndpointTemplate: template,
    54  	}
    55  	return bidder, nil
    56  }
    57  
    58  func (adapter *SmartRTBAdapter) buildEndpointURL(pubID string) (string, error) {
    59  	endpointParams := macros.EndpointTemplateParams{PublisherID: pubID}
    60  	return macros.ResolveMacros(adapter.EndpointTemplate, endpointParams)
    61  }
    62  
    63  func parseExtImp(dst *bidRequestExt, imp *openrtb2.Imp) error {
    64  	var ext adapters.ExtImpBidder
    65  	if err := json.Unmarshal(imp.Ext, &ext); err != nil {
    66  		return &errortypes.BadInput{
    67  			Message: err.Error(),
    68  		}
    69  	}
    70  
    71  	var src openrtb_ext.ExtImpSmartRTB
    72  	if err := json.Unmarshal(ext.Bidder, &src); err != nil {
    73  		return &errortypes.BadInput{
    74  			Message: err.Error(),
    75  		}
    76  	}
    77  
    78  	if dst.PubID == "" {
    79  		dst.PubID = src.PubID
    80  	}
    81  
    82  	if src.ZoneID != "" {
    83  		imp.TagID = src.ZoneID
    84  	}
    85  	return nil
    86  }
    87  
    88  func (s *SmartRTBAdapter) MakeRequests(brq *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    89  	var imps []openrtb2.Imp
    90  	var err error
    91  	ext := bidRequestExt{}
    92  	nrImps := len(brq.Imp)
    93  	errs := make([]error, 0, nrImps)
    94  
    95  	for i := 0; i < nrImps; i++ {
    96  		imp := brq.Imp[i]
    97  		if imp.Banner == nil && imp.Video == nil {
    98  			continue
    99  		}
   100  
   101  		err = parseExtImp(&ext, &imp)
   102  		if err != nil {
   103  			errs = append(errs, err)
   104  			continue
   105  		}
   106  
   107  		imps = append(imps, imp)
   108  	}
   109  
   110  	if len(imps) == 0 {
   111  		return nil, errs
   112  	}
   113  
   114  	if ext.PubID == "" {
   115  		return nil, append(errs, &errortypes.BadInput{Message: "Cannot infer publisher ID from bid ext"})
   116  	}
   117  
   118  	brq.Ext, err = json.Marshal(ext)
   119  	if err != nil {
   120  		return nil, append(errs, err)
   121  	}
   122  
   123  	brq.Imp = imps
   124  
   125  	rq, err := json.Marshal(brq)
   126  	if err != nil {
   127  		return nil, append(errs, err)
   128  	}
   129  
   130  	url, err := s.buildEndpointURL(ext.PubID)
   131  	if err != nil {
   132  		return nil, append(errs, err)
   133  	}
   134  
   135  	headers := http.Header{}
   136  	headers.Add("Content-Type", "application/json;charset=utf-8")
   137  	headers.Add("Accept", "application/json")
   138  	headers.Add("x-openrtb-version", "2.5")
   139  	return []*adapters.RequestData{{
   140  		Method:  "POST",
   141  		Uri:     url,
   142  		Body:    rq,
   143  		Headers: headers,
   144  		ImpIDs:  openrtb_ext.GetImpIDs(brq.Imp),
   145  	}}, errs
   146  }
   147  
   148  func (s *SmartRTBAdapter) MakeBids(
   149  	brq *openrtb2.BidRequest, drq *adapters.RequestData,
   150  	rs *adapters.ResponseData,
   151  ) (*adapters.BidderResponse, []error) {
   152  	if rs.StatusCode == http.StatusNoContent {
   153  		return nil, nil
   154  	} else if rs.StatusCode == http.StatusBadRequest {
   155  		return nil, []error{&errortypes.BadInput{Message: "Invalid request."}}
   156  	} else if rs.StatusCode != http.StatusOK {
   157  		return nil, []error{&errortypes.BadServerResponse{
   158  			Message: fmt.Sprintf("Unexpected HTTP status %d.", rs.StatusCode),
   159  		}}
   160  	}
   161  
   162  	var brs openrtb2.BidResponse
   163  	if err := json.Unmarshal(rs.Body, &brs); err != nil {
   164  		return nil, []error{err}
   165  	}
   166  
   167  	rv := adapters.NewBidderResponseWithBidsCapacity(5)
   168  	for _, seat := range brs.SeatBid {
   169  		for i := range seat.Bid {
   170  			var ext bidExt
   171  			if err := json.Unmarshal(seat.Bid[i].Ext, &ext); err != nil {
   172  				return nil, []error{&errortypes.BadServerResponse{
   173  					Message: "Invalid bid extension from endpoint.",
   174  				}}
   175  			}
   176  
   177  			var btype openrtb_ext.BidType
   178  			switch ext.CreativeType {
   179  			case creativeTypeBanner:
   180  				btype = openrtb_ext.BidTypeBanner
   181  			case creativeTypeVideo:
   182  				btype = openrtb_ext.BidTypeVideo
   183  			default:
   184  				return nil, []error{&errortypes.BadServerResponse{
   185  					Message: fmt.Sprintf("Unsupported creative type %s.",
   186  						ext.CreativeType),
   187  				}}
   188  			}
   189  
   190  			seat.Bid[i].Ext = nil
   191  
   192  			rv.Bids = append(rv.Bids, &adapters.TypedBid{
   193  				Bid:     &seat.Bid[i],
   194  				BidType: btype,
   195  			})
   196  		}
   197  	}
   198  	return rv, nil
   199  }