From 939f355b3ce196c27f60673b2aa4dbe19cc3957c Mon Sep 17 00:00:00 2001 From: Bryan DeLong Date: Fri, 21 Sep 2018 10:24:38 -0400 Subject: [PATCH] SomoAudience adapter: Cleanup of code and adding an optional parameter (#664) * Prebid server reworking and optional params * Removed some copy and paste errors --- adapters/somoaudience/params_test.go | 3 + adapters/somoaudience/somoaudience.go | 189 +++++++++++------- .../exemplary/in-banner-video.json | 12 +- .../exemplary/native-1.1.json | 8 +- .../somoaudiencetest/exemplary/no-bid.json | 9 +- .../exemplary/no-content-response.json | 10 +- .../exemplary/simple-banner.json | 8 +- .../exemplary/simple-video.json | 8 +- .../somoaudiencetest/params/race/banner.json | 2 +- .../somoaudiencetest/params/race/video.json | 2 +- .../somoaudiencetest/supplemental/audio.json | 2 +- .../supplemental/bad-request-response.json | 8 +- .../somoaudiencetest/supplemental/gdpr.json | 8 +- .../supplemental/im-a-teapot-response.json | 8 +- openrtb_ext/imp_somoaudience.go | 3 +- static/bidder-params/somoaudience.json | 5 + 16 files changed, 163 insertions(+), 122 deletions(-) diff --git a/adapters/somoaudience/params_test.go b/adapters/somoaudience/params_test.go index 8f6930e2c3e..2cbb2b1f51a 100644 --- a/adapters/somoaudience/params_test.go +++ b/adapters/somoaudience/params_test.go @@ -41,9 +41,12 @@ func TestInvalidParams(t *testing.T) { var validParams = []string{ `{"placement_hash":"22a58cfb0c9b656bff713d1236e930e8"}`, + `{"placement_hash":"22a58cfb0c9b656bff713d1236e930e8", "bid_floor": 1.05}`, } var invalidParams = []string{ `{"placement_hash": 323423}`, `{"tag_id":"234234"}`, + `{"placement_hash":"22a58cfb0c9b656bff713d1236e930e8", "bid_floor": "423s"}`, + `{"placement_hash":"22a58cfb0c9b656bff713d1236e930e8", "bid_floor": -1}`, } diff --git a/adapters/somoaudience/somoaudience.go b/adapters/somoaudience/somoaudience.go index f05a6e754a8..39dc37c5f8a 100644 --- a/adapters/somoaudience/somoaudience.go +++ b/adapters/somoaudience/somoaudience.go @@ -4,81 +4,156 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" + "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/openrtb_ext" ) +const config = "hb_pbs_1.0.0" + type SomoaudienceAdapter struct { endpoint string } +type somoaudienceReqExt struct { + BidderConfig string `json:"prebid"` +} + func (a *SomoaudienceAdapter) MakeRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) { - totalImps := len(request.Imp) - errors := make([]error, 0, totalImps) - imp2placement := make(map[string][]int) + var errs []error + var bannerImps []openrtb.Imp + var videoImps []openrtb.Imp + var nativeImps []openrtb.Imp + + for _, imp := range request.Imp { + if imp.Banner != nil { + bannerImps = append(bannerImps, imp) + } else if imp.Video != nil { + videoImps = append(videoImps, imp) + } else if imp.Native != nil { + nativeImps = append(nativeImps, imp) + } else { + err := &errortypes.BadInput{ + Message: fmt.Sprintf("SomoAudience only supports banner and video imps. Ignoring imp id=%s", imp.ID), + } + glog.Warning("SomoAudience CAPABILITY VIOLATION: only supports banner and video imps") + errs = append(errs, err) + } + } + var adapterRequests []*adapters.RequestData + // Make a copy as we don't want to change the original request + reqCopy := *request + + reqCopy.Imp = bannerImps + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + + // Somoaudience only supports single imp video request + for _, videoImp := range videoImps { + reqCopy.Imp = []openrtb.Imp{videoImp} + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + } + + // Somoaudience only supports single imp video request + for _, nativeImp := range nativeImps { + reqCopy.Imp = []openrtb.Imp{nativeImp} + adapterReq, errors := a.makeRequest(&reqCopy) + if adapterReq != nil { + adapterRequests = append(adapterRequests, adapterReq) + } + errs = append(errs, errors...) + } + return adapterRequests, errs + +} - for i := 0; i < totalImps; i++ { +func (a *SomoaudienceAdapter) makeRequest(request *openrtb.BidRequest) (*adapters.RequestData, []error) { + var errs []error + var err error + var validImps []openrtb.Imp + reqExt := somoaudienceReqExt{BidderConfig: config} - placementHash, err := validateImpression(&request.Imp[i]) + var placementHash string + for _, imp := range request.Imp { + placementHash, err = preprocess(&imp, &reqExt) if err != nil { - errors = append(errors, err) + errs = append(errs, err) continue } + imp.Ext = nil + validImps = append(validImps, imp) + } - if _, ok := imp2placement[placementHash]; !ok { - imp2placement[placementHash] = make([]int, 0, totalImps-i) - } + // If all the imps were malformed, don't bother making a server call with no impressions. + if len(validImps) == 0 { + return nil, errs + } - imp2placement[placementHash] = append(imp2placement[placementHash], i) + request.Imp = validImps + request.Ext, err = json.Marshal(reqExt) + if err != nil { + errs = append(errs, err) + return nil, errs } - totalReqs := len(imp2placement) - if 0 == totalReqs { - return nil, errors + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs } headers := http.Header{} headers.Add("Content-Type", "application/json;charset=utf-8") headers.Add("Accept", "application/json") + headers.Add("x-openrtb-version", "2.5") - reqs := make([]*adapters.RequestData, 0, totalReqs) - - imps := request.Imp - request.Imp = make([]openrtb.Imp, 0, len(imps)) - - for placementHash, impIds := range imp2placement { - request.Imp = request.Imp[:0] - - for i := 0; i < len(impIds); i++ { - request.Imp = append(request.Imp, imps[impIds[i]]) - } + if request.Device != nil { + addHeaderIfNonEmpty(headers, "User-Agent", request.Device.UA) + addHeaderIfNonEmpty(headers, "X-Forwarded-For", request.Device.IP) + addHeaderIfNonEmpty(headers, "Accept-Language", request.Device.Language) + addHeaderIfNonEmpty(headers, "DNT", strconv.Itoa(int(request.Device.DNT))) + } + return &adapters.RequestData{ + Method: "POST", + Uri: a.endpoint + fmt.Sprintf("?s=%s", placementHash), + Body: reqJSON, + Headers: headers, + }, errs +} - body, err := json.Marshal(request) - if err != nil { - errors = append(errors, fmt.Errorf("error while encoding bidRequest, err: %s", err)) - return nil, errors +func preprocess(imp *openrtb.Imp, reqExt *somoaudienceReqExt) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + return "", &errortypes.BadInput{ + Message: "ignoring imp id=empty-extbid-test, extImpBidder is empty", } - - reqs = append(reqs, &adapters.RequestData{ - Method: "POST", - Uri: a.endpoint + fmt.Sprintf("?s=%s", placementHash), - Body: body, - Headers: headers, - }) } - if 0 == len(reqs) { - return nil, errors + var somoExt openrtb_ext.ExtImpSomoaudience + if err := json.Unmarshal(bidderExt.Bidder, &somoExt); err != nil { + return "", &errortypes.BadInput{ + Message: "ignoring imp id=empty-extbid-test, error while decoding impExt, err: " + err.Error(), + } } - return reqs, errors + imp.BidFloor = somoExt.BidFloor + imp.Ext = nil + return somoExt.PlacementHash, nil } func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { @@ -118,10 +193,10 @@ func (a *SomoaudienceAdapter) MakeBids(bidReq *openrtb.BidRequest, unused *adapt return bidResponse, nil } -func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { +func getMediaTypeForImp(impID string, imps []openrtb.Imp) openrtb_ext.BidType { mediaType := openrtb_ext.BidTypeBanner for _, imp := range imps { - if imp.ID == impId { + if imp.ID == impID { if imp.Banner != nil { mediaType = openrtb_ext.BidTypeBanner } else if imp.Video != nil { @@ -138,37 +213,11 @@ func getMediaTypeForImp(impId string, imps []openrtb.Imp) openrtb_ext.BidType { return mediaType } -func validateImpression(imp *openrtb.Imp) (string, error) { - - if imp.Audio != nil { - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("ignoring imp id=%s, Somoaudience doesn't support Audio", imp.ID), - } - } - - if 0 == len(imp.Ext) { - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("ignoring imp id=%s, extImpBidder is empty", imp.ID), - } - } - - var bidderExt adapters.ExtImpBidder - - if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("ignoring imp id=%s, error while decoding extImpBidder, err: %s", imp.ID, err), - } +//Adding header fields to request header +func addHeaderIfNonEmpty(headers http.Header, headerName string, headerValue string) { + if len(headerValue) > 0 { + headers.Add(headerName, headerValue) } - - impExt := openrtb_ext.ExtImpSomoaudience{} - err := json.Unmarshal(bidderExt.Bidder, &impExt) - if err != nil { - return "", &errortypes.BadInput{ - Message: fmt.Sprintf("ignoring imp id=%s, error while decoding impExt, err: %s", imp.ID, err), - } - } - - return impExt.PlacementHash, nil } func NewSomoaudienceBidder(endpoint string) *SomoaudienceAdapter { diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/in-banner-video.json b/adapters/somoaudience/somoaudiencetest/exemplary/in-banner-video.json index 1c5d0336d98..527958f7b2f 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/in-banner-video.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/in-banner-video.json @@ -24,7 +24,8 @@ }, "ext": { "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" + "placement_hash": "22a58cfb0c9b656bff713d1236e930e8", + "bid_floor": 1.05 } } } @@ -36,6 +37,9 @@ "expectedRequest": { "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "id": "test-request-id", "imp": [ { @@ -58,11 +62,7 @@ "w": 1024, "h": 576 }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } - } + "bidfloor": 1.05 } ] } diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/native-1.1.json b/adapters/somoaudience/somoaudiencetest/exemplary/native-1.1.json index 2f85c53e975..31e45ac469e 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/native-1.1.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/native-1.1.json @@ -40,11 +40,6 @@ "native": { "request": "{\"ver\":\"1.1\",\"context\":1,\"contextsubtype\":11,\"plcmttype\":4,\"plcmtcnt\":1,\"assets\":[{\"id\":1,\"required\":1,\"title\":{\"len\":500}},{\"id\":2,\"required\":1,\"img\":{\"type\":3,\"wmin\":1,\"hmin\":1}},{\"id\":3,\"required\":0,\"data\":{\"type\":1,\"len\":200}},{\"id\":4,\"required\":0,\"data\":{\"type\":2,\"len\":15000}},{\"id\":5,\"required\":0,\"data\":{\"type\":6,\"len\":40}},{\"id\":6,\"required\":0,\"data\":{\"type\":500}}]}", "ver": "1.1" - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ], @@ -55,6 +50,9 @@ "device": { "ip": "152.193.6.74" }, + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "user": { "id": "db089de9-a62e-4861-a881-0ff15e052516", "buyeruid": "8299345306627569435" diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/no-bid.json b/adapters/somoaudience/somoaudiencetest/exemplary/no-bid.json index d2874df9a65..3072168b3a2 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/no-bid.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/no-bid.json @@ -64,6 +64,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -78,12 +81,6 @@ "h": 600 } ] - }, - - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ], diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/no-content-response.json b/adapters/somoaudience/somoaudiencetest/exemplary/no-content-response.json index d0c97a96431..25088dac9e9 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/no-content-response.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/no-content-response.json @@ -31,6 +31,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -45,11 +48,6 @@ "h": 600 } ] - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ] @@ -65,6 +63,6 @@ ], "expectedBidResponses": [ - + ] } diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/simple-banner.json b/adapters/somoaudience/somoaudiencetest/exemplary/simple-banner.json index 886502d0111..c93ec159171 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/simple-banner.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/simple-banner.json @@ -31,6 +31,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -45,11 +48,6 @@ "h": 600 } ] - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ] diff --git a/adapters/somoaudience/somoaudiencetest/exemplary/simple-video.json b/adapters/somoaudience/somoaudiencetest/exemplary/simple-video.json index f65740b3827..f8b676595e8 100644 --- a/adapters/somoaudience/somoaudiencetest/exemplary/simple-video.json +++ b/adapters/somoaudience/somoaudiencetest/exemplary/simple-video.json @@ -25,6 +25,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -33,11 +36,6 @@ "protocols": [2, 5], "w": 1024, "h": 576 - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ] diff --git a/adapters/somoaudience/somoaudiencetest/params/race/banner.json b/adapters/somoaudience/somoaudiencetest/params/race/banner.json index 73ae4e10217..4fe8830bd60 100644 --- a/adapters/somoaudience/somoaudiencetest/params/race/banner.json +++ b/adapters/somoaudience/somoaudiencetest/params/race/banner.json @@ -1,3 +1,3 @@ { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" + "placement_hash": "22a58cfb0c9b656bff713d1236e930e8", "bid_floor": 1.05 } diff --git a/adapters/somoaudience/somoaudiencetest/params/race/video.json b/adapters/somoaudience/somoaudiencetest/params/race/video.json index 0d05a175c85..c8b8ae54c04 100644 --- a/adapters/somoaudience/somoaudiencetest/params/race/video.json +++ b/adapters/somoaudience/somoaudiencetest/params/race/video.json @@ -1,3 +1,3 @@ { -"placement_hash": "22a58cfb0c9b656bff713d1236e930e8" +"placement_hash": "22a58cfb0c9b656bff713d1236e930e8", "bid_floor":1.05 } diff --git a/adapters/somoaudience/somoaudiencetest/supplemental/audio.json b/adapters/somoaudience/somoaudiencetest/supplemental/audio.json index e89f5c8bb65..03027a75bb3 100644 --- a/adapters/somoaudience/somoaudiencetest/supplemental/audio.json +++ b/adapters/somoaudience/somoaudiencetest/supplemental/audio.json @@ -17,6 +17,6 @@ }, "expectedMakeRequestsErrors": [ - "ignoring imp id=unsupported-audio-imp, Somoaudience doesn't support Audio" + "SomoAudience only supports banner and video imps. Ignoring imp id=unsupported-audio-imp" ] } diff --git a/adapters/somoaudience/somoaudiencetest/supplemental/bad-request-response.json b/adapters/somoaudience/somoaudiencetest/supplemental/bad-request-response.json index 183ee26263f..aaccbd6e42f 100644 --- a/adapters/somoaudience/somoaudiencetest/supplemental/bad-request-response.json +++ b/adapters/somoaudience/somoaudiencetest/supplemental/bad-request-response.json @@ -31,6 +31,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -45,11 +48,6 @@ "h": 600 } ] - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ] diff --git a/adapters/somoaudience/somoaudiencetest/supplemental/gdpr.json b/adapters/somoaudience/somoaudiencetest/supplemental/gdpr.json index de2df6d0ed3..a5e82ac781a 100644 --- a/adapters/somoaudience/somoaudiencetest/supplemental/gdpr.json +++ b/adapters/somoaudience/somoaudiencetest/supplemental/gdpr.json @@ -60,6 +60,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -74,13 +77,8 @@ "h": 600 } ] - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" } } - } ], "site": { "domain": "www.publisher.com", diff --git a/adapters/somoaudience/somoaudiencetest/supplemental/im-a-teapot-response.json b/adapters/somoaudience/somoaudiencetest/supplemental/im-a-teapot-response.json index 7ed7c728f4d..d5ec0f7f962 100644 --- a/adapters/somoaudience/somoaudiencetest/supplemental/im-a-teapot-response.json +++ b/adapters/somoaudience/somoaudiencetest/supplemental/im-a-teapot-response.json @@ -31,6 +31,9 @@ "uri": "http://publisher-east.mobileadtrading.com/rtb/bid?s=22a58cfb0c9b656bff713d1236e930e8", "body": { "id": "test-request-id", + "ext":{ + "prebid": "hb_pbs_1.0.0" + }, "imp": [ { "id": "test-imp-id", @@ -45,11 +48,6 @@ "h": 600 } ] - }, - "ext": { - "bidder": { - "placement_hash": "22a58cfb0c9b656bff713d1236e930e8" - } } } ] diff --git a/openrtb_ext/imp_somoaudience.go b/openrtb_ext/imp_somoaudience.go index 2cfe492bb52..ef52ec7d054 100644 --- a/openrtb_ext/imp_somoaudience.go +++ b/openrtb_ext/imp_somoaudience.go @@ -1,5 +1,6 @@ package openrtb_ext type ExtImpSomoaudience struct { - PlacementHash string `json:"placement_hash"` + PlacementHash string `json:"placement_hash"` + BidFloor float64 `json:"bid_floor,omitempty"` } diff --git a/static/bidder-params/somoaudience.json b/static/bidder-params/somoaudience.json index 466d3f14417..421bfafe548 100644 --- a/static/bidder-params/somoaudience.json +++ b/static/bidder-params/somoaudience.json @@ -8,6 +8,11 @@ "placement_hash": { "type": "string", "description": "A hash defining the placement selling the impression" + }, + "bid_floor": { + "type": "number", + "description": "Bid Floor for Impression", + "minimum": 0 } }, "required": ["placement_hash"]