Skip to content
This repository has been archived by the owner on Dec 22, 2022. It is now read-only.

Commit

Permalink
Introduce Adhese adapter (prebid#1292)
Browse files Browse the repository at this point in the history
Co-authored-by: Mateusz <[email protected]>
  • Loading branch information
sander-adhese and mefjush authored May 28, 2020
1 parent 8a875d4 commit e9553ab
Show file tree
Hide file tree
Showing 27 changed files with 1,579 additions and 1 deletion.
269 changes: 269 additions & 0 deletions adapters/adhese/adhese.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package adhese

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"text/template"

"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/macros"
"github.com/prebid/prebid-server/openrtb_ext"
)

type AdheseAdapter struct {
endpointTemplate template.Template
}

func extractSlotParameter(parameters openrtb_ext.ExtImpAdhese) string {
return fmt.Sprintf("/sl%s-%s", url.PathEscape(parameters.Location), url.PathEscape(parameters.Format))
}

func extractTargetParameters(parameters openrtb_ext.ExtImpAdhese) string {
if len(parameters.Keywords) == 0 {
return ""
}
var parametersAsString = ""
var targetParsed map[string]interface{}
err := json.Unmarshal(parameters.Keywords, &targetParsed)
if err != nil {
return ""
}

targetKeys := make([]string, 0, len(targetParsed))
for key := range targetParsed {
targetKeys = append(targetKeys, key)
}
sort.Strings(targetKeys)

for _, targetKey := range targetKeys {
var targetingValues = targetParsed[targetKey].([]interface{})
parametersAsString += "/" + url.PathEscape(targetKey)
for _, targetRawValKey := range targetingValues {
var targetValueParsed = targetRawValKey.(string)
parametersAsString += targetValueParsed + ";"
}
parametersAsString = strings.TrimRight(parametersAsString, ";")
}

return parametersAsString
}

func extractGdprParameter(request *openrtb.BidRequest) string {
if request.User != nil {
var extUser openrtb_ext.ExtUser
if err := json.Unmarshal(request.User.Ext, &extUser); err == nil {
return "/xt" + extUser.Consent
}
}
return ""
}

func extractRefererParameter(request *openrtb.BidRequest) string {
if request.Site != nil && request.Site.Page != "" {
return "/xf" + url.QueryEscape(request.Site.Page)
}
return ""
}

func (a *AdheseAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
errs := make([]error, 0, len(request.Imp))

var err error

// If all the requests are invalid, Call to adaptor is skipped
if len(request.Imp) == 0 {
errs = append(errs, WrapReqError("Imp is empty"))
return nil, errs
}

var imp = &request.Imp[0]
var bidderExt adapters.ExtImpBidder

if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
errs = append(errs, WrapReqError("Request could not be parsed as ExtImpBidder due to: "+err.Error()))
return nil, errs
}

var params openrtb_ext.ExtImpAdhese
if err := json.Unmarshal(bidderExt.Bidder, &params); err != nil {
errs = append(errs, WrapReqError("Request could not be parsed as ExtImpAdhese due to: "+err.Error()))
return nil, errs
}

// Compose url
endpointParams := macros.EndpointTemplateParams{AccountID: params.Account}

host, err := macros.ResolveMacros(*&a.endpointTemplate, endpointParams)
if err != nil {
errs = append(errs, WrapReqError("Could not compose url from template and request account val: "+err.Error()))
return nil, errs
}
complete_url := fmt.Sprintf("%s%s%s%s%s",
host,
extractSlotParameter(params),
extractTargetParameters(params),
extractGdprParameter(request),
extractRefererParameter(request))

return []*adapters.RequestData{{
Method: "GET",
Uri: complete_url,
}}, errs
}

func (a *AdheseAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if response.StatusCode == http.StatusNoContent {
return nil, nil
} else if response.StatusCode != http.StatusOK {
return nil, []error{WrapServerError(fmt.Sprintf("Unexpected status code: %d.", response.StatusCode))}
}

var bidResponse openrtb.BidResponse

var adheseBidResponseArray []AdheseBid
if err := json.Unmarshal(response.Body, &adheseBidResponseArray); err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed as generic Adhese bid.", string(response.Body)))}
}

var adheseBid = adheseBidResponseArray[0]

if adheseBid.Origin == "JERLICIA" {
var extArray []AdheseExt
var originDataArray []AdheseOriginData
if err := json.Unmarshal(response.Body, &extArray); err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA ext.", string(response.Body)))}
}

if err := json.Unmarshal(response.Body, &originDataArray); err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Response %v could not be parsed to JERLICIA origin data.", string(response.Body)))}
}
bidResponse = convertAdheseBid(adheseBid, extArray[0], originDataArray[0])
} else {
bidResponse = convertAdheseOpenRtbBid(adheseBid)
}

price, err := strconv.ParseFloat(adheseBid.Extension.Prebid.Cpm.Amount, 64)
if err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Price %v as float ", string(adheseBid.Extension.Prebid.Cpm.Amount)))}
}
width, err := strconv.ParseUint(adheseBid.Width, 10, 64)
if err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Width %v as int ", string(adheseBid.Width)))}
}
height, err := strconv.ParseUint(adheseBid.Height, 10, 64)
if err != nil {
return nil, []error{err, WrapServerError(fmt.Sprintf("Could not parse Height %v as int ", string(adheseBid.Height)))}
}
bidResponse.Cur = adheseBid.Extension.Prebid.Cpm.Currency
if len(bidResponse.SeatBid) > 0 && len(bidResponse.SeatBid[0].Bid) > 0 {
bidResponse.SeatBid[0].Bid[0].Price = price
bidResponse.SeatBid[0].Bid[0].W = width
bidResponse.SeatBid[0].Bid[0].H = height
}

bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)

if len(bidResponse.SeatBid) == 0 {
return nil, []error{WrapServerError("Response resulted in an empty seatBid array.")}
}

var errs []error
for _, sb := range bidResponse.SeatBid {
for i := 0; i < len(sb.Bid); i++ {
bid := sb.Bid[i]
bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getBidType(bid.AdM),
})

}
}
return bidderResponse, errs
}

func convertAdheseBid(adheseBid AdheseBid, adheseExt AdheseExt, adheseOriginData AdheseOriginData) openrtb.BidResponse {
adheseExtJson, err := json.Marshal(adheseOriginData)
if err != nil {
glog.Error(fmt.Sprintf("Unable to parse adhese Origin Data as JSON due to %v", err))
adheseExtJson = make([]byte, 0)
}
return openrtb.BidResponse{
ID: adheseExt.Id,
SeatBid: []openrtb.SeatBid{{
Bid: []openrtb.Bid{{
DealID: adheseExt.OrderId,
CrID: adheseExt.Id,
AdM: getAdMarkup(adheseBid, adheseExt),
Ext: adheseExtJson,
}},
Seat: "",
}},
}
}

func convertAdheseOpenRtbBid(adheseBid AdheseBid) openrtb.BidResponse {
var response openrtb.BidResponse = adheseBid.OriginData
if len(response.SeatBid) > 0 && len(response.SeatBid[0].Bid) > 0 {
response.SeatBid[0].Bid[0].AdM = adheseBid.Body
}
return response
}

func getAdMarkup(adheseBid AdheseBid, adheseExt AdheseExt) string {
if adheseExt.Ext == "js" {
if ContainsAny(adheseBid.Body, []string{"<script", "<div", "<html"}) {
counter := ""
if len(adheseExt.ImpressionCounter) > 0 {
counter = "<img src='" + adheseExt.ImpressionCounter + "' style='height:1px; width:1px; margin: -1px -1px; display:none;'/>"
}
return adheseBid.Body + counter
}
if ContainsAny(adheseBid.Body, []string{"<?xml", "<vast"}) {
return adheseBid.Body
}
}
return adheseExt.Tag
}

func getBidType(bidAdm string) openrtb_ext.BidType {
if bidAdm != "" && ContainsAny(bidAdm, []string{"<?xml", "<vast"}) {
return openrtb_ext.BidTypeVideo
}
return openrtb_ext.BidTypeBanner
}

func WrapReqError(errorStr string) *errortypes.BadInput {
return &errortypes.BadInput{Message: errorStr}
}

func WrapServerError(errorStr string) *errortypes.BadServerResponse {
return &errortypes.BadServerResponse{Message: errorStr}
}

func ContainsAny(raw string, keys []string) bool {
lowerCased := strings.ToLower(raw)
for i := 0; i < len(keys); i++ {
if strings.Contains(lowerCased, keys[i]) {
return true
}
}
return false

}

func NewAdheseBidder(uri string) *AdheseAdapter {
template, err := template.New("endpointTemplate").Parse(uri)
if err != nil {
glog.Fatal("Unable to parse endpoint url template")
return nil
}
return &AdheseAdapter{endpointTemplate: *template}
}
11 changes: 11 additions & 0 deletions adapters/adhese/adhese_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package adhese

import (
"testing"

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

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "adhesetest", NewAdheseBidder("https://ads-{{.AccountID}}.adhese.com/json"))
}
103 changes: 103 additions & 0 deletions adapters/adhese/adhesetest/exemplary/banner-internal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{
"mockBidRequest": {
"id": "test-req",
"user": {
"ext": {
"consent" : "dummy"
}
},
"imp": [
{
"id": "test-req",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {
"account": "demo",
"location": "_adhese_prebid_demo_",
"format": "leaderboard",
"targets":
{
"ci": ["gent", "brussels"],
"ag": ["55"],
"tl": ["all"]
}
}
}
}
],
"site": {
"id": "test",
"publisher": {
"id": "123"
}
}
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://ads-demo.adhese.com/json/sl_adhese_prebid_demo_-leaderboard/ag55/cigent;brussels/tlall/xtdummy"
},
"mockResponse": {
"status": 200,
"body": [{
"origin": "JERLICIA",
"originInstance": "",
"ext": "js",
"slotName": "_main_page_-leaderboard",
"adType": "leaderboard",
"orderId": "888",
"id": "60613369",
"width": "728",
"height": "90",
"body": "<div style='background-color:red; height:250px; width:300px'></div>",
"tracker": "https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a",
"impressionCounter": "https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a",
"extension": {
"prebid": {
"cpm": {
"amount": "1",
"currency": "USD"
}
}
}
}]
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"adm": "<div style='background-color:red; height:250px; width:300px'></div><img src='https://hosts-demo.adhese.com/rtb_gateway/handlers/client/track/?id=a2f39296-6dd0-4b3c-be85-7baa22e7ff4a' style='height:1px; width:1px; margin: -1px -1px; display:none;'/>",
"w": 728,
"h": 90,
"id": "",
"impid": "",
"price": 1.000000,
"dealid": "888",
"crid": "60613369",
"ext": {
"adFormat": "",
"adType": "leaderboard",
"adspaceId": "",
"libId": "",
"orderProperty": "",
"priority": "",
"viewableImpressionCounter": ""
}
},
"type": "banner"
}
]
}
]
}
Loading

0 comments on commit e9553ab

Please sign in to comment.