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 all 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
197 changes: 197 additions & 0 deletions adapters/displayio/displayio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package displayio

import (
"encoding/json"
"errors"
"fmt"
"net/http"
"text/template"

"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"
)

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")

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

for _, impression := range request.Imp {
var requestExt map[string]interface{}

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
}

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}

requestCopy := *request

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

requestExt["displayio"] = dioExt

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

requestCopy.Imp = []openrtb2.Imp{impression}
body, err := json.Marshal(requestCopy)
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(requestCopy.Imp),
})
}

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)
}
22 changes: 22 additions & 0 deletions adapters/displayio/displayio_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package displayio

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderDisplayio,
config.Adapter{Endpoint: "https://adapter.endpoint/?macro={{.PublisherID}}"},
config.Server{ExternalUrl: "https://server.endpoint/"},
)

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

adapterstest.RunJSONBidderTest(t, "displayiotest", bidder)
}
147 changes: 147 additions & 0 deletions adapters/displayio/displayiotest/exemplary/multi-format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"mockBidRequest": {
"id": "requestId10111011101110111011",
"app": {
"id": "1011"
},
"imp": [
{
"id": "impId10111011101110111011",
"tagid": "1011",
"ext": {
"bidder": {
"placementId": "1011",
"publisherId": "101",
"inventoryId": "1011"
}
},
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"video": {
"mimes": [
"video/mp4"
],
"protocols": [
2,
5
],
"w": 640,
"h": 480
},
"bidfloor": 0.5,
"bidfloorcur": "USD"
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://adapter.endpoint/?macro=101",
"body": {
"id": "requestId10111011101110111011",
"app": {
"id": "1011"
},
"imp": [
{
"id": "impId10111011101110111011",
"tagid": "1011",
"ext": {
"bidder": {
"placementId": "1011",
"publisherId": "101",
"inventoryId": "1011"
}
},
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"video": {
"mimes": [
"video/mp4"
],
"protocols": [
2,
5
],
"w": 640,
"h": 480
},
"bidfloor": 0.5,
"bidfloorcur": "USD"
}
],
"ext": {
"displayio": {
"placementId": "1011",
"inventoryId": "1011"
}
}
},
"impIDs": [
"impId10111011101110111011"
]
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"bidid": "5778926625248726496",
"seatbid": [
{
"seat": "seat1",
"bid": [
{
"id": "12345",
"impid": "impId10111011101110111011",
"price": 0.01,
"adm": "<html/>",
"adomain": [
"domain.test"
],
"w": 300,
"h": 250,
"mtype": 1
}
]
}
],
"cur": "USD"
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "12345",
"impid": "impId10111011101110111011",
"price": 0.01,
"adm": "<html/>",
"adomain": [
"domain.test"
],
"w": 300,
"h": 250,
"mtype": 1
},
"type": "banner"
}
]
}
]
}
Loading
Loading