Skip to content

Commit

Permalink
New Adapter: Pangle (prebid#1697)
Browse files Browse the repository at this point in the history
Co-authored-by: hcai <[email protected]>
  • Loading branch information
2 people authored and shunj-nb committed Nov 8, 2022
1 parent 41a3a76 commit 50df88b
Show file tree
Hide file tree
Showing 24 changed files with 1,480 additions and 0 deletions.
197 changes: 197 additions & 0 deletions adapters/pangle/pangle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package pangle

import (
"encoding/json"
"fmt"
"net/http"

"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
)

type adapter struct {
Endpoint string
}

type wrappedExtImpBidder struct {
*adapters.ExtImpBidder
AdType int `json:"adtype,omitempty"`
}

type pangleBidExt struct {
Pangle *bidExt `json:"pangle,omitempty"`
}

type bidExt struct {
AdType *int `json:"adtype,omitempty"`
}

/* Builder */

func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) {
bidder := &adapter{
Endpoint: config.Endpoint,
}

return bidder, nil
}

/* MakeRequests */

func getAdType(imp openrtb.Imp, parsedImpExt *wrappedExtImpBidder) int {
// video
if imp.Video != nil {
if parsedImpExt != nil && parsedImpExt.Prebid != nil && parsedImpExt.Prebid.IsRewardedInventory == 1 {
return 7
}
if imp.Instl == 1 {
return 8
}
}
// banner
if imp.Banner != nil {
if imp.Instl == 1 {
return 2
} else {
return 1
}
}
// native
if imp.Native != nil && len(imp.Native.Request) > 0 {
return 5
}

return -1
}

func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var requests []*adapters.RequestData
var errs []error

requestCopy := *request
for _, imp := range request.Imp {
var impExt wrappedExtImpBidder
if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
errs = append(errs, fmt.Errorf("failed unmarshalling imp ext (err)%s", err.Error()))
continue
}
// detect and fill adtype
if adType := getAdType(imp, &impExt); adType == -1 {
errs = append(errs, adapters.BadInput("not a supported adtype"))
continue
} else {
impExt.AdType = adType
if newImpExt, err := json.Marshal(impExt); err == nil {
imp.Ext = newImpExt
} else {
errs = append(errs, fmt.Errorf("failed re-marshalling imp ext with adtype"))
continue
}
}
// for setting token
var bidderImpExt openrtb_ext.ImpExtPangle
if err := json.Unmarshal(impExt.Bidder, &bidderImpExt); err != nil {
errs = append(errs, fmt.Errorf("failed unmarshalling bidder imp ext (err)%s", err.Error()))
continue
}

requestCopy.Imp = []openrtb.Imp{imp}
requestJSON, err := json.Marshal(requestCopy)
if err != nil {
errs = append(errs, err)
continue
}

requestData := &adapters.RequestData{
Method: "POST",
Uri: a.Endpoint,
Body: requestJSON,
Headers: http.Header{
"TOKEN": []string{bidderImpExt.Token},
"Content-Type": []string{"application/json"},
},
}
requests = append(requests, requestData)
}

return requests, errs
}

/* MakeBids */

func getMediaTypeForBid(bid *openrtb.Bid) (openrtb_ext.BidType, error) {
if bid == nil {
return "", fmt.Errorf("the bid request object is nil")
}

var bidExt pangleBidExt
if err := json.Unmarshal(bid.Ext, &bidExt); err != nil {
return "", fmt.Errorf("invalid bid ext")
} else if bidExt.Pangle == nil || bidExt.Pangle.AdType == nil {
return "", fmt.Errorf("missing pangleExt/adtype in bid ext")
}

switch *bidExt.Pangle.AdType {
case 1:
return openrtb_ext.BidTypeBanner, nil
case 2:
return openrtb_ext.BidTypeBanner, nil
case 5:
return openrtb_ext.BidTypeNative, nil
case 7:
return openrtb_ext.BidTypeVideo, nil
case 8:
return openrtb_ext.BidTypeVideo, nil
}

return "", fmt.Errorf("unrecognized adtype in response")
}

func (a *adapter) MakeBids(request *openrtb.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if responseData.StatusCode == http.StatusNoContent {
return nil, nil
}

if responseData.StatusCode == http.StatusBadRequest {
err := &errortypes.BadInput{
Message: "Unexpected status code: 400. Bad request from publisher. Run with request.debug = 1 for more info.",
}
return nil, []error{err}
}

if responseData.StatusCode != http.StatusOK {
err := &errortypes.BadServerResponse{
Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info.", responseData.StatusCode),
}
return nil, []error{err}
}

var response openrtb.BidResponse
if err := json.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
}

var errs []error
bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
bidResponse.Currency = response.Cur
for _, seatBid := range response.SeatBid {
for _, temp := range seatBid.Bid {
bid := temp // avoid taking address of a for loop variable
mediaType, err := getMediaTypeForBid(&bid)
if err != nil {
errs = append(errs, err)
continue
}
b := &adapters.TypedBid{
Bid: &bid,
BidType: mediaType,
}
bidResponse.Bids = append(bidResponse.Bids, b)
}
}

return bidResponse, errs
}
21 changes: 21 additions & 0 deletions adapters/pangle/pangle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package pangle

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
conf := config.Adapter{
Endpoint: "https://pangle.io/api/get_ads",
}
bidder, buildErr := Builder(openrtb_ext.BidderPangle, conf)
if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

adapterstest.RunJSONBidderTest(t, "pangletest", bidder)
}
127 changes: 127 additions & 0 deletions adapters/pangle/pangletest/exemplary/app_banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"mockBidRequest": {
"id": "test-request-id",
"app": {
"bundle": "com.prebid"
},
"device": {
"ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab"
},
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {
"token": "123"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://pangle.io/api/get_ads",
"headers": {
"Content-Type": [
"application/json"
],
"TOKEN": [
"123"
]
},
"body": {
"id": "test-request-id",
"app": {
"bundle": "com.prebid"
},
"device": {
"ifa": "87857b31-8942-4646-ae80-ab9c95bf3fab"
},
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"adtype": 1,
"bidder": {
"token": "123"
},
"prebid": null
}
}
]
}
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"seatbid": [
{
"seat": "seat-id",
"bid": [
{
"id": "1",
"impid": "test-imp-id",
"adid": "11110126",
"price": 0.500000,
"adm": "some-test-ad",
"crid": "test-crid",
"h": 250,
"w": 300,
"ext": {
"pangle": {
"adtype": 1
}
}
}
]
}
],
"cur": "USD"
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "1",
"impid": "test-imp-id",
"adid": "11110126",
"price": 0.5,
"adm": "some-test-ad",
"crid": "test-crid",
"w": 300,
"h": 250,
"ext": {
"pangle": {
"adtype": 1
}
}
},
"type": "banner"
}
]
}
]
}
Loading

0 comments on commit 50df88b

Please sign in to comment.