Skip to content

Commit

Permalink
New Adapter: ResetDigital (#3766)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Jacinto <[email protected]>
Co-authored-by: Emmanuel <[email protected]>
Co-authored-by: dirk-rd <[email protected]>
  • Loading branch information
4 people authored Jan 28, 2025
1 parent aaac978 commit 0c90e46
Show file tree
Hide file tree
Showing 24 changed files with 1,848 additions and 0 deletions.
58 changes: 58 additions & 0 deletions adapters/resetdigital/params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package resetdigital

import (
"encoding/json"
"testing"

"github.com/prebid/prebid-server/v3/openrtb_ext"
)

// TestValidParams tests valid parameter(s) declared in openrtb_ext/imp_resetdigital.go
func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, validParam := range validParams {
if err := validator.Validate(openrtb_ext.BidderResetDigital, json.RawMessage(validParam)); err != nil {
t.Errorf("Schema rejected ResetDigital params: %s \n Error: %s", validParam, err)
}
}
}

// TestValidParams tests invalid parameter(s) declared in openrtb_ext/imp_resetdigital.go
func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
t.Fatalf("Failed to fetch the json-schemas. %v", err)
}

for _, invalidParam := range invalidParams {
if err := validator.Validate(openrtb_ext.BidderResetDigital, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected ResetDigital params: %s", invalidParam)
}
}
}

// list of valid parameter(s) test cases
var validParams = []string{
`{"placement_id":"1000"}`,
`{"placement_id":"0"}`,
`{"placement_id":"abc"}`,
`{"placement_id":"123abc"}`,
`{}`,
`{"cp":"1000"}`,
}

// list of invalid parameter(s) test cases
var invalidParams = []string{
``,
`null`,
`true`,
`5`,
`4.2`,
`[]`,
`{"placement_id":}`,
`{"placement_id":""}`,
}
308 changes: 308 additions & 0 deletions adapters/resetdigital/resetdigital.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
package resetdigital

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

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/openrtb_ext"
)

type adapter struct {
endpoint *template.Template
endpointUri string
}

type resetDigitalRequest struct {
Site resetDigitalSite `json:"site"`
Imps []resetDigitalImp `json:"imps"`
}
type resetDigitalSite struct {
Domain string `json:"domain"`
Referrer string `json:"referrer"`
}
type resetDigitalImp struct {
ZoneID resetDigitalImpZone `json:"zone_id"`
BidID string `json:"bid_id"`
ImpID string `json:"imp_id"`
Ext resetDigitalImpExt `json:"ext"`
MediaTypes resetDigitalMediaTypes `json:"media_types"`
}
type resetDigitalImpZone struct {
PlacementID string `json:"placementId"`
}
type resetDigitalImpExt struct {
Gpid string `json:"gpid"`
}
type resetDigitalMediaTypes struct {
Banner resetDigitalMediaType `json:"banner,omitempty"`
Video resetDigitalMediaType `json:"video,omitempty"`
Audio resetDigitalMediaType `json:"audio,omitempty"`
}
type resetDigitalMediaType struct {
Sizes [][]int64 `json:"sizes,omitempty"`
Mimes []string `json:"mimes,omitempty"`
}
type resetDigitalBidResponse struct {
Bids []resetDigitalBid `json:"bids"`
}
type resetDigitalBid struct {
BidID string `json:"bid_id"`
ImpID string `json:"imp_id"`
CPM float64 `json:"cpm"`
CID string `json:"cid,omitempty"`
CrID string `json:"crid,omitempty"`
AdID string `json:"adid"`
W string `json:"w,omitempty"`
H string `json:"h,omitempty"`
Seat string `json:"seat"`
HTML string `json:"html"`
}

func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
template, 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: template,
}
return bidder, nil
}

func getHeaders(request *openrtb2.BidRequest) http.Header {
headers := http.Header{}

addNonEmptyHeaders(&headers, map[string]string{
"Content-Type": "application/json;charset=utf-8",
"Accept": "application/json",
})

if request != nil && request.Device != nil {
addNonEmptyHeaders(&headers, map[string]string{
"Accept-Language": request.Device.Language,
"User-Agent": request.Device.UA,
"X-Forwarded-For": request.Device.IP,
"X-Real-Ip": request.Device.IP,
})
}
if request != nil && request.Site != nil {
addNonEmptyHeaders(&headers, map[string]string{
"Referer": request.Site.Page,
})
}

return headers
}

func addNonEmptyHeaders(headers *http.Header, headerValues map[string]string) {
for key, value := range headerValues {
if len(value) > 0 {
headers.Add(key, value)
}
}
}

func (a *adapter) MakeRequests(requestData *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var (
requests []*adapters.RequestData
errors []error
)

for _, imp := range requestData.Imp {
bidType, err := getBidType(imp)
if err != nil {
errors = append(errors, err)
continue
}

splittedRequestData, err := processDataFromRequest(requestData, imp, bidType)
if err != nil {
errors = append(errors, err)
continue
}

requestBody, err := json.Marshal(splittedRequestData)

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

requests = append(requests, &adapters.RequestData{
Method: "POST",
Uri: a.endpointUri,
Body: requestBody,
Headers: getHeaders(requestData),
ImpIDs: []string{imp.ID},
})
}

return requests, errors
}

