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

     1  package operaads
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/prebid/prebid-server/v2/adapters"
    12  	"github.com/prebid/prebid-server/v2/config"
    13  	"github.com/prebid/prebid-server/v2/errortypes"
    14  	"github.com/prebid/prebid-server/v2/macros"
    15  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    16  	"github.com/prebid/prebid-server/v2/util/ptrutil"
    17  
    18  	"github.com/prebid/openrtb/v20/openrtb2"
    19  )
    20  
    21  type adapter struct {
    22  	epTemplate *template.Template
    23  }
    24  
    25  var (
    26  	errBannerFormatMiss = errors.New("Size information missing for banner")
    27  	errDeviceOrOSMiss   = errors.New("Impression is missing device OS information")
    28  )
    29  
    30  // Builder builds a new instance of the operaads 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  	epTemplate, err := template.New("endpoint").Parse(config.Endpoint)
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  	bidder := &adapter{
    37  		epTemplate: epTemplate,
    38  	}
    39  	return bidder, nil
    40  }
    41  
    42  func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
    43  	impCount := len(request.Imp)
    44  	requestData := make([]*adapters.RequestData, 0, impCount)
    45  	errs := []error{}
    46  	headers := http.Header{}
    47  	headers.Add("Content-Type", "application/json;charset=utf-8")
    48  	headers.Add("Accept", "application/json")
    49  
    50  	err := checkRequest(request)
    51  	if err != nil {
    52  		errs = append(errs, &errortypes.BadInput{
    53  			Message: err.Error(),
    54  		})
    55  		return nil, errs
    56  	}
    57  
    58  	for _, imp := range request.Imp {
    59  		var bidderExt adapters.ExtImpBidder
    60  		if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
    61  			errs = append(errs, &errortypes.BadInput{
    62  				Message: err.Error(),
    63  			})
    64  			continue
    65  		}
    66  		var operaadsExt openrtb_ext.ImpExtOperaads
    67  		if err := json.Unmarshal(bidderExt.Bidder, &operaadsExt); err != nil {
    68  			errs = append(errs, &errortypes.BadInput{
    69  				Message: err.Error(),
    70  			})
    71  			continue
    72  		}
    73  		macro := macros.EndpointTemplateParams{PublisherID: operaadsExt.PublisherID, AccountID: operaadsExt.EndpointID}
    74  		endpoint, err := macros.ResolveMacros(a.epTemplate, &macro)
    75  		if err != nil {
    76  			errs = append(errs, &errortypes.BadInput{
    77  				Message: err.Error(),
    78  			})
    79  			continue
    80  		}
    81  		imp.TagID = operaadsExt.PlacementID
    82  		formats := make([]interface{}, 0, 1)
    83  		if imp.Native != nil {
    84  			formats = append(formats, imp.Native)
    85  		}
    86  		if imp.Video != nil {
    87  			formats = append(formats, imp.Video)
    88  		}
    89  		if imp.Banner != nil {
    90  			formats = append(formats, imp.Banner)
    91  		}
    92  		for _, format := range formats {
    93  			req, err := flatImp(*request, imp, headers, endpoint, format)
    94  			if err != nil {
    95  				errs = append(errs, &errortypes.BadInput{
    96  					Message: err.Error(),
    97  				})
    98  				continue
    99  			}
   100  			if req != nil {
   101  				requestData = append(requestData, req)
   102  			}
   103  		}
   104  	}
   105  	return requestData, errs
   106  }
   107  
   108  func flatImp(requestCopy openrtb2.BidRequest, impCopy openrtb2.Imp, headers http.Header, endpoint string, format interface{}) (*adapters.RequestData, error) {
   109  	switch format.(type) {
   110  	case *openrtb2.Video:
   111  		impCopy.Native = nil
   112  		impCopy.Banner = nil
   113  		impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeVideo)
   114  	case *openrtb2.Banner:
   115  		impCopy.Video = nil
   116  		impCopy.Native = nil
   117  		impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeBanner)
   118  	case *openrtb2.Native:
   119  		impCopy.Video = nil
   120  		impCopy.Banner = nil
   121  		impCopy.ID = buildOperaImpId(impCopy.ID, openrtb_ext.BidTypeNative)
   122  	default: // do not need flat
   123  		return nil, nil
   124  	}
   125  	err := convertImpression(&impCopy)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	requestCopy.Imp = []openrtb2.Imp{impCopy}
   130  	reqJSON, err := json.Marshal(&requestCopy)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return &adapters.RequestData{
   135  		Method:  http.MethodPost,
   136  		Uri:     endpoint,
   137  		Body:    reqJSON,
   138  		Headers: headers,
   139  		ImpIDs:  openrtb_ext.GetImpIDs(requestCopy.Imp),
   140  	}, nil
   141  }
   142  
   143  func checkRequest(request *openrtb2.BidRequest) error {
   144  	if request.Device == nil || len(request.Device.OS) == 0 {
   145  		return errDeviceOrOSMiss
   146  	}
   147  
   148  	return nil
   149  }
   150  
   151  func convertImpression(imp *openrtb2.Imp) error {
   152  	if imp.Banner != nil {
   153  		bannerCopy, err := convertBanner(imp.Banner)
   154  		if err != nil {
   155  			return err
   156  		}
   157  		imp.Banner = bannerCopy
   158  	}
   159  	if imp.Native != nil && imp.Native.Request != "" {
   160  		v := make(map[string]interface{})
   161  		err := json.Unmarshal([]byte(imp.Native.Request), &v)
   162  		if err != nil {
   163  			return err
   164  		}
   165  		_, ok := v["native"]
   166  		if !ok {
   167  			body, err := json.Marshal(struct {
   168  				Native interface{} `json:"native"`
   169  			}{
   170  				Native: v,
   171  			})
   172  			if err != nil {
   173  				return err
   174  			}
   175  			native := *imp.Native
   176  			native.Request = string(body)
   177  			imp.Native = &native
   178  		}
   179  	}
   180  	return nil
   181  }
   182  
   183  // make sure that banner has openrtb 2.3-compatible size information
   184  func convertBanner(banner *openrtb2.Banner) (*openrtb2.Banner, error) {
   185  	if banner.W == nil || banner.H == nil || *banner.W == 0 || *banner.H == 0 {
   186  		if len(banner.Format) > 0 {
   187  			f := banner.Format[0]
   188  			bannerCopy := *banner
   189  			bannerCopy.W = ptrutil.ToPtr(f.W)
   190  			bannerCopy.H = ptrutil.ToPtr(f.H)
   191  			return &bannerCopy, nil
   192  		} else {
   193  			return nil, errBannerFormatMiss
   194  		}
   195  	}
   196  	return banner, nil
   197  }
   198  
   199  func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
   200  	if response.StatusCode == http.StatusNoContent {
   201  		return nil, nil
   202  	}
   203  
   204  	if response.StatusCode == http.StatusBadRequest {
   205  		return nil, []error{&errortypes.BadInput{
   206  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   207  		}}
   208  	}
   209  
   210  	if response.StatusCode != http.StatusOK {
   211  		return nil, []error{&errortypes.BadServerResponse{
   212  			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
   213  		}}
   214  	}
   215  
   216  	var parsedResponse openrtb2.BidResponse
   217  	if err := json.Unmarshal(response.Body, &parsedResponse); err != nil {
   218  		return nil, []error{&errortypes.BadServerResponse{
   219  			Message: err.Error(),
   220  		}}
   221  	}
   222  
   223  	bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
   224  	for _, sb := range parsedResponse.SeatBid {
   225  		for i := 0; i < len(sb.Bid); i++ {
   226  			bid := sb.Bid[i]
   227  			if bid.Price != 0 {
   228  				var bidType openrtb_ext.BidType
   229  				bid.ImpID, bidType = parseOriginImpId(bid.ImpID)
   230  				bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
   231  					Bid:     &bid,
   232  					BidType: bidType,
   233  				})
   234  			}
   235  		}
   236  	}
   237  	return bidResponse, nil
   238  }
   239  
   240  func buildOperaImpId(originId string, bidType openrtb_ext.BidType) string {
   241  	return strings.Join([]string{originId, "opa", string(bidType)}, ":")
   242  }
   243  
   244  func parseOriginImpId(impId string) (originId string, bidType openrtb_ext.BidType) {
   245  	items := strings.Split(impId, ":")
   246  	if len(items) < 2 {
   247  		return impId, ""
   248  	}
   249  	return strings.Join(items[:len(items)-2], ":"), openrtb_ext.BidType(items[len(items)-1])
   250  }