-
Notifications
You must be signed in to change notification settings - Fork 735
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: UNICORN #1719
New Adapter: UNICORN #1719
Changes from 22 commits
b699f42
956aa91
9bdb44b
0515c28
36ae31f
ec49639
725321d
003e27d
95844a4
ca3026a
285c5d7
f1c2427
42ae15a
019800d
7be6aa9
8b9dcd1
f067fac
8cb4906
74b363a
3b1f698
1080b56
435bf6e
c27da8b
fb58a7e
deaf6c0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package unicorn | ||
|
||
import ( | ||
"encoding/json" | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/openrtb_ext" | ||
) | ||
|
||
func TestValidParams(t *testing.T) { | ||
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
if err != nil { | ||
t.Fatalf("Failed to fetch the json schema. %v", err) | ||
} | ||
|
||
for _, p := range validParams { | ||
if err := validator.Validate(openrtb_ext.BidderUnicorn, json.RawMessage(p)); err != nil { | ||
t.Errorf("Schema rejected valid params: %s", p) | ||
} | ||
} | ||
} | ||
|
||
func TestInvalidParams(t *testing.T) { | ||
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") | ||
if err != nil { | ||
t.Fatalf("Failed to fetch the json schema. %v", err) | ||
} | ||
|
||
for _, p := range invalidParams { | ||
if err := validator.Validate(openrtb_ext.BidderUnicorn, json.RawMessage(p)); err == nil { | ||
t.Errorf("Schema allowed invalid params: %s", p) | ||
} | ||
} | ||
} | ||
|
||
var validParams = []string{ | ||
`{ | ||
"accountId": 199578, | ||
"publisherId": 123456, | ||
"mediaId": "test_media", | ||
"placementId": "test_placement" | ||
}`, | ||
`{ | ||
"accountId": 199578, | ||
"mediaId": "test_media" | ||
}`, | ||
} | ||
|
||
var invalidParams = []string{ | ||
`{}`, | ||
`{ | ||
"accountId": "199578", | ||
"publisherId": "123456", | ||
"mediaId": 12345, | ||
"placementId": 12345 | ||
}`, | ||
`{ | ||
"publisherId": 123456, | ||
"placementId": "test_placement" | ||
}`, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,244 @@ | ||
package unicorn | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/buger/jsonparser" | ||
"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 | ||
} | ||
|
||
// unicornImpExt is imp ext for UNICORN | ||
type unicornImpExt struct { | ||
Context *unicornImpExtContext `json:"context,omitempty"` | ||
Bidder openrtb_ext.ExtImpUnicorn `json:"bidder"` | ||
} | ||
|
||
type unicornImpExtContext struct { | ||
Data interface{} `json:"data,omitempty"` | ||
} | ||
|
||
// unicornExt is ext for UNICORN | ||
type unicornExt struct { | ||
Prebid *openrtb_ext.ExtImpPrebid `json:"prebid,omitempty"` | ||
AccountID int64 `json:"accountId,omitempty"` | ||
} | ||
|
||
// Builder builds a new instance of the UNICORN adapter for the given bidder with the given config. | ||
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter) (adapters.Bidder, error) { | ||
bidder := &adapter{ | ||
endpoint: config.Endpoint, | ||
} | ||
return bidder, nil | ||
} | ||
|
||
// MakeRequests makes the HTTP requests which should be made to fetch bids. | ||
func (a *adapter) MakeRequests(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
var extRegs openrtb_ext.ExtRegs | ||
if request.Regs != nil { | ||
if request.Regs.COPPA == 1 { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: "COPPA is not supported", | ||
}} | ||
} | ||
if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil { | ||
if extRegs.GDPR != nil && (*extRegs.GDPR == 1) { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: "GDPR is not supported", | ||
}} | ||
} | ||
if extRegs.USPrivacy != "" { | ||
return nil, []error{&errortypes.BadInput{ | ||
Message: "CCPA is not supported", | ||
}} | ||
} | ||
} | ||
} | ||
|
||
err := modifyImps(request, requestInfo) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
request.Source.Ext = setSourceExt() | ||
|
||
request.Ext, err = setExt(request) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
requestJSON, err := json.Marshal(request) | ||
if err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
requestData := &adapters.RequestData{ | ||
Method: "POST", | ||
Uri: a.endpoint, | ||
Body: requestJSON, | ||
Headers: getHeaders(request), | ||
} | ||
|
||
return []*adapters.RequestData{requestData}, nil | ||
} | ||
|
||
func getHeaders(request *openrtb.BidRequest) http.Header { | ||
headers := http.Header{} | ||
headers.Add("Content-Type", "application/json;charset=utf-8") | ||
headers.Add("Accept", "application/json") | ||
headers.Add("X-Openrtb-Version", "2.5") | ||
|
||
if request.Device != nil { | ||
if len(request.Device.UA) > 0 { | ||
headers.Add("User-Agent", request.Device.UA) | ||
} | ||
|
||
if len(request.Device.IPv6) > 0 { | ||
headers.Add("X-Forwarded-For", request.Device.IPv6) | ||
} | ||
|
||
if len(request.Device.IP) > 0 { | ||
headers.Add("X-Forwarded-For", request.Device.IP) | ||
} | ||
} | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return headers | ||
} | ||
|
||
func modifyImps(request *openrtb.BidRequest, requestInfo *adapters.ExtraRequestInfo) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second parameter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Certainly, |
||
for i := 0; i < len(request.Imp); i++ { | ||
imp := &request.Imp[i] | ||
|
||
var ext unicornImpExt | ||
err := json.Unmarshal(imp.Ext, &ext) | ||
|
||
if err != nil { | ||
return &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Error while decoding imp[%d].ext: %s", i, err), | ||
} | ||
} | ||
|
||
var placementID string | ||
if ext.Bidder.PlacementID != "" { | ||
placementID = ext.Bidder.PlacementID | ||
} else { | ||
placementID, err = getStoredRequestImpID(imp) | ||
if err != nil { | ||
return &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Error get StoredRequestImpID from imp[%d]: %s", i, err), | ||
} | ||
} | ||
} | ||
|
||
ext.Bidder.PlacementID = placementID | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fact that there's a scenario where
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Certainly, fixed it. Thanks you. |
||
|
||
imp.Ext, err = json.Marshal(ext) | ||
if err != nil { | ||
return &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Error while encoding imp[%d].ext: %s", i, err), | ||
} | ||
} | ||
|
||
secure := int8(1) | ||
imp.Secure = &secure | ||
imp.TagID = placementID | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We seem to not be doing anything with
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh you are right. Misread line 126 as a local copy of the imp ( |
||
} | ||
return nil | ||
} | ||
|
||
func getStoredRequestImpID(imp *openrtb.Imp) (string, error) { | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
v, err := jsonparser.GetString(imp.Ext, "prebid", "storedrequest", "id") | ||
|
||
if err != nil { | ||
return "", fmt.Errorf("stored request id not found: %s", err) | ||
} | ||
|
||
return v, nil | ||
} | ||
|
||
func setSourceExt() json.RawMessage { | ||
return json.RawMessage(`{"stype": "prebid_server_uncn", "bidder": "unicorn"}`) | ||
} | ||
|
||
func setExt(request *openrtb.BidRequest) (json.RawMessage, error) { | ||
accountID, err := jsonparser.GetInt(request.Imp[0].Ext, "bidder", "accountId") | ||
if err != nil { | ||
accountID = 0 | ||
} | ||
var decodedExt *unicornExt | ||
err = json.Unmarshal(request.Ext, &decodedExt) | ||
if err != nil { | ||
decodedExt = &unicornExt{ | ||
Prebid: nil, | ||
} | ||
} | ||
decodedExt.AccountID = accountID | ||
|
||
ext, err := json.Marshal(decodedExt) | ||
if err != nil { | ||
return nil, &errortypes.BadInput{ | ||
Message: fmt.Sprintf("Error while encoding ext, err: %s", err), | ||
} | ||
} | ||
return ext, nil | ||
} | ||
|
||
// MakeBids unpacks the server's response into Bids. | ||
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 { | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
err := &errortypes.BadInput{ | ||
Message: "Unexpected http status code: 400", | ||
} | ||
return nil, []error{err} | ||
} | ||
|
||
if responseData.StatusCode != http.StatusOK { | ||
err := &errortypes.BadServerResponse{ | ||
Message: fmt.Sprintf("Unexpected http status code: %d", responseData.StatusCode), | ||
} | ||
return nil, []error{err} | ||
} | ||
|
||
var response openrtb.BidResponse | ||
if err := json.Unmarshal(responseData.Body, &response); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp)) | ||
bidResponse.Currency = response.Cur | ||
for _, seatBid := range response.SeatBid { | ||
for _, bid := range seatBid.Bid { | ||
bid := bid | ||
var bidType openrtb_ext.BidType | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, imp := range request.Imp { | ||
if imp.ID == bid.ImpID { | ||
if imp.Banner != nil { | ||
SyntaxNode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
bidType = openrtb_ext.BidTypeBanner | ||
} else if imp.Native != nil { | ||
bidType = openrtb_ext.BidTypeNative | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Native will support it in the future, so I left it, but since it is not currently supported, I will remove this branch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed it |
||
} | ||
} | ||
} | ||
b := &adapters.TypedBid{ | ||
Bid: &bid, | ||
BidType: bidType, | ||
} | ||
bidResponse.Bids = append(bidResponse.Bids, b) | ||
} | ||
} | ||
return bidResponse, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package unicorn | ||
|
||
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) { | ||
bidder, buildErr := Builder(openrtb_ext.BidderUnicorn, config.Adapter{ | ||
Endpoint: "https://ds.uncn.jp"}) | ||
|
||
if buildErr != nil { | ||
t.Fatalf("Builder returned unexpected error %v", buildErr) | ||
} | ||
|
||
adapterstest.RunJSONBidderTest(t, "unicorntest", bidder) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prebid server core passes a shallow copy of the
openrtb.BidRequest
object to theMakeRequest()
function which makes pointer fields likeSource
to be red flags because they point to shared memory.Please create a new Source object copy locally and assign to the
bidRequest
. Notice that, depending on its initial configuration, prebid server core may or may not initializebidRequest.Source
field if it comes with anil
value (seeendpoints/openrtb2/auction.go
's line 311). In summary, we could do something like:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it. Thank you so much!
c27da8b
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed again because there is have copying source problem.
fb58a7e