-
Notifications
You must be signed in to change notification settings - Fork 748
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
feat: add nativery prebid adapter #4074
Changes from all commits
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,39 @@ | ||
package nativery | ||
|
||
type adapter struct { | ||
endpoint string | ||
} | ||
|
||
type refRef struct { | ||
Page string `json:"page"` | ||
Ref string `json:"ref"` | ||
} | ||
|
||
// request body to send to widget server in ext | ||
type nativeryExtReqBody struct { | ||
Id string `json:"id"` //the placement/widget id | ||
Xhr int `json:"xhr"` | ||
V int `json:"v"` | ||
Ref string `json:"ref"` | ||
RefRef refRef `json:"refref"` | ||
} | ||
|
||
type impExt struct { | ||
Nativery nativeryExtReqBody `json:"nativery"` | ||
} | ||
|
||
type bidReqExtNativery struct { | ||
IsAMP bool `json:"is_amp"` | ||
} | ||
|
||
type bidExtNativery struct { | ||
BidType string `json:"bid_ad_media_type"` | ||
BidAdvDomains []string `json:"bid_adv_domains"` | ||
|
||
BrandId int `json:"brand_id,omitempty"` | ||
BrandCategory int `json:"brand_category_id,omitempty"` | ||
} | ||
|
||
type bidExt struct { | ||
Nativery bidExtNativery `json:"nativery"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
package nativery | ||
|
||
import ( | ||
"fmt" | ||
"maps" | ||
"net/http" | ||
"encoding/json" | ||
|
||
"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/metrics" | ||
"github.com/prebid/prebid-server/v3/openrtb_ext" | ||
) | ||
|
||
// Function used to builds a new instance of the Nativery adapter for the given bidder with the given config. | ||
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { | ||
// build bidder | ||
bidder := &adapter{ | ||
endpoint: config.Endpoint, | ||
} | ||
return bidder, nil | ||
} | ||
|
||
// makeRequests creates HTTP requests for a given BidRequest and adapter configuration. | ||
// It generates requests for each ad exchange targeted by the BidRequest, | ||
// serializes the BidRequest into the request body, and sets the appropriate | ||
// HTTP headers and other parameters. | ||
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { | ||
reqCopy := *request | ||
var errs []error | ||
|
||
// check if the request come from AMP | ||
var isAMP int | ||
if reqInfo.PbsEntryPoint == metrics.ReqTypeAMP { | ||
isAMP = 1 | ||
} | ||
|
||
// attach body request for all the impressions | ||
validImps := []openrtb2.Imp{} | ||
for _, imp := range request.Imp { | ||
reqCopy.Imp = []openrtb2.Imp{imp} | ||
|
||
nativeryExt, err := buildNativeryExt(&reqCopy.Imp[0]) | ||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
if err := buildRequest(reqCopy, nativeryExt); err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
validImps = append(validImps, reqCopy.Imp...) | ||
|
||
} | ||
|
||
reqCopy.Imp = validImps | ||
// If all the requests were malformed, don't bother making a server call with no impressions. | ||
if len(reqCopy.Imp) == 0 { | ||
return nil, errs | ||
} | ||
|
||
reqExt, err := getRequestExt(reqCopy.Ext) | ||
if err != nil { | ||
return nil, append(errs, err) | ||
} | ||
|
||
reqExtNativery, err := getNativeryExt(reqExt, isAMP) | ||
if err != nil { | ||
return nil, append(errs, err) | ||
} | ||
// TODO: optimize it, we reiterate imp there and before | ||
adapterRequests, errors := splitRequests(reqCopy.Imp, &reqCopy, reqExt, reqExtNativery, a.endpoint) | ||
|
||
return adapterRequests, append(errs, errors...) | ||
} | ||
|
||
func buildNativeryExt(imp *openrtb2.Imp) (openrtb_ext.ImpExtNativery, error) { | ||
var bidderExt adapters.ExtImpBidder | ||
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { | ||
return openrtb_ext.ImpExtNativery{}, err | ||
} | ||
|
||
var nativeryExt openrtb_ext.ImpExtNativery | ||
if err := json.Unmarshal(bidderExt.Bidder, &nativeryExt); err != nil { | ||
return openrtb_ext.ImpExtNativery{}, err | ||
} | ||
|
||
return nativeryExt, nil | ||
} | ||
|
||
// utility function used to build the body for the http request for a single impression | ||
func buildRequest(reqCopy openrtb2.BidRequest, reqExt openrtb_ext.ImpExtNativery) error { | ||
|
||
impExt := impExt{Nativery: nativeryExtReqBody{ | ||
Id: reqExt.PlacementID, | ||
Xhr: 2, | ||
V: 3, | ||
// TODO: Site is only for browser request, we have to handle if the req comes from app or dooh | ||
Ref: reqCopy.Site.Page, | ||
RefRef: refRef{Page: reqCopy.Site.Page, Ref: reqCopy.Site.Ref}, | ||
}} | ||
|
||
var err error | ||
reqCopy.Imp[0].Ext, err = json.Marshal(&impExt) | ||
|
||
return err | ||
} | ||
|
||
// makebids handles the entire bidding process for a single BidRequest. | ||
// It creates and sends bid requests to multiple ad exchanges, receives | ||
// and parses responses, extracts bids and other relevant information, | ||
// and populates a BidderResponse object with the aggregated information. | ||
func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { | ||
// check if the response has no content | ||
if adapters.IsResponseStatusCodeNoContent(response) { | ||
return nil, nil | ||
} | ||
|
||
// check if the response has errors | ||
if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
// handle response | ||
var nativeryResponse openrtb2.BidResponse | ||
if err := json.Unmarshal(response.Body, &nativeryResponse); err != nil { | ||
return nil, []error{err} | ||
} | ||
|
||
var errs []error | ||
// create bidder with impressions length capacity | ||
bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(internalRequest.Imp)) | ||
for _, sb := range nativeryResponse.SeatBid { | ||
for i := range sb.Bid { | ||
bid := sb.Bid[i] | ||
|
||
// should be data sended from nativery server to partecipate to the auction | ||
var bidExt bidExt | ||
if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
|
||
bidType, err := getMediaTypeForBid(&bidExt) | ||
if err != nil { | ||
errs = append(errs, err) | ||
continue | ||
} | ||
// get metadata | ||
bidMeta := buildBidMeta(string(bidType), bidExt.Nativery.BidAdvDomains) | ||
|
||
bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{ | ||
Bid: &bid, | ||
BidType: bidType, | ||
// metadata is encouraged | ||
BidMeta: bidMeta, | ||
}) | ||
} | ||
|
||
} | ||
|
||
// set bidder currency, EUR by default | ||
if nativeryResponse.Cur != "" { | ||
bidderResponse.Currency = nativeryResponse.Cur | ||
} else { | ||
bidderResponse.Currency = "EUR" | ||
} | ||
return bidderResponse, errs | ||
|
||
} | ||
|
||
// getMediaTypeForBid switch nativery type in bid type. | ||
func getMediaTypeForBid(bid *bidExt) (openrtb_ext.BidType, error) { | ||
switch bid.Nativery.BidType { | ||
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. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeNative. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. 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. Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeVideo. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression. |
||
case "native": | ||
return openrtb_ext.BidTypeNative, nil | ||
case "display": | ||
return openrtb_ext.BidTypeBanner, nil | ||
case "video": | ||
return openrtb_ext.BidTypeVideo, nil | ||
default: | ||
return "", fmt.Errorf("unrecognized bid_ad_media_type in response from nativery: %s", bid.Nativery.BidType) | ||
} | ||
} | ||
|
||
func convertIntToBoolean(num *int) bool { | ||
var b bool | ||
// Dereferenzia num usando * | ||
if num != nil && *num == 1 { | ||
b = true | ||
} else { | ||
b = false | ||
} | ||
return b | ||
} | ||
|
||
func buildBidMeta(mediaType string, advDomain []string) *openrtb_ext.ExtBidPrebidMeta { | ||
|
||
//advertiserDomains and dchain are encouraged to implements | ||
return &openrtb_ext.ExtBidPrebidMeta{ | ||
MediaType: mediaType, | ||
AdvertiserDomains: advDomain, | ||
/* | ||
DChain: json.RawMessage{} , | ||
Cosa include Dchain: | ||
nodes: Un array di oggetti che rappresentano i diversi partecipanti alla catena di domanda. | ||
complete: Un flag che indica se la catena di domanda è completa (1) o incompleta (0). | ||
ver: La versione del modulo Dchain utilizzato. | ||
*/ | ||
} | ||
} | ||
|
||
func splitRequests(imps []openrtb2.Imp, request *openrtb2.BidRequest, requestExt map[string]json.RawMessage, requestExtNativery bidReqExtNativery, uri string) ([]*adapters.RequestData, []error) { | ||
var errs []error | ||
|
||
resArr := make([]*adapters.RequestData, 0, 1) | ||
|
||
headers := http.Header{} | ||
headers.Add("Content-Type", "application/json;charset=utf-8") | ||
headers.Add("Accept", "application/json") | ||
|
||
nativeryExtJson, err := json.Marshal(requestExtNativery) | ||
if err != nil { | ||
errs = append(errs, err) | ||
} | ||
|
||
requestExtClone := maps.Clone(requestExt) | ||
requestExtClone["nativery"] = nativeryExtJson | ||
|
||
request.Ext, err = json.Marshal(requestExtClone) | ||
if err != nil { | ||
errs = append(errs, err) | ||
} | ||
|
||
for _, imp := range imps { | ||
impsForReq := []openrtb2.Imp{imp} | ||
request.Imp = impsForReq | ||
|
||
reqJSON, err := json.Marshal(request) | ||
if err != nil { | ||
errs = append(errs, err) | ||
return nil, errs | ||
} | ||
|
||
resArr = append(resArr, &adapters.RequestData{ | ||
Method: "POST", | ||
Uri: uri, | ||
Body: reqJSON, | ||
Headers: headers, | ||
}) | ||
} | ||
return resArr, errs | ||
} | ||
|
||
func getRequestExt(ext json.RawMessage) (map[string]json.RawMessage, error) { | ||
extMap := make(map[string]json.RawMessage) | ||
|
||
if len(ext) > 0 { | ||
if err := json.Unmarshal(ext, &extMap); err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
return extMap, nil | ||
} | ||
|
||
func getNativeryExt(extMap map[string]json.RawMessage, isAMP int) (bidReqExtNativery, error) { | ||
var nativeryExt bidReqExtNativery | ||
|
||
// if ext.nativery already exists return it | ||
if nativeryExtJson, exists := extMap["nativery"]; exists && len(nativeryExtJson) > 0 { | ||
if err := json.Unmarshal(nativeryExtJson, &nativeryExt); err != nil { | ||
return nativeryExt, err | ||
} | ||
} | ||
|
||
nativeryExt.IsAMP = convertIntToBoolean(&isAMP) | ||
|
||
return nativeryExt, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package nativery | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/prebid/prebid-server/v2/adapters/adapterstest" | ||
"github.com/prebid/prebid-server/v2/config" | ||
"github.com/prebid/prebid-server/v2/openrtb_ext" | ||
) | ||
|
||
func TestJsonSamples(t *testing.T) { | ||
bidder, buildErr := Builder(openrtb_ext.BidderNativery, config.Adapter{ | ||
Endpoint: "http://example.com/hb"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}) | ||
|
||
if buildErr != nil { | ||
t.Fatalf("Builder returned unexpected error %v", buildErr) | ||
} | ||
|
||
adapterstest.RunJSONBidderTest(t, "nativerytest", 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.
Consider this as a suggestion. The current implementation follows an anti-pattern, assumes that if there is a multi-format request, the media type defaults to openrtb_ext.BidTypeBanner. Prebid server expects the media type to be explicitly set in the adapter response. Therefore, we strongly recommend implementing a pattern where the adapter server sets the MType field in the response to accurately determine the media type for the impression.