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