func processDataFromRequest(requestData *openrtb2.BidRequest, imp openrtb2.Imp, bidType openrtb_ext.BidType) (resetDigitalRequest, error) {
var reqData resetDigitalRequest

if requestData.Site != nil {
reqData.Site.Domain = requestData.Site.Domain
reqData.Site.Referrer = requestData.Site.Page
}

rdImp := resetDigitalImp{
BidID: requestData.ID,
ImpID: imp.ID,
}

if bidType == openrtb_ext.BidTypeBanner && imp.Banner != nil {
var tempH, tempW int64
if imp.Banner.H != nil {
tempH = *imp.Banner.H
}
if imp.Banner.W != nil {
tempW = *imp.Banner.W
}
if tempH > 0 && tempW > 0 {
rdImp.MediaTypes.Banner.Sizes = append(rdImp.MediaTypes.Banner.Sizes, []int64{tempW, tempH})
}
}
if bidType == openrtb_ext.BidTypeVideo && imp.Video != nil {
var tempH, tempW int64
if imp.Video.H != nil {
tempH = *imp.Video.H
}
if imp.Video.W != nil {
tempW = *imp.Video.W
}
if tempH > 0 && tempW > 0 {
rdImp.MediaTypes.Video.Sizes = append(rdImp.MediaTypes.Video.Sizes, []int64{tempW, tempH})
}
if imp.Video.MIMEs != nil {
rdImp.MediaTypes.Video.Mimes = append(rdImp.MediaTypes.Video.Mimes, imp.Video.MIMEs...)
}
}
if bidType == openrtb_ext.BidTypeAudio && imp.Audio != nil && imp.Audio.MIMEs != nil {
rdImp.MediaTypes.Audio.Mimes = append(rdImp.MediaTypes.Audio.Mimes, imp.Audio.MIMEs...)
}

var bidderExt adapters.ExtImpBidder
var resetDigitalExt openrtb_ext.ImpExtResetDigital

if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return resetDigitalRequest{}, err
}
if err := json.Unmarshal(bidderExt.Bidder, &resetDigitalExt); err != nil {
return resetDigitalRequest{}, err
}
rdImp.ZoneID.PlacementID = resetDigitalExt.PlacementID

reqData.Imps = append(reqData.Imps, rdImp)

return reqData, nil
}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *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 response resetDigitalBidResponse
if err := json.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
}

if len(response.Bids) != 1 {
return nil, []error{fmt.Errorf("expected exactly one bid in the response, but got %d", len(response.Bids))}
}

resetDigitalBid := &response.Bids[0]

requestImp, found := findRequestImpByID(request.Imp, resetDigitalBid.ImpID)
if !found {
return nil, []error{fmt.Errorf("no matching impression found for ImpID %s", resetDigitalBid.ImpID)}
}

bid, err := getBidFromResponse(resetDigitalBid)
if err != nil {
return nil, []error{err}
}

bidType := GetMediaTypeForImp(requestImp)

bidResponse := adapters.NewBidderResponseWithBidsCapacity(1)
bidResponse.Currency = "USD" // Default currency
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: bid,
BidType: bidType,
Seat: openrtb_ext.BidderName(resetDigitalBid.Seat),
})

return bidResponse, nil
}

// findRequestImpByID searches for an impression by its ID in the list of impressions
func findRequestImpByID(imps []openrtb2.Imp, impID string) (openrtb2.Imp, bool) {
for _, imp := range imps {
if imp.ID == impID {
return imp, true
}
}
return openrtb2.Imp{}, false
}

func getBidFromResponse(bidResponse *resetDigitalBid) (*openrtb2.Bid, error) {

bid := &openrtb2.Bid{
ID: bidResponse.BidID,
Price: bidResponse.CPM,
ImpID: bidResponse.ImpID,
CID: bidResponse.CID,
CrID: bidResponse.CrID,
AdM: bidResponse.HTML,
}

w, err := strconv.ParseInt(bidResponse.W, 10, 64)
if err != nil {
return nil, err
}
bid.W = w

h, err := strconv.ParseInt(bidResponse.H, 10, 64)
if err != nil {
return nil, err
}
bid.H = h
return bid, nil
}

func getBidType(imp openrtb2.Imp) (openrtb_ext.BidType, error) {
if imp.Banner != nil {
return openrtb_ext.BidTypeBanner, nil
} else if imp.Video != nil {
return openrtb_ext.BidTypeVideo, nil
} else if imp.Audio != nil {
return openrtb_ext.BidTypeAudio, nil
}

return "", fmt.Errorf("failed to find matching imp for bid %s", imp.ID)
}

func GetMediaTypeForImp(reqImp openrtb2.Imp) openrtb_ext.BidType {

if reqImp.Video != nil {
return openrtb_ext.BidTypeVideo
}
if reqImp.Audio != nil {
return openrtb_ext.BidTypeAudio
}
return openrtb_ext.BidTypeBanner
}
21 changes: 21 additions & 0 deletions adapters/resetdigital/resetdigital_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package resetdigital

import (
"testing"

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

func TestJsonSamples(t *testing.T) {

bidder, buildErr := Builder(openrtb_ext.BidderResetDigital, config.Adapter{
Endpoint: "https://test.com"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})

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

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

0 comments on commit 0c90e46

Please sign in to comment.