Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Adapter: Displayio #3691

Merged
merged 10 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions adapters/displayio/displayio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package displayio

import (
"encoding/json"
"errors"
"fmt"
"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v2/adapters"
"github.com/prebid/prebid-server/v2/config"
"github.com/prebid/prebid-server/v2/errortypes"
"github.com/prebid/prebid-server/v2/macros"
"github.com/prebid/prebid-server/v2/openrtb_ext"
"net/http"
"text/template"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: I suggest organizing your imports so all standard packages are listed first, in which case you would move these after "fmt" and before all of the github imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports reorganized

)

type adapter struct {
endpoint *template.Template
}

type reqDioExt struct {
UserSession string `json:"userSession,omitempty"`
PlacementId string `json:"placementId"`
InventoryId string `json:"inventoryId"`
}

func (adapter *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to consider making a copy of request even though MakeRequests is provided with a shallow copy of it. It looks like you're modifying request, particularly request.ext, for each impression with each subsequent impression reading a previously modified version of request.ext instead of the version that was originally passed into MakeRequests.
On second look, this might not be a problem right now since you're always writing placementId and inventoryId to request.ext and those are guaranteed to exist for every impression which means the previous imp values are always overwritten. However, maybe this a change you want to consider to make your adapter future proof?

I should also note that any changes made to request in MakeRequests will be part of the request passed into MakeBids as the adapter shares the same shallow request copy provided to your adapter by PBS core. In that case, MakeBids will see request.ext.displayio with the placementId and inventoryId fields set to whatever their values are in the last impression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks for suggestion. I changed the code to use a copy of request for each imp

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-openrtb-version", "2.5")

var requestExt map[string]interface{}
var dioExt reqDioExt

impressions := request.Imp
result := make([]*adapters.RequestData, 0, len(impressions))
errs := make([]error, 0, len(impressions))

for _, impression := range impressions {
if impression.BidFloor == 0 {
errs = append(errs, &errortypes.BadInput{
Message: "BidFloor should be defined",
})
continue
}

if impression.BidFloorCur == "" {
impression.BidFloorCur = "USD"
}

if impression.BidFloorCur != "USD" {
convertedValue, err := requestInfo.ConvertCurrency(impression.BidFloor, impression.BidFloorCur, "USD")

if err != nil {
errs = append(errs, err)
continue
}

impression.BidFloorCur = "USD"
impression.BidFloor = convertedValue
}

if len(impression.Ext) == 0 {
errs = append(errs, errors.New("impression extensions required"))
continue
}

var bidderExt adapters.ExtImpBidder
err := json.Unmarshal(impression.Ext, &bidderExt)

if err != nil {
errs = append(errs, err)
continue
}
if len(bidderExt.Bidder) == 0 {
errs = append(errs, errors.New("bidder required"))
continue
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to add this check. Prebid core code will make sure Bidder value is present

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, removed

var impressionExt openrtb_ext.ExtImpDisplayio
err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
if err != nil {
errs = append(errs, err)
continue
}

dioExt = reqDioExt{PlacementId: impressionExt.PlacementId, InventoryId: impressionExt.InventoryId}

err = json.Unmarshal(request.Ext, &requestExt)
if err != nil {
requestExt = make(map[string]interface{})
}

requestExt["displayio"] = dioExt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason to unMarshal request.Ext on line 89? unMarshalled value is always overwritten by dioExt assignment on line 94

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@onkarvhanumante it's not overwritten but modified. We alter request.ext with dio-related data.


request.Ext, err = json.Marshal(requestExt)
if err != nil {
errs = append(errs, err)
continue
}

request.Imp = []openrtb2.Imp{impression}
body, err := json.Marshal(request)
if err != nil {
errs = append(errs, err)
continue
}

url, err := adapter.buildEndpointURL(&impressionExt)
if err != nil {
return nil, []error{err}
}

result = append(result, &adapters.RequestData{
Method: "POST",
Uri: url,
Body: body,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
})
}

request.Imp = impressions

if len(result) == 0 {
return nil, errs
}
return result, errs
}

// MakeBids translates Displayio bid response to prebid-server specific format
func (adapter *adapter) MakeBids(internalRequest *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {

if adapters.IsResponseStatusCodeNoContent(responseData) {
return nil, nil
}

if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
return nil, []error{err}
}

var bidResp openrtb2.BidResponse

if err := json.Unmarshal(responseData.Body, &bidResp); err != nil {
msg := fmt.Sprintf("Bad server response: %d", err)
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
}

if len(bidResp.SeatBid) != 1 {
msg := fmt.Sprintf("Invalid SeatBids count: %d", len(bidResp.SeatBid))
return nil, []error{&errortypes.BadServerResponse{Message: msg}}
}

var errs []error
bidResponse := adapters.NewBidderResponse()

for _, sb := range bidResp.SeatBid {
for i := range sb.Bid {
bidType, err := getBidMediaTypeFromMtype(&sb.Bid[i])
if err != nil {
errs = append(errs, err)
} else {
b := &adapters.TypedBid{
Bid: &sb.Bid[i],
BidType: bidType,
}
bidResponse.Bids = append(bidResponse.Bids, b)
}
}
}

return bidResponse, errs
}

func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
endpoint, err := template.New("endpointTemplate").Parse(config.Endpoint)
if err != nil {
return nil, fmt.Errorf("unable to parse endpoint url template: %v", err)
}

bidder := &adapter{
endpoint: endpoint,
}
return bidder, nil
}

func getBidMediaTypeFromMtype(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
switch bid.MType {
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupVideo:
return openrtb_ext.BidTypeVideo, nil
default:
return "", fmt.Errorf("unexpected media type for bid: %s", bid.ImpID)
}
}

func (adapter *adapter) buildEndpointURL(params *openrtb_ext.ExtImpDisplayio) (string, error) {
endpointParams := macros.EndpointTemplateParams{PublisherID: params.PublisherId}
return macros.ResolveMacros(adapter.endpoint, endpointParams)
}
23 changes: 23 additions & 0 deletions adapters/displayio/displayio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package displayio

import (
"testing"

"github.com/prebid/prebid-server/v2/config"
"github.com/prebid/prebid-server/v2/openrtb_ext"

"github.com/prebid/prebid-server/v2/adapters/adapterstest"
)

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderDisplayio,
config.Adapter{Endpoint: "https://prebid.display.io/?publisher=101"},
onkarvhanumante marked this conversation as resolved.
Show resolved Hide resolved
config.Server{ExternalUrl: "https://prebid.display.io/?publisher=101"},
)

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "displayiotest", bidder)
}
Loading
Loading