From fd085be55d07f533c6f197a27242f62bfffe4db0 Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Mon, 3 Jun 2024 08:15:40 +0530 Subject: [PATCH 1/9] OTT-1799: oRTB bidder endpoint can support macros --- adapters/bidder.go | 2 - adapters/ortbbidder/bidderparams/config.go | 2 +- adapters/ortbbidder/constant.go | 12 + adapters/ortbbidder/ortbbidder.go | 182 ++- adapters/ortbbidder/ortbbidder_test.go | 1011 +++++++++++++++-- adapters/ortbbidder/requestParamMapper.go | 78 +- .../ortbbidder/requestParamMapper_test.go | 297 ++--- adapters/ortbbidder/util.go | 27 +- adapters/ortbbidder/util_test.go | 119 +- config/bidderinfo.go | 4 + exchange/exchange.go | 1 - static/bidder-params/owortb_testbidder.json | 56 +- 12 files changed, 1338 insertions(+), 453 deletions(-) create mode 100644 adapters/ortbbidder/constant.go diff --git a/adapters/bidder.go b/adapters/bidder.go index b659f969f71..c18dde7c120 100644 --- a/adapters/bidder.go +++ b/adapters/bidder.go @@ -156,8 +156,6 @@ type ExtraRequestInfo struct { PbsEntryPoint metrics.RequestType GlobalPrivacyControlHeader string CurrencyConversions currency.Conversions - - BidderCoreName openrtb_ext.BidderName // OW specific: required for oRTB bidder } func NewExtraRequestInfo(c currency.Conversions) ExtraRequestInfo { diff --git a/adapters/ortbbidder/bidderparams/config.go b/adapters/ortbbidder/bidderparams/config.go index 1b75da9bf25..4e57d3b1414 100644 --- a/adapters/ortbbidder/bidderparams/config.go +++ b/adapters/ortbbidder/bidderparams/config.go @@ -46,7 +46,7 @@ func (bcfg *BidderConfig) GetRequestParams(bidderName string) (map[string]Bidder if bcfg == nil || len(bcfg.bidderConfigMap) == 0 { return nil, false } - bidderConfig, _ := bcfg.bidderConfigMap[bidderName] + bidderConfig := bcfg.bidderConfigMap[bidderName] if bidderConfig == nil { return nil, false } diff --git a/adapters/ortbbidder/constant.go b/adapters/ortbbidder/constant.go new file mode 100644 index 00000000000..9e773d6a356 --- /dev/null +++ b/adapters/ortbbidder/constant.go @@ -0,0 +1,12 @@ +package ortbbidder + +// constants required for oRTB adapter +const ( + impKey = "imp" + extKey = "ext" + bidderKey = "bidder" + appsiteKey = "appsite" + siteKey = "site" + appKey = "app" + requestModeSingle = "single" +) diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index 4cc3f349f8a..0e11cc50233 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -5,12 +5,15 @@ import ( "fmt" "net/http" "strings" + "text/template" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" "github.com/prebid/prebid-server/v2/config" + "github.com/prebid/prebid-server/v2/macros" "github.com/prebid/prebid-server/v2/openrtb_ext" + "github.com/prebid/prebid-server/v2/util/jsonutil" ) // adapter implements adapters.Bidder interface @@ -19,15 +22,12 @@ type adapter struct { bidderParamsConfig *bidderparams.BidderConfig } -const ( - RequestModeSingle string = "single" -) - // adapterInfo contains oRTB bidder specific info required in MakeRequests/MakeBids functions type adapterInfo struct { config.Adapter - extraInfo extraAdapterInfo - bidderName openrtb_ext.BidderName + extraInfo extraAdapterInfo + bidderName openrtb_ext.BidderName + endpointTemplate *template.Template } type extraAdapterInfo struct { RequestMode string `json:"requestMode"` @@ -42,78 +42,152 @@ func InitBidderParamsConfig(dirPath string) (err error) { return err } -// makeRequest converts openrtb2.BidRequest to adapters.RequestData, sets requestParams in request if required -func (o adapterInfo) makeRequest(request *openrtb2.BidRequest, requestParams map[string]bidderparams.BidderParamMapper) (*adapters.RequestData, error) { - if request == nil { - return nil, fmt.Errorf("found nil request") +// makeRequestForAllImps processes a request map to create single RequestData object for all impressions. +// It constructs the endpoint URL and maps the request-params in request to form the RequestData object. +func (adapterInfo adapterInfo) makeRequestForAllImps(request map[string]any, bidderParamMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { + imps, ok := request[impKey].([]any) + if !ok { + return nil, []error{newBadInputError("invalid imp object found in request")} } - requestBody, err := json.Marshal(request) - if err != nil { - return nil, fmt.Errorf("failed to marshal request %s", err.Error()) + var ( + err error + uri string + errs []error + ) + // iterate through imps in reverse order to ensure setRequestParams prioritizes + // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. + for impIndex := len(imps) - 1; impIndex >= 0; impIndex-- { + imp, ok := imps[impIndex].(map[string]any) + if !ok || imp == nil { + errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) + continue + } + bidderParams := getImpExtBidderParams(imp) + // build endpoint URL once, using the imp[0].ext.bidder parameters + // this must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. + if impIndex == 0 { + uri, err = macros.ResolveMacros(adapterInfo.endpointTemplate, bidderParams) + if err != nil { + return nil, []error{newBadInputError(fmt.Sprintf("failed to form endpoint url, err:%s", err.Error()))} + } + } + // update the request and imp object by mapping bidderParams at expected location. + setRequestParams(request, imp, bidderParams, bidderParamMapper) } - requestBody, err = setRequestParams(requestBody, requestParams) + requestBody, err := jsonutil.Marshal(request) if err != nil { - return nil, err - } - return &adapters.RequestData{ - Method: http.MethodPost, - Uri: o.Endpoint, - Body: requestBody, - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, + return nil, []error{newBadInputError(fmt.Sprintf("failed to marshal request after setting bidder-params, err:%s", err.Error()))} + } + return []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: uri, + Body: requestBody, + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, }, - }, nil + }, errs +} + +// makeRequestPerImp processes a request map to generate 'N' RequestData objects, one for each of the 'N' impressions. +// It constructs the endpoint URL and maps the request parameters to create the RequestData objects. +func (adapterInfo adapterInfo) makeRequestPerImp(request map[string]any, requestParams map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { + imps, ok := request[impKey].([]any) + if !ok { + return nil, []error{newBadInputError("invalid imp object found in request")} + } + var ( + bidderParams map[string]any + requestData []*adapters.RequestData + errs []error + ) + for impIndex, imp := range imps { + imp, ok := imp.(map[string]any) + if !ok || imp == nil { + errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) + continue + } + bidderParams = getImpExtBidderParams(imp) + // build endpoint url from imp.ext.bidder + // this must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. + uri, err := macros.ResolveMacros(adapterInfo.endpointTemplate, bidderParams) + if err != nil { + errs = append(errs, newBadInputError(fmt.Sprintf("failed to form endpoint url for imp at index:%d, err:%s", impIndex, err.Error()))) + continue + } + // update the request and imp object by mapping bidderParams at expected location. + setRequestParams(request, imp, bidderParams, requestParams) + // request should contain single impression so override the request["imp"] field + request[impKey] = []any{imp} + + requestBody, err := jsonutil.Marshal(request) + if err != nil { + errs = append(errs, newBadInputError(fmt.Sprintf("failed to marshal request after seeting bidder-params for imp at index:%d, err:%s", impIndex, err.Error()))) + continue + } + requestData = append(requestData, &adapters.RequestData{ + Method: http.MethodPost, + Uri: uri, + Body: requestBody, + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }) + } + return requestData, errs } // Builder returns an instance of oRTB adapter func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraAdapterInfo := extraAdapterInfo{} if len(config.ExtraAdapterInfo) > 0 { - err := json.Unmarshal([]byte(config.ExtraAdapterInfo), &extraAdapterInfo) + err := jsonutil.Unmarshal([]byte(config.ExtraAdapterInfo), &extraAdapterInfo) if err != nil { - return nil, fmt.Errorf("Failed to parse extra_info for bidder:[%s] err:[%s]", bidderName, err.Error()) + return nil, fmt.Errorf("failed to parse extra_info: %s", err.Error()) } } + template, err := template.New("endpointTemplate").Parse(config.Endpoint) + if err != nil || template == nil { + return nil, fmt.Errorf("failed to parse endpoint url template: %v", err) + } return &adapter{ - adapterInfo: adapterInfo{config, extraAdapterInfo, bidderName}, + adapterInfo: adapterInfo{config, extraAdapterInfo, bidderName, template}, bidderParamsConfig: g_bidderParamsConfig, }, nil } // MakeRequests prepares oRTB bidder-specific request information using which prebid server make call(s) to bidder. -func (o *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - if request == nil || requestInfo == nil { - return nil, []error{fmt.Errorf("Found either nil request or nil requestInfo")} +func (o *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if bidRequest == nil { + return nil, []error{newBadInputError("found nil request")} } if o.bidderParamsConfig == nil { - return nil, []error{fmt.Errorf("Found nil bidderParamsConfig")} + return nil, []error{newBadInputError("found nil bidderParamsConfig")} } - var errs []error - adapterInfo := o.adapterInfo - requestParams, _ := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) - - // bidder request supports single impression in single HTTP call. - if adapterInfo.extraInfo.RequestMode == RequestModeSingle { - requestData := make([]*adapters.RequestData, 0, len(request.Imp)) - requestCopy := *request - for _, imp := range request.Imp { - requestCopy.Imp = []openrtb2.Imp{imp} // requestCopy contains single impression - reqData, err := adapterInfo.makeRequest(&requestCopy, requestParams) - if err != nil { - errs = append(errs, err) - continue - } - requestData = append(requestData, reqData) - } - return requestData, errs + rawRequest, err := jsonutil.Marshal(bidRequest) + if err != nil { + return nil, []error{newBadInputError(fmt.Sprintf("failed to marshal request, err:%s", err.Error()))} } - // bidder request supports multi impressions in single HTTP call. - requestData, err := adapterInfo.makeRequest(request, requestParams) + var request map[string]any + err = jsonutil.Unmarshal(rawRequest, &request) if err != nil { - return nil, []error{err} + return nil, []error{newBadInputError(fmt.Sprintf("failed to unmarshal request, err:%s", err.Error()))} + } + var ( + requestData []*adapters.RequestData + errs []error + ) + requestParams, _ := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) + switch o.adapterInfo.extraInfo.RequestMode { + case requestModeSingle: + requestData, errs = o.adapterInfo.makeRequestPerImp(request, requestParams) + default: + requestData, errs = o.adapterInfo.makeRequestForAllImps(request, requestParams) } - return []*adapters.RequestData{requestData}, nil + return requestData, errs } // MakeBids prepares bidderResponse from the oRTB bidder server's http.Response @@ -127,7 +201,7 @@ func (o *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R } var response openrtb2.BidResponse - if err := json.Unmarshal(responseData.Body, &response); err != nil { + if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil { return nil, []error{err} } diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go index b951efc4086..efe6a027ea9 100644 --- a/adapters/ortbbidder/ortbbidder_test.go +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -2,9 +2,11 @@ package ortbbidder import ( "encoding/json" + "errors" "fmt" "net/http" "testing" + "text/template" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" @@ -18,10 +20,10 @@ import ( func TestMakeRequests(t *testing.T) { type args struct { - request *openrtb2.BidRequest - requestInfo *adapters.ExtraRequestInfo - adapterInfo adapterInfo - bidderCfgMap *bidderparams.BidderConfig + request *openrtb2.BidRequest + requestInfo *adapters.ExtraRequestInfo + adapterInfo adapterInfo + bidderCfg *bidderparams.BidderConfig } type want struct { requestData []*adapters.RequestData @@ -35,49 +37,53 @@ func TestMakeRequests(t *testing.T) { { name: "request_is_nil", args: args{ - bidderCfgMap: &bidderparams.BidderConfig{}, + bidderCfg: &bidderparams.BidderConfig{}, }, want: want{ - errors: []error{fmt.Errorf("Found either nil request or nil requestInfo")}, + errors: []error{newBadInputError("found nil request")}, }, }, { - name: "requestInfo_is_nil", + name: "bidderParamsConfig_is_nil", args: args{ - bidderCfgMap: &bidderparams.BidderConfig{}, + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, + }, + adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: "single"}, "testbidder", nil}, + bidderCfg: nil, }, want: want{ - errors: []error{fmt.Errorf("Found either nil request or nil requestInfo")}, + errors: []error{newBadInputError("found nil bidderParamsConfig")}, }, }, { - name: "multi_requestmode_to_form_requestdata", + name: "bidderParamsConfig_not_contains_bidder_param_data", args: args{ request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{ - {ID: "imp1", TagID: "tag1"}, - {ID: "imp2", TagID: "tag2"}, - }, - }, - requestInfo: &adapters.ExtraRequestInfo{ - BidderCoreName: openrtb_ext.BidderName("ortb_test_multi_requestmode"), + ID: "reqid", + Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, }, - adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: ""}, "testbidder"}, - bidderCfgMap: &bidderparams.BidderConfig{}, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, }, }, + errors: nil, }, }, { @@ -90,11 +96,12 @@ func TestMakeRequests(t *testing.T) { {ID: "imp2", TagID: "tag2"}, }, }, - requestInfo: &adapters.ExtraRequestInfo{ - BidderCoreName: openrtb_ext.BidderName("ortb_test_single_requestmode"), - }, - adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: "single"}, "testbidder"}, - bidderCfgMap: &bidderparams.BidderConfig{}, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, }, want: want{ requestData: []*adapters.RequestData{ @@ -120,26 +127,111 @@ func TestMakeRequests(t *testing.T) { }, }, { - name: "biddersConfigMap_is_nil", + name: "single_requestmode_validate_endpoint_macro", args: args{ request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://{{.host}}" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, + { + name: "multi_requestmode_to_form_requestdata", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, }, - requestInfo: &adapters.ExtraRequestInfo{ - BidderCoreName: openrtb_ext.BidderName("ortb_test_single_requestmode"), + }, + }, + { + name: "multi_requestmode_validate_endpoint_macros", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, + {ID: "imp2", TagID: "tag2"}, + }, }, - adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: "single"}, "testbidder"}, - bidderCfgMap: nil, + adapterInfo: func() adapterInfo { + endpoint := "http://{{.host}}" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, }, want: want{ - errors: []error{fmt.Errorf("Found nil bidderParamsConfig")}, + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - adapter := &adapter{adapterInfo: tt.args.adapterInfo, bidderParamsConfig: tt.args.bidderCfgMap} + adapter := &adapter{adapterInfo: tt.args.adapterInfo, bidderParamsConfig: tt.args.bidderCfg} requestData, errors := adapter.MakeRequests(tt.args.request, tt.args.requestInfo) assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") assert.Equalf(t, tt.want.errors, errors, "mismatched errors") @@ -363,76 +455,6 @@ func TestJsonSamplesForMultiRequestMode(t *testing.T) { adapterstest.RunJSONBidderTest(t, "ortbbiddertest/owortb_generic_multi_requestmode", bidder) } -func Test_makeRequest(t *testing.T) { - type fields struct { - Adapter config.Adapter - } - type args struct { - request *openrtb2.BidRequest - requestParams map[string]bidderparams.BidderParamMapper - } - type want struct { - requestData *adapters.RequestData - err error - } - tests := []struct { - name string - fields fields - args args - want want - }{ - { - name: "valid_request", - fields: fields{ - Adapter: config.Adapter{Endpoint: "https://example.com"}, - }, - args: args{ - request: &openrtb2.BidRequest{ - ID: "123", - Imp: []openrtb2.Imp{{ID: "imp1"}}, - }, - requestParams: make(map[string]bidderparams.BidderParamMapper), - }, - want: want{ - requestData: &adapters.RequestData{ - Method: http.MethodPost, - Uri: "https://example.com", - Body: []byte(`{"id":"123","imp":[{"id":"imp1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - err: nil, - }, - }, - { - name: "nil_request", - fields: fields{ - Adapter: config.Adapter{Endpoint: "https://example.com"}, - }, - args: args{ - request: nil, - requestParams: make(map[string]bidderparams.BidderParamMapper), - }, - want: want{ - requestData: nil, - err: fmt.Errorf("found nil request"), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - o := adapterInfo{ - Adapter: tt.fields.Adapter, - } - got, err := o.makeRequest(tt.args.request, tt.args.requestParams) - assert.Equal(t, tt.want.requestData, got, "mismatched requestData") - assert.Equal(t, tt.want.err, err, "mismatched error") - }) - } -} - func TestBuilder(t *testing.T) { InitBidderParamsConfig("../../static/bidder-params") type args struct { @@ -460,7 +482,22 @@ func TestBuilder(t *testing.T) { }, want: want{ bidder: nil, - err: fmt.Errorf("Failed to parse extra_info for bidder:[ortbbidder] err:[invalid character 'i' looking for beginning of value]"), + err: fmt.Errorf("failed to parse extra_info: expect { or n, but found i"), + }, + }, + { + name: "fails_to_parse_template_endpoint", + args: args{ + bidderName: "ortbbidder", + config: config.Adapter{ + ExtraAdapterInfo: "{}", + Endpoint: "http://{{.Host}", + }, + server: config.Server{}, + }, + want: want{ + bidder: nil, + err: fmt.Errorf("failed to parse endpoint url template: template: endpointTemplate:1: bad character U+007D '}'"), }, }, { @@ -482,6 +519,10 @@ func TestBuilder(t *testing.T) { ExtraAdapterInfo: `{"requestMode":"single"}`, }, bidderName: "ortbbidder", + endpointTemplate: func() *template.Template { + template, _ := template.New("endpointTemplate").Parse("") + return template + }(), }, bidderParamsConfig: g_bidderParamsConfig, }, @@ -504,6 +545,10 @@ func TestBuilder(t *testing.T) { ExtraAdapterInfo: ``, }, bidderName: "ortbbidder", + endpointTemplate: func() *template.Template { + template, _ := template.New("endpointTemplate").Parse("") + return template + }(), }, bidderParamsConfig: g_bidderParamsConfig, }, @@ -576,3 +621,757 @@ func TestIsORTBBidder(t *testing.T) { }) } } + +func TestMakeRequestForAllImps(t *testing.T) { + type fields struct { + endpointTemplate *template.Template + } + type args struct { + request map[string]any + bidderParamMapper map[string]bidderparams.BidderParamMapper + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "no_imp_object", + fields: fields{}, + args: args{ + request: map[string]any{}, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp object found in request")}, + }, + }, + { + name: "invalid_imp_object", + fields: fields{}, + args: args{ + request: map[string]any{ + "imp": []any{"invalid"}, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "", + Body: json.RawMessage(`{"imp":["invalid"]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: []error{newBadInputError("invalid imp object found at index:0")}, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macros_value_absent_in_bidder_params", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{}, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/", + Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macro_replacement_failure", + fields: fields{ + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{}, + }, + }, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to form endpoint url, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + }, + }, + }, + map[string]any{ + "id": "imp_2", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "imp2.host.com", + }, + }, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "map_bidder_params_in_single_imp", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "map_bidder_params_in_multi_imp", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + map[string]any{ + "id": "imp_2", + "ext": map[string]any{ + "bidder": map[string]any{ + "tagid": "valid_tag_id", + }, + }, + }, + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + tagMapper := bidderparams.BidderParamMapper{} + tagMapper.SetLocation([]string{"imp", "tagid"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + "tagid": tagMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "first_imp_bidder_param_has_high_pririty", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + map[string]any{ + "id": "imp_2", + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + }, + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "bidder_param_mapping_absent", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + bidderParamMapper: nil, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := adapterInfo{ + endpointTemplate: tt.fields.endpointTemplate, + } + requestData, errs := o.makeRequestForAllImps(tt.args.request, tt.args.bidderParamMapper) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} + +func TestMakeRequestPerImp(t *testing.T) { + type fields struct { + endpointTemplate *template.Template + } + type args struct { + request map[string]any + bidderParamMapper map[string]bidderparams.BidderParamMapper + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "no_imp_object", + fields: fields{}, + args: args{ + request: map[string]any{}, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp object found in request")}, + }, + }, + { + name: "invalid_imp_object", + fields: fields{}, + args: args{ + request: map[string]any{ + "imp": []any{"invalid"}, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp object found at index:0")}, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macros_value_absent_in_bidder_params", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{}, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/", + Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macro_replacement_failure", + fields: fields{ + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{}, + }, + }, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to form endpoint url for imp at index:0, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "single_imp_request", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "multi_imps_request", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "imp1.host.com", + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + }, + map[string]any{ + "id": "imp_2", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "imp2.host.com", + "ext": map[string]any{ + "pubid": 2222, + }, + }, + }, + }, + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "multi_imps_request_with_one_invalid_imp", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "imp1.host.com", + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + }, + "invalid-imp", + }, + }, + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation([]string{"host"}) + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation([]string{"device"}) + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: []error{newBadInputError("invalid imp object found at index:1")}, + }, + }, + { + name: "bidder_param_mapping_absent", + fields: fields{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + args: args{ + request: map[string]any{ + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "host": "localhost.com", + "ext": map[string]any{ + "pubid": 5890, + }, + }, + }, + }, + }, + }, + bidderParamMapper: nil, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := adapterInfo{ + endpointTemplate: tt.fields.endpointTemplate, + } + requestData, errs := o.makeRequestPerImp(tt.args.request, tt.args.bidderParamMapper) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} diff --git a/adapters/ortbbidder/requestParamMapper.go b/adapters/ortbbidder/requestParamMapper.go index 013db98c7c9..27633711e88 100644 --- a/adapters/ortbbidder/requestParamMapper.go +++ b/adapters/ortbbidder/requestParamMapper.go @@ -1,71 +1,33 @@ package ortbbidder import ( - "encoding/json" - "fmt" - "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" ) -const ( - impKey = "imp" - extKey = "ext" - bidderKey = "bidder" - appsiteKey = "appsite" - siteKey = "site" - appKey = "app" -) - -// setRequestParams updates the requestBody based on the requestParams mapping details. -func setRequestParams(requestBody []byte, requestParams map[string]bidderparams.BidderParamMapper) ([]byte, error) { - if len(requestParams) == 0 { - return requestBody, nil - } - request := map[string]any{} - err := json.Unmarshal(requestBody, &request) - if err != nil { - return nil, err - } - imps, ok := request[impKey].([]any) - if !ok { - return nil, fmt.Errorf("error:[invalid_imp_found_in_requestbody], imp:[%v]", request[impKey]) - } - updatedRequest := false - for ind, imp := range imps { - request[impKey] = imp - imp, ok := imp.(map[string]any) - if !ok { - return nil, fmt.Errorf("error:[invalid_imp_found_in_implist], imp:[%v]", request[impKey]) - } - ext, ok := imp[extKey].(map[string]any) +// setRequestParams updates the request and imp object by mapping bidderParams at expected location. +func setRequestParams(request, imp, bidderParams map[string]any, paramsMapper map[string]bidderparams.BidderParamMapper) { + for paramName, paramValue := range bidderParams { + paramMapper, ok := paramsMapper[paramName] if !ok { continue } - bidderParams, ok := ext[bidderKey].(map[string]any) - if !ok { - continue - } - for paramName, paramValue := range bidderParams { - paramMapper, ok := requestParams[paramName] - if !ok { - continue - } - // set the value in the request according to the mapping details and remove the parameter. - if setValue(request, paramMapper.GetLocation(), paramValue) { - delete(bidderParams, paramName) - updatedRequest = true - } + // set the value in the request according to the mapping details + // remove the parameter from bidderParams after successful mapping + if setValue(request, imp, paramMapper.GetLocation(), paramValue) { + delete(bidderParams, paramName) } - imps[ind] = request[impKey] } - // update the impression list in the request - request[impKey] = imps - // if the request was modified, marshal it back to JSON. - if updatedRequest { - requestBody, err = json.Marshal(request) - if err != nil { - return nil, fmt.Errorf("error:[fail_to_update_request_body] msg:[%s]", err.Error()) - } +} + +// getImpExtBidderParams returns imp.ext.bidder parameters +func getImpExtBidderParams(imp map[string]any) map[string]any { + ext, ok := imp[extKey].(map[string]any) + if !ok { + return nil + } + bidderParams, ok := ext[bidderKey].(map[string]any) + if !ok { + return nil } - return requestBody, nil + return bidderParams } diff --git a/adapters/ortbbidder/requestParamMapper_test.go b/adapters/ortbbidder/requestParamMapper_test.go index 3aad8961e4b..46d547c3eaa 100644 --- a/adapters/ortbbidder/requestParamMapper_test.go +++ b/adapters/ortbbidder/requestParamMapper_test.go @@ -1,7 +1,6 @@ package ortbbidder import ( - "encoding/json" "testing" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" @@ -10,12 +9,15 @@ import ( func TestSetRequestParams(t *testing.T) { type args struct { - requestBody []byte - mapper map[string]bidderparams.BidderParamMapper + request map[string]any + imp map[string]any + bidderParams map[string]any + paramsMapper map[string]bidderparams.BidderParamMapper } type want struct { - err string - requestBody []byte + request map[string]any + imp map[string]any + bidderParams map[string]any } tests := []struct { name string @@ -23,235 +25,158 @@ func TestSetRequestParams(t *testing.T) { want want }{ { - name: "empty_mapper", + name: "bidder_param_missing", args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{}}}]}`), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{}}}]}`), - }, - }, - { - name: "nil_requestbody", - args: args{ - requestBody: nil, - mapper: map[string]bidderparams.BidderParamMapper{ - "adunit": {}, + request: map[string]any{ + "id": "req_1", }, - }, - want: want{ - err: "unexpected end of JSON input", - }, - }, - { - name: "requestbody_has_invalid_imps", - args: args{ - requestBody: json.RawMessage(`{"imp":{"id":"1"}}`), - mapper: map[string]bidderparams.BidderParamMapper{ - "adunit": {}, + imp: map[string]any{ + "id": "imp_1", }, - }, - want: want{ - err: "error:[invalid_imp_found_in_requestbody], imp:[map[id:1]]", - }, - }, - { - name: "missing_imp_ext", - args: args{ - requestBody: json.RawMessage(`{"imp":[{}]}`), - mapper: map[string]bidderparams.BidderParamMapper{ - "adunit": {}, + bidderParams: map[string]any{ + "param": "value", }, + paramsMapper: nil, }, want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{}]}`), - }, - }, - { - name: "missing_bidder_in_imp_ext", - args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{}}]}`), - mapper: map[string]bidderparams.BidderParamMapper{ - "adunit": {}, + request: map[string]any{ + "id": "req_1", }, - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{}}]}`), - }, - }, - { - name: "missing_bidderparams_in_imp_ext", - args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{}}}]}`), - mapper: map[string]bidderparams.BidderParamMapper{ - "adunit": {}, + imp: map[string]any{ + "id": "imp_1", + }, + bidderParams: map[string]any{ + "param": "value", }, - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{}}}]}`), - }, - }, - { - name: "mapper_not_contains_bidder_param_location", - args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"ext"}) - return map[string]bidderparams.BidderParamMapper{ - "slot": bpm, - } - }(), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123}}}]}`), - }, - }, - { - name: "mapper_contains_bidder_param_location", - args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"ext", "adunit"}) - return map[string]bidderparams.BidderParamMapper{ - "adunit": bpm, - } - }(), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"ext":{"adunit":123},"imp":[{"ext":{"bidder":{}}}]}`), }, }, { - name: "do_not_delete_bidder_param_if_failed_to_set_value", + name: "request_level_param_set_successfully", args: args{ - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"req", "", ""}) + request: map[string]any{ + "id": "req_1", + }, + imp: map[string]any{ + "id": "imp_1", + }, + bidderParams: map[string]any{ + "param": "value", + }, + paramsMapper: func() map[string]bidderparams.BidderParamMapper { + mapper := bidderparams.BidderParamMapper{} + mapper.SetLocation([]string{"param"}) return map[string]bidderparams.BidderParamMapper{ - "adunit": bpm, + "param": mapper, } }(), }, want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123}}}]}`), + request: map[string]any{ + "param": "value", + "id": "req_1", + }, + imp: map[string]any{ + "id": "imp_1", + }, + bidderParams: map[string]any{}, }, }, { - name: "set_multiple_bidder_params", + name: "imp_level_param_set_successfully", args: args{ - requestBody: json.RawMessage(`{"app":{"name":"sampleapp"},"imp":[{"tagid":"oldtagid","ext":{"bidder":{"paramWithoutLocation":"value","adunit":123,"slot":"test_slot","wrapper":{"pubid":5890,"profile":1}}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - adunit := bidderparams.BidderParamMapper{} - adunit.SetLocation([]string{"adunit", "id"}) - slot := bidderparams.BidderParamMapper{} - slot.SetLocation([]string{"imp", "tagid"}) - wrapper := bidderparams.BidderParamMapper{} - wrapper.SetLocation([]string{"app", "ext"}) + request: map[string]any{ + "id": "req_1", + }, + imp: map[string]any{ + "id": "imp_1", + }, + bidderParams: map[string]any{ + "param": "value", + }, + paramsMapper: func() map[string]bidderparams.BidderParamMapper { + mapper := bidderparams.BidderParamMapper{} + mapper.SetLocation([]string{"imp", "param"}) return map[string]bidderparams.BidderParamMapper{ - "adunit": adunit, - "slot": slot, - "wrapper": wrapper, + "param": mapper, } }(), }, want: want{ - err: "", - requestBody: json.RawMessage(`{"adunit":{"id":123},"app":{"ext":{"profile":1,"pubid":5890},"name":"sampleapp"},"imp":[{"ext":{"bidder":{"paramWithoutLocation":"value"}},"tagid":"test_slot"}]}`), + request: map[string]any{ + "id": "req_1", + }, + imp: map[string]any{ + "param": "value", + "id": "imp_1", + }, + bidderParams: map[string]any{}, }, }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setRequestParams(tt.args.request, tt.args.imp, tt.args.bidderParams, tt.args.paramsMapper) + assert.Equal(t, tt.want.bidderParams, tt.args.bidderParams, "mismatched bidderparams") + assert.Equal(t, tt.want.imp, tt.args.imp, "mismatched imp") + assert.Equal(t, tt.want.request, tt.args.request, "mismatched request") + }) + } +} + +func TestGetImpExtBidderParams(t *testing.T) { + type args struct { + imp map[string]any + } + tests := []struct { + name string + args args + want map[string]any + }{ { - name: "conditional_mapping_set_app_object", + name: "ext_key_absent_in_imp", args: args{ - requestBody: json.RawMessage(`{"app":{"name":"sampleapp"},"imp":[{"tagid":"oldtagid","ext":{"bidder":{"paramWithoutLocation":"value","adunit":123,"slot":"test_slot","wrapper":{"pubid":5890,"profile":1}}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"appsite", "wrapper"}) - return map[string]bidderparams.BidderParamMapper{ - "wrapper": bpm, - } - }(), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"app":{"name":"sampleapp","wrapper":{"profile":1,"pubid":5890}},"imp":[{"ext":{"bidder":{"adunit":123,"paramWithoutLocation":"value","slot":"test_slot"}},"tagid":"oldtagid"}]}`), + imp: map[string]any{}, }, + want: nil, }, { - name: "conditional_mapping_set_site_object", + name: "invalid_ext_key_in_imp", args: args{ - requestBody: json.RawMessage(`{"site":{"name":"sampleapp"},"imp":[{"tagid":"oldtagid","ext":{"bidder":{"paramWithoutLocation":"value","adunit":123,"slot":"test_slot","wrapper":{"pubid":5890,"profile":1}}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"appsite", "wrapper"}) - return map[string]bidderparams.BidderParamMapper{ - "wrapper": bpm, - } - }(), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"imp":[{"ext":{"bidder":{"adunit":123,"paramWithoutLocation":"value","slot":"test_slot"}},"tagid":"oldtagid"}],"site":{"name":"sampleapp","wrapper":{"profile":1,"pubid":5890}}}`), + imp: map[string]any{ + "ext": "invalid", + }, }, + want: nil, }, { - name: "multi_imps_bidder_params_mapping", + name: "bidder_key_absent_in_imp_ext", args: args{ - requestBody: json.RawMessage(`{"app":{"name":"sampleapp"},"imp":[{"tagid":"tagid_1","ext":{"bidder":{"paramWithoutLocation":"value","adunit":111,"slot":"test_slot_1","wrapper":{"pubid":5890,"profile":1}}}},{"tagid":"tagid_2","ext":{"bidder":{"slot":"test_slot_2","adunit":222}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - adunit := bidderparams.BidderParamMapper{} - adunit.SetLocation([]string{"adunit", "id"}) - slot := bidderparams.BidderParamMapper{} - slot.SetLocation([]string{"imp", "tagid"}) - wrapper := bidderparams.BidderParamMapper{} - wrapper.SetLocation([]string{"app", "ext"}) - return map[string]bidderparams.BidderParamMapper{ - "adunit": adunit, - "slot": slot, - "wrapper": wrapper, - } - }(), - }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"adunit":{"id":222},"app":{"ext":{"profile":1,"pubid":5890},"name":"sampleapp"},"imp":[{"ext":{"bidder":{"paramWithoutLocation":"value"}},"tagid":"test_slot_1"},{"ext":{"bidder":{}},"tagid":"test_slot_2"}]}`), + imp: map[string]any{ + "ext": map[string]any{}, + }, }, + want: nil, }, { - name: "multi_imps_bidder_params_mapping_override_if_same_param_present", + name: "bidder_key_present_in_imp_ext", args: args{ - requestBody: json.RawMessage(`{"app":{"name":"sampleapp"},"imp":[{"tagid":"tagid_1","ext":{"bidder":{"paramWithoutLocation":"value","adunit":111}}},{"tagid":"tagid_2","ext":{"bidder":{"adunit":222}}}]}`), - mapper: func() map[string]bidderparams.BidderParamMapper { - bpm := bidderparams.BidderParamMapper{} - bpm.SetLocation([]string{"adunit", "id"}) - return map[string]bidderparams.BidderParamMapper{ - "adunit": bpm, - } - }(), + imp: map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "param": "value", + }, + }, + }, }, - want: want{ - err: "", - requestBody: json.RawMessage(`{"adunit":{"id":222},"app":{"name":"sampleapp"},"imp":[{"ext":{"bidder":{"paramWithoutLocation":"value"}},"tagid":"tagid_1"},{"ext":{"bidder":{}},"tagid":"tagid_2"}]}`), + want: map[string]any{ + "param": "value", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := setRequestParams(tt.args.requestBody, tt.args.mapper) - assert.Equal(t, string(tt.want.requestBody), string(got), "mismatched request body") - assert.Equal(t, len(tt.want.err) == 0, err == nil, "mismatched error") - if err != nil { - assert.Equal(t, err.Error(), tt.want.err, "mismatched error string") - } + got := getImpExtBidderParams(tt.args.imp) + assert.Equal(t, tt.want, got, "mismatched bidder-params") }) } } diff --git a/adapters/ortbbidder/util.go b/adapters/ortbbidder/util.go index 5b2dccc67bf..78e1bb18b32 100644 --- a/adapters/ortbbidder/util.go +++ b/adapters/ortbbidder/util.go @@ -1,5 +1,7 @@ package ortbbidder +import "github.com/prebid/prebid-server/v2/errortypes" + /* setValue updates or creates a value in a node based on a specified location. The location is a string that specifies a path through the node hierarchy, @@ -14,13 +16,13 @@ Arguments: Example: - location = imp.ext.adunitid; value = 123 ==> {"imp": {"ext" : {"adunitid":123}}} */ -func setValue(node map[string]any, locations []string, value any) bool { +func setValue(requestNode, impNode map[string]any, locations []string, value any) bool { if value == nil || len(locations) == 0 { return false } lastNodeIndex := len(locations) - 1 - currentNode := node + currentNode := requestNode for index, loc := range locations { if len(loc) == 0 { // if location part is empty string @@ -30,7 +32,7 @@ func setValue(node map[string]any, locations []string, value any) bool { currentNode[loc] = value break } - nextNode := getNode(currentNode, loc) + nextNode := getNode(currentNode, impNode, loc) // not the last part, navigate deeper if nextNode == nil { // loc does not exist, set currentNode to a new node @@ -49,15 +51,24 @@ func setValue(node map[string]any, locations []string, value any) bool { return true } -// getNode retrieves the value for a given key from a map with special handling for the "appsite" key -func getNode(nodes map[string]any, key string) any { +// getNode retrieves the value for a given key from a map with special handling for the "appsite", "imp" key +func getNode(requestNode, impNode map[string]any, key string) any { switch key { case appsiteKey: // if key is "appsite" and if nodes contains "site" object then return nodes["site"] else return nodes["app"] - if value, ok := nodes[siteKey]; ok { + if value, ok := requestNode[siteKey]; ok { return value } - return nodes[appKey] + return requestNode[appKey] + case impKey: + return impNode + } + return requestNode[key] +} + +// newBadInputError returns the error of type bad-input +func newBadInputError(message string) error { + return &errortypes.BadInput{ + Message: message, } - return nodes[key] } diff --git a/adapters/ortbbidder/util_test.go b/adapters/ortbbidder/util_test.go index 059303f6dfb..319ef0eb44f 100644 --- a/adapters/ortbbidder/util_test.go +++ b/adapters/ortbbidder/util_test.go @@ -8,13 +8,15 @@ import ( func TestSetValue(t *testing.T) { type args struct { - node map[string]any - location []string - value any + requestNode map[string]any + impNode map[string]any + location []string + value any } type want struct { node map[string]any status bool + imp map[string]any } tests := []struct { name string @@ -24,9 +26,9 @@ func TestSetValue(t *testing.T) { { name: "set_nil_value", args: args{ - node: map[string]any{}, - location: []string{"key"}, - value: nil, + requestNode: map[string]any{}, + location: []string{"key"}, + value: nil, }, want: want{ status: false, @@ -36,9 +38,9 @@ func TestSetValue(t *testing.T) { { name: "set_value_in_empty_location", args: args{ - node: map[string]any{}, - location: []string{}, - value: 123, + requestNode: map[string]any{}, + location: []string{}, + value: 123, }, want: want{ status: false, @@ -48,9 +50,9 @@ func TestSetValue(t *testing.T) { { name: "set_value_in_invalid_location_modifies_node", args: args{ - node: map[string]any{}, - location: []string{"key", ""}, - value: 123, + requestNode: map[string]any{}, + location: []string{"key", ""}, + value: 123, }, want: want{ status: false, @@ -62,9 +64,9 @@ func TestSetValue(t *testing.T) { { name: "set_value_at_root_level_in_empty_node", args: args{ - node: map[string]any{}, - location: []string{"key"}, - value: 123, + requestNode: map[string]any{}, + location: []string{"key"}, + value: 123, }, want: want{ status: true, @@ -74,9 +76,9 @@ func TestSetValue(t *testing.T) { { name: "set_value_at_root_level_in_non-empty_node", args: args{ - node: map[string]any{"oldKey": "oldValue"}, - location: []string{"key"}, - value: 123, + requestNode: map[string]any{"oldKey": "oldValue"}, + location: []string{"key"}, + value: 123, }, want: want{ status: true, @@ -86,9 +88,9 @@ func TestSetValue(t *testing.T) { { name: "set_value_at_non-root_level_in_non-json_node", args: args{ - node: map[string]any{"rootKey": "rootValue"}, - location: []string{"rootKey", "key"}, - value: 123, + requestNode: map[string]any{"rootKey": "rootValue"}, + location: []string{"rootKey", "key"}, + value: 123, }, want: want{ status: false, @@ -98,7 +100,7 @@ func TestSetValue(t *testing.T) { { name: "set_value_at_non-root_level_in_json_node", args: args{ - node: map[string]any{"rootKey": map[string]any{ + requestNode: map[string]any{"rootKey": map[string]any{ "oldKey": "oldValue", }}, location: []string{"rootKey", "newKey"}, @@ -115,7 +117,7 @@ func TestSetValue(t *testing.T) { { name: "set_value_at_non-root_level_in_nested-json_node", args: args{ - node: map[string]any{"rootKey": map[string]any{ + requestNode: map[string]any{"rootKey": map[string]any{ "parentKey1": map[string]any{ "innerKey": "innerValue", }, @@ -136,7 +138,7 @@ func TestSetValue(t *testing.T) { { name: "override_existing_key's_value", args: args{ - node: map[string]any{"rootKey": map[string]any{ + requestNode: map[string]any{"rootKey": map[string]any{ "parentKey": map[string]any{ "innerKey": "innerValue", }, @@ -154,7 +156,7 @@ func TestSetValue(t *testing.T) { { name: "appsite_key_app_object_present", args: args{ - node: map[string]any{"app": map[string]any{ + requestNode: map[string]any{"app": map[string]any{ "parentKey": "oldValue", }}, location: []string{"appsite", "parentKey"}, @@ -170,7 +172,7 @@ func TestSetValue(t *testing.T) { { name: "appsite_key_site_object_present", args: args{ - node: map[string]any{"site": map[string]any{ + requestNode: map[string]any{"site": map[string]any{ "parentKey": "oldValue", }}, location: []string{"appsite", "parentKey"}, @@ -183,11 +185,41 @@ func TestSetValue(t *testing.T) { }}, }, }, + { + name: "imp_key_present", + args: args{ + requestNode: map[string]any{"id": map[string]any{ + "id": "req_1", + "imp": map[string]any{ + "id": "imp_1", + }, + }}, + impNode: map[string]any{ + "id": "imp_1", + }, + location: []string{"imp", "ext"}, + value: "value", + }, + want: want{ + status: true, + imp: map[string]any{ + "ext": "value", + "id": "imp_1", + }, + node: map[string]any{"id": map[string]any{ + "id": "req_1", + "imp": map[string]any{ + "id": "imp_1", + }, + }}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := setValue(tt.args.node, tt.args.location, tt.args.value) - assert.Equalf(t, tt.want.node, tt.args.node, "SetValue failed to update node object") + got := setValue(tt.args.requestNode, tt.args.impNode, tt.args.location, tt.args.value) + assert.Equalf(t, tt.want.node, tt.args.requestNode, "SetValue failed to update node object") + assert.Equalf(t, tt.want.imp, tt.args.impNode, "SetValue failed to update imp object") assert.Equalf(t, tt.want.status, got, "SetValue returned invalid status") }) } @@ -195,8 +227,9 @@ func TestSetValue(t *testing.T) { func TestGetNode(t *testing.T) { type args struct { - nodes map[string]any - key string + requestNode map[string]any + impNode map[string]any + key string } tests := []struct { name string @@ -206,7 +239,7 @@ func TestGetNode(t *testing.T) { { name: "appsite_key_present_when_app_object_present", args: args{ - nodes: map[string]any{"app": map[string]any{ + requestNode: map[string]any{"app": map[string]any{ "parentKey": "oldValue", }}, key: "appsite", @@ -216,7 +249,7 @@ func TestGetNode(t *testing.T) { { name: "appsite_key_present_when_site_object_present", args: args{ - nodes: map[string]any{"site": map[string]any{ + requestNode: map[string]any{"site": map[string]any{ "siteKey": "siteValue", }}, key: "appsite", @@ -226,17 +259,35 @@ func TestGetNode(t *testing.T) { { name: "appsite_key_absent", args: args{ - nodes: map[string]any{"device": map[string]any{ + requestNode: map[string]any{"device": map[string]any{ "deviceKey": "deviceVal", }}, key: "appsite", }, want: nil, }, + { + name: "imp_key_present", + args: args{ + requestNode: map[string]any{ + "device": map[string]any{ + "deviceKey": "deviceVal", + }, + "imp": []string{}, + }, + impNode: map[string]any{ + "id": "imp_1", + }, + key: "imp", + }, + want: map[string]any{ + "id": "imp_1", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - node := getNode(tt.args.nodes, tt.args.key) + node := getNode(tt.args.requestNode, tt.args.impNode, tt.args.key) assert.Equal(t, tt.want, node) }) } diff --git a/config/bidderinfo.go b/config/bidderinfo.go index d78f5722552..68e6a10a72e 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -426,6 +426,10 @@ func validateAdapterEndpoint(endpoint string, bidderName string, errs []error) [ if err != nil { return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, bidderName, err)) } + // OW specific : do not perform endpoint validation if bidder is an oRTB bidder + if strings.HasPrefix(bidderName, "owortb_") { + return nil + } // Resolve macros (if any) in the endpoint URL resolvedEndpoint, err := macros.ResolveMacros(endpointTemplate, testEndpointTemplateParams) if err != nil { diff --git a/exchange/exchange.go b/exchange/exchange.go index 2e42e685bf2..02afe83a620 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -759,7 +759,6 @@ func (e *exchange) getAllBids( reqInfo := adapters.NewExtraRequestInfo(conversions) reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader - reqInfo.BidderCoreName = bidderRequest.BidderCoreName // OW specific: required for oRTB bidder bidReqOptions := bidRequestOptions{ accountDebugAllowed: accountDebugAllowed, diff --git a/static/bidder-params/owortb_testbidder.json b/static/bidder-params/owortb_testbidder.json index b0ce3c53aed..01cafb99f2f 100644 --- a/static/bidder-params/owortb_testbidder.json +++ b/static/bidder-params/owortb_testbidder.json @@ -4,11 +4,61 @@ "description": "A schema which validates params accepted by the testbidder (oRTB Integration)", "type": "object", "properties": { - "adunitID": { + "adunit": { "type": "string", "description": "adunitID param", - "location": "ext.adunit.id" + "location": "id" + }, + "tagid": { + "type": "string", + "description": "tagid param", + "location": "imp.tagid" + }, + "zone": { + "type": "string", + "description": "zone param", + "location": "appsite.id" + }, + "maxduration": { + "type": "integer", + "description": "maxduration param", + "location": "imp.video.maxduration" + }, + "livestream": { + "type": "integer", + "description": "livestream param", + "location": "app.cnt.livestream" + }, + "url": { + "type": "string", + "description": "URL param setting in video-startdelay", + "location": "imp.video.startdelay" + }, + "randomKey": { + "type": "string", + "description": "randomKey param", + "location": "content.data" + }, + "host": { + "type": "string", + "description": "host param", + "location": "ext.server.host" + }, + "wrapper": { + "type": "object", + "description": "Specifies configuration for a publisher", + "properties": { + "profile": { + "type": "integer", + "description": "An ID which identifies the openwrap profile of publisher" + }, + "version": { + "type": "integer", + "description": "An ID which identifies version of the openwrap profile" + } + }, + "location": "device.ext.publisherWrapper" } }, - "required": ["adunitID"] + "required": ["adunit"] } \ No newline at end of file From 0f8cc85fe72646922c5b579c59e3dc06a9190cec Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Thu, 6 Jun 2024 15:21:30 +0530 Subject: [PATCH 2/9] fix review comments --- adapters/ortbbidder/bidderparams/config.go | 23 +- .../ortbbidder/bidderparams/config_test.go | 104 ++-- adapters/ortbbidder/bidderparams/parser.go | 7 +- .../ortbbidder/bidderparams/parser_test.go | 10 +- adapters/ortbbidder/ortbbidder.go | 187 ++++--- adapters/ortbbidder/ortbbidder_test.go | 522 ++++-------------- adapters/ortbbidder/requestParamMapper.go | 31 +- .../ortbbidder/requestParamMapper_test.go | 101 +++- adapters/ortbbidder/util.go | 80 +-- adapters/ortbbidder/util_test.go | 167 +++++- static/bidder-params/owortb_testbidder.json | 8 +- 11 files changed, 592 insertions(+), 648 deletions(-) diff --git a/adapters/ortbbidder/bidderparams/config.go b/adapters/ortbbidder/bidderparams/config.go index 4e57d3b1414..618c4847647 100644 --- a/adapters/ortbbidder/bidderparams/config.go +++ b/adapters/ortbbidder/bidderparams/config.go @@ -1,18 +1,22 @@ package bidderparams +import ( + "fmt" +) + // BidderParamMapper contains property details like location type BidderParamMapper struct { - location []string + location string } // GetLocation returns the location of bidderParam -func (bpm *BidderParamMapper) GetLocation() []string { +func (bpm *BidderParamMapper) GetLocation() string { return bpm.location } // SetLocation sets the location in BidderParamMapper // Do not modify the location of bidderParam unless you are writing unit test case -func (bpm *BidderParamMapper) SetLocation(location []string) { +func (bpm *BidderParamMapper) SetLocation(location string) { bpm.location = location } @@ -28,9 +32,9 @@ type BidderConfig struct { } // setRequestParams sets the bidder specific requestParams -func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) { +func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) error { if bcfg == nil { - return + return fmt.Errorf("BidderConfig is nil") } if bcfg.bidderConfigMap == nil { bcfg.bidderConfigMap = make(map[string]*config) @@ -39,16 +43,17 @@ func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[ bcfg.bidderConfigMap[bidderName] = &config{} } bcfg.bidderConfigMap[bidderName].requestParams = requestParams + return nil } // GetRequestParams returns bidder specific requestParams -func (bcfg *BidderConfig) GetRequestParams(bidderName string) (map[string]BidderParamMapper, bool) { +func (bcfg *BidderConfig) GetRequestParams(bidderName string) map[string]BidderParamMapper { if bcfg == nil || len(bcfg.bidderConfigMap) == 0 { - return nil, false + return nil } bidderConfig := bcfg.bidderConfigMap[bidderName] if bidderConfig == nil { - return nil, false + return nil } - return bidderConfig.requestParams, true + return bidderConfig.requestParams } diff --git a/adapters/ortbbidder/bidderparams/config_test.go b/adapters/ortbbidder/bidderparams/config_test.go index 580689e6bf4..6f01294ffbd 100644 --- a/adapters/ortbbidder/bidderparams/config_test.go +++ b/adapters/ortbbidder/bidderparams/config_test.go @@ -1,6 +1,7 @@ package bidderparams import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -14,12 +15,15 @@ func TestSetRequestParams(t *testing.T) { bidderName string requestParams map[string]BidderParamMapper } - + type want struct { + bidderCfg *BidderConfig + err error + } tests := []struct { name string fields fields args args - want *BidderConfig + want want }{ { name: "bidderConfig_is_nil", @@ -30,11 +34,13 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "adunit": { - location: []string{"ext", "adunit"}, + location: "ext.adunit", }, }, }, - want: nil, + want: want{ + err: fmt.Errorf("BidderConfig is nil"), + }, }, { name: "bidderConfigMap_is_nil", @@ -47,16 +53,18 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "adunit": { - location: []string{"ext", "adunit"}, + location: "ext.adunit", }, }, }, - want: &BidderConfig{ - bidderConfigMap: map[string]*config{ - "test": { - requestParams: map[string]BidderParamMapper{ - "adunit": { - location: []string{"ext", "adunit"}, + want: want{ + bidderCfg: &BidderConfig{ + bidderConfigMap: map[string]*config{ + "test": { + requestParams: map[string]BidderParamMapper{ + "adunit": { + location: "ext.adunit", + }, }, }, }, @@ -74,16 +82,18 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "param-1": { - location: []string{"path"}, + location: "path", }, }, }, - want: &BidderConfig{ - bidderConfigMap: map[string]*config{ - "test": { - requestParams: map[string]BidderParamMapper{ - "param-1": { - location: []string{"path"}, + want: want{ + bidderCfg: &BidderConfig{ + bidderConfigMap: map[string]*config{ + "test": { + requestParams: map[string]BidderParamMapper{ + "param-1": { + location: "path", + }, }, }, }, @@ -98,7 +108,7 @@ func TestSetRequestParams(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-1": { - location: []string{"path-1"}, + location: "path-1", }, }, }, @@ -109,16 +119,18 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "param-2": { - location: []string{"path-2"}, + location: "path-2", }, }, }, - want: &BidderConfig{ - bidderConfigMap: map[string]*config{ - "test": { - requestParams: map[string]BidderParamMapper{ - "param-2": { - location: []string{"path-2"}, + want: want{ + bidderCfg: &BidderConfig{ + bidderConfigMap: map[string]*config{ + "test": { + requestParams: map[string]BidderParamMapper{ + "param-2": { + location: "path-2", + }, }, }, }, @@ -128,8 +140,9 @@ func TestSetRequestParams(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.fields.bidderConfig.setRequestParams(tt.args.bidderName, tt.args.requestParams) - assert.Equal(t, tt.want, tt.fields.bidderConfig, "mismatched bidderConfig") + err := tt.fields.bidderConfig.setRequestParams(tt.args.bidderName, tt.args.requestParams) + assert.Equal(t, tt.want.bidderCfg, tt.fields.bidderConfig, "mismatched bidderConfig") + assert.Equal(t, tt.want.err, err, "mismatched error") }) } } @@ -143,7 +156,6 @@ func TestGetBidderRequestProperties(t *testing.T) { } type want struct { requestParams map[string]BidderParamMapper - found bool } tests := []struct { name string @@ -161,7 +173,6 @@ func TestGetBidderRequestProperties(t *testing.T) { }, want: want{ requestParams: nil, - found: false, }, }, { @@ -176,7 +187,6 @@ func TestGetBidderRequestProperties(t *testing.T) { }, want: want{ requestParams: nil, - found: false, }, }, { @@ -193,7 +203,6 @@ func TestGetBidderRequestProperties(t *testing.T) { }, want: want{ requestParams: nil, - found: false, }, }, { @@ -210,7 +219,6 @@ func TestGetBidderRequestProperties(t *testing.T) { }, want: want{ requestParams: nil, - found: false, }, }, { @@ -221,7 +229,7 @@ func TestGetBidderRequestProperties(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-1": { - location: []string{"value-1"}, + location: "value-1", }, }, }, @@ -234,18 +242,16 @@ func TestGetBidderRequestProperties(t *testing.T) { want: want{ requestParams: map[string]BidderParamMapper{ "param-1": { - location: []string{"value-1"}, + location: "value-1", }, }, - found: true, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - params, found := tt.fields.biddersConfig.GetRequestParams(tt.args.bidderName) + params := tt.fields.biddersConfig.GetRequestParams(tt.args.bidderName) assert.Equal(t, tt.want.requestParams, params, "mismatched requestParams") - assert.Equal(t, tt.want.found, found, "mismatched found value") }) } } @@ -254,21 +260,21 @@ func TestBidderParamMapperGetLocation(t *testing.T) { tests := []struct { name string bpm BidderParamMapper - want []string + want string }{ { name: "location_is_nil", bpm: BidderParamMapper{ - location: nil, + location: "", }, - want: nil, + want: "", }, { name: "location_is_non_empty", bpm: BidderParamMapper{ - location: []string{"req", "ext"}, + location: "req.ext", }, - want: []string{"req", "ext"}, + want: "req.ext", }, } for _, tt := range tests { @@ -281,7 +287,7 @@ func TestBidderParamMapperGetLocation(t *testing.T) { func TestBidderParamMapperSetLocation(t *testing.T) { type args struct { - location []string + location string } tests := []struct { name string @@ -293,22 +299,22 @@ func TestBidderParamMapperSetLocation(t *testing.T) { name: "set_location", bpm: BidderParamMapper{}, args: args{ - location: []string{"req", "ext"}, + location: "req.ext", }, want: BidderParamMapper{ - location: []string{"req", "ext"}, + location: "req.ext", }, }, { name: "override_location", bpm: BidderParamMapper{ - location: []string{"imp", "ext"}, + location: "imp.ext", }, args: args{ - location: []string{"req", "ext"}, + location: "req.ext", }, want: BidderParamMapper{ - location: []string{"req", "ext"}, + location: "req.ext", }, }, } diff --git a/adapters/ortbbidder/bidderparams/parser.go b/adapters/ortbbidder/bidderparams/parser.go index 8d198464fcb..4dec0a94baf 100644 --- a/adapters/ortbbidder/bidderparams/parser.go +++ b/adapters/ortbbidder/bidderparams/parser.go @@ -36,7 +36,10 @@ func LoadBidderConfig(dirPath string, isBidderAllowed func(string) bool) (*Bidde if err != nil { return nil, err } - bidderConfigMap.setRequestParams(bidderName, requestParams) + err = bidderConfigMap.setRequestParams(bidderName, requestParams) + if err != nil { + return nil, fmt.Errorf("error:[fail_to_set_request_param] dir:[%s] filename:[%s] err:[%s]", dirPath, file.Name(), err.Error()) + } } return bidderConfigMap, nil } @@ -78,7 +81,7 @@ func prepareRequestParams(bidderName string, requestParamsConfig map[string]any) return nil, fmt.Errorf("error:[incorrect_location_in_bidderparam] bidder:[%s] bidderParam:[%s]", bidderName, paramName) } requestParams[paramName] = BidderParamMapper{ - location: strings.Split(locationStr, "."), + location: locationStr, } } return requestParams, nil diff --git a/adapters/ortbbidder/bidderparams/parser_test.go b/adapters/ortbbidder/bidderparams/parser_test.go index 54ae74deea0..bdf31deab10 100644 --- a/adapters/ortbbidder/bidderparams/parser_test.go +++ b/adapters/ortbbidder/bidderparams/parser_test.go @@ -119,7 +119,7 @@ func TestPrepareRequestParams(t *testing.T) { }, want: want{ requestParams: map[string]BidderParamMapper{ - "adunitid": {location: []string{"app", "adunitid"}}, + "adunitid": {location: "app.adunitid"}, }, err: nil, }, @@ -144,8 +144,8 @@ func TestPrepareRequestParams(t *testing.T) { }, want: want{ requestParams: map[string]BidderParamMapper{ - "adunitid": {location: []string{"app", "adunitid"}}, - "slotname": {location: []string{"ext", "slot"}}, + "adunitid": {location: "app.adunitid"}, + "slotname": {location: "ext.slot"}, }, err: nil, }, @@ -249,8 +249,8 @@ func TestLoadBidderConfig(t *testing.T) { bidderConfigMap: map[string]*config{ "owortb_test": { requestParams: map[string]BidderParamMapper{ - "adunitid": {location: []string{"app", "adunit", "id"}}, - "slotname": {location: []string{"ext", "slotname"}}, + "adunitid": {location: "app.adunit.id"}, + "slotname": {location: "ext.slotname"}, }, }, }}, diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index 0e11cc50233..be3c5bc89f9 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -7,6 +7,7 @@ import ( "strings" "text/template" + "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" @@ -42,17 +43,29 @@ func InitBidderParamsConfig(dirPath string) (err error) { return err } -// makeRequestForAllImps processes a request map to create single RequestData object for all impressions. -// It constructs the endpoint URL and maps the request-params in request to form the RequestData object. -func (adapterInfo adapterInfo) makeRequestForAllImps(request map[string]any, bidderParamMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { +// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. +// when supportSingleImpInRequest is true, it processes a request to generate 'N' RequestData objects, one for each of the 'N' impressions +// else it create single RequestData object for all impressions. +func (adapterInfo adapterInfo) makeRequest(rawRequest []byte, bidderParamMapper map[string]bidderparams.BidderParamMapper, supportSingleImpInRequest bool) ([]*adapters.RequestData, []error) { + request, err := convertRequestToMap(rawRequest) + if err != nil { + return nil, []error{newBadInputError(err.Error())} + } imps, ok := request[impKey].([]any) if !ok { - return nil, []error{newBadInputError("invalid imp object found in request")} + return nil, []error{newBadInputError("imp object not found in request")} + } + // set "imp" object in request to empty to improve performance while creating deep copy of request + if supportSingleImpInRequest { + rawRequest, err = jsonparser.Set(rawRequest, []byte("[]"), impKey) + if err != nil { + return nil, []error{fmt.Errorf("failed to empty the imp key in request")} + } } var ( - err error - uri string - errs []error + uri string + errs []error + requestData []*adapters.RequestData ) // iterate through imps in reverse order to ensure setRequestParams prioritizes // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. @@ -63,81 +76,91 @@ func (adapterInfo adapterInfo) makeRequestForAllImps(request map[string]any, bid continue } bidderParams := getImpExtBidderParams(imp) - // build endpoint URL once, using the imp[0].ext.bidder parameters - // this must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. + if supportSingleImpInRequest { + // build endpoint-url from bidder-params, it must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. + // for "single" requestMode, build endpoint-uri separately using each imp's bidder-params + uri, err = buildEndpoint(adapterInfo.endpointTemplate, bidderParams) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + continue + } + // override "imp" key in request to ensure request contains single imp + request[impKey] = []any{imp} + // update the request and imp object by mapping bidderParams at expected location. + setRequestParams(request, bidderParams, bidderParamMapper, []int{0}) + requestData, err = appendRequestData(requestData, request, uri) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + } + // create a deep copy of the request to ensure common fields are not altered. + // example - if imp2 modifies the original req.bcat field using its bidder-params, imp1 should still be able to use the original req.bcat value. + if impIndex != 0 { + request, err = convertRequestToMap(rawRequest) + if err != nil { + errs = append(errs, newBadInputError(fmt.Sprintf("failed to build request from rawRequest, err:%s", err.Error()))) + return requestData, errs + } + } + continue + } + // processing for "multi" requestMode if impIndex == 0 { - uri, err = macros.ResolveMacros(adapterInfo.endpointTemplate, bidderParams) + // build endpoint-url only once using first imp's bidder-params + uri, err = buildEndpoint(adapterInfo.endpointTemplate, bidderParams) if err != nil { - return nil, []error{newBadInputError(fmt.Sprintf("failed to form endpoint url, err:%s", err.Error()))} + errs = append(errs, newBadInputError(err.Error())) + return nil, errs } } // update the request and imp object by mapping bidderParams at expected location. - setRequestParams(request, imp, bidderParams, bidderParamMapper) + setRequestParams(request, bidderParams, bidderParamMapper, []int{impIndex}) + } + // for "multi" requestMode, combine all the prepared requests + if !supportSingleImpInRequest { + requestData, err = appendRequestData(requestData, request, uri) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + } } - requestBody, err := jsonutil.Marshal(request) + + return requestData, errs +} + +// appendRequestData creates new RequestData using request and uri then appends it to requestData passed as argument +func appendRequestData(requestData []*adapters.RequestData, request map[string]any, uri string) ([]*adapters.RequestData, error) { + rawRequest, err := jsonutil.Marshal(request) if err != nil { - return nil, []error{newBadInputError(fmt.Sprintf("failed to marshal request after setting bidder-params, err:%s", err.Error()))} + return requestData, fmt.Errorf("failed to marshal request after setting bidder-params, err:%s", err.Error()) } - return []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: uri, - Body: requestBody, - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, + requestData = append(requestData, &adapters.RequestData{ + Method: http.MethodPost, + Uri: uri, + Body: rawRequest, + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, }, - }, errs + }) + return requestData, nil } -// makeRequestPerImp processes a request map to generate 'N' RequestData objects, one for each of the 'N' impressions. -// It constructs the endpoint URL and maps the request parameters to create the RequestData objects. -func (adapterInfo adapterInfo) makeRequestPerImp(request map[string]any, requestParams map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { - imps, ok := request[impKey].([]any) - if !ok { - return nil, []error{newBadInputError("invalid imp object found in request")} +// buildEndpoint replaces macros present in the endpoint-url and returns the updated uri +func buildEndpoint(endpointTemplate *template.Template, bidderParams map[string]any) (uri string, err error) { + uri, err = macros.ResolveMacros(endpointTemplate, bidderParams) + if err != nil { + return uri, fmt.Errorf("failed to replace macros in endpoint, err:%s", err.Error()) } - var ( - bidderParams map[string]any - requestData []*adapters.RequestData - errs []error - ) - for impIndex, imp := range imps { - imp, ok := imp.(map[string]any) - if !ok || imp == nil { - errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) - continue - } - bidderParams = getImpExtBidderParams(imp) - // build endpoint url from imp.ext.bidder - // this must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. - uri, err := macros.ResolveMacros(adapterInfo.endpointTemplate, bidderParams) - if err != nil { - errs = append(errs, newBadInputError(fmt.Sprintf("failed to form endpoint url for imp at index:%d, err:%s", impIndex, err.Error()))) - continue - } - // update the request and imp object by mapping bidderParams at expected location. - setRequestParams(request, imp, bidderParams, requestParams) - // request should contain single impression so override the request["imp"] field - request[impKey] = []any{imp} + uri = strings.ReplaceAll(uri, "", "") + return uri, err +} - requestBody, err := jsonutil.Marshal(request) - if err != nil { - errs = append(errs, newBadInputError(fmt.Sprintf("failed to marshal request after seeting bidder-params for imp at index:%d, err:%s", impIndex, err.Error()))) - continue - } - requestData = append(requestData, &adapters.RequestData{ - Method: http.MethodPost, - Uri: uri, - Body: requestBody, - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }) +// convertRequestToMap converts request from []byte to map[string]any, it creates deep copy of request using json-unmarshal +func convertRequestToMap(rawRequest []byte) (request map[string]any, err error) { + err = jsonutil.Unmarshal(rawRequest, &request) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal request, err:%s", err.Error()) } - return requestData, errs + return request, err } // Builder returns an instance of oRTB adapter @@ -149,7 +172,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return nil, fmt.Errorf("failed to parse extra_info: %s", err.Error()) } } - template, err := template.New("endpointTemplate").Parse(config.Endpoint) + template, err := template.New("endpointTemplate").Option("missingkey=zero").Parse(config.Endpoint) if err != nil || template == nil { return nil, fmt.Errorf("failed to parse endpoint url template: %v", err) } @@ -160,34 +183,20 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co } // MakeRequests prepares oRTB bidder-specific request information using which prebid server make call(s) to bidder. -func (o *adapter) MakeRequests(bidRequest *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - if bidRequest == nil { +func (o *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { + if request == nil { return nil, []error{newBadInputError("found nil request")} } if o.bidderParamsConfig == nil { return nil, []error{newBadInputError("found nil bidderParamsConfig")} } - rawRequest, err := jsonutil.Marshal(bidRequest) + rawRequest, err := jsonutil.Marshal(request) if err != nil { return nil, []error{newBadInputError(fmt.Sprintf("failed to marshal request, err:%s", err.Error()))} } - var request map[string]any - err = jsonutil.Unmarshal(rawRequest, &request) - if err != nil { - return nil, []error{newBadInputError(fmt.Sprintf("failed to unmarshal request, err:%s", err.Error()))} - } - var ( - requestData []*adapters.RequestData - errs []error - ) - requestParams, _ := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) - switch o.adapterInfo.extraInfo.RequestMode { - case requestModeSingle: - requestData, errs = o.adapterInfo.makeRequestPerImp(request, requestParams) - default: - requestData, errs = o.adapterInfo.makeRequestForAllImps(request, requestParams) - } - return requestData, errs + requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) + return o.adapterInfo.makeRequest( + rawRequest, requestParams, o.adapterInfo.extraInfo.RequestMode == requestModeSingle) } // MakeBids prepares bidderResponse from the oRTB bidder server's http.Response diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go index efe6a027ea9..fff01bc0eac 100644 --- a/adapters/ortbbidder/ortbbidder_test.go +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -108,7 +108,7 @@ func TestMakeRequests(t *testing.T) { { Method: http.MethodPost, Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -117,7 +117,7 @@ func TestMakeRequests(t *testing.T) { { Method: http.MethodPost, Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -147,8 +147,8 @@ func TestMakeRequests(t *testing.T) { requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://localhost.com", - Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), + Uri: "http://", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -156,8 +156,8 @@ func TestMakeRequests(t *testing.T) { }, { Method: http.MethodPost, - Uri: "http://", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -520,7 +520,7 @@ func TestBuilder(t *testing.T) { }, bidderName: "ortbbidder", endpointTemplate: func() *template.Template { - template, _ := template.New("endpointTemplate").Parse("") + template, _ := template.New("endpointTemplate").Option("missingkey=zero").Parse("") return template }(), }, @@ -546,7 +546,7 @@ func TestBuilder(t *testing.T) { }, bidderName: "ortbbidder", endpointTemplate: func() *template.Template { - template, _ := template.New("endpointTemplate").Parse("") + template, _ := template.New("endpointTemplate").Option("missingkey=zero").Parse("") return template }(), }, @@ -622,13 +622,14 @@ func TestIsORTBBidder(t *testing.T) { } } -func TestMakeRequestForAllImps(t *testing.T) { +func TestMakeRequest(t *testing.T) { type fields struct { endpointTemplate *template.Template } type args struct { - request map[string]any - bidderParamMapper map[string]bidderparams.BidderParamMapper + rawRequest json.RawMessage + bidderParamMapper map[string]bidderparams.BidderParamMapper + supportSingleImpInRequest bool } type want struct { requestData []*adapters.RequestData @@ -640,24 +641,33 @@ func TestMakeRequestForAllImps(t *testing.T) { args args want want }{ + { + name: "nil_request", + fields: fields{}, + args: args{ + rawRequest: nil, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to unmarshal request, err:expect { or n, but found \x00")}, + }, + }, { name: "no_imp_object", fields: fields{}, args: args{ - request: map[string]any{}, + rawRequest: json.RawMessage(`{}`), }, want: want{ requestData: nil, - errs: []error{newBadInputError("invalid imp object found in request")}, + errs: []error{newBadInputError("imp object not found in request")}, }, }, { name: "invalid_imp_object", fields: fields{}, args: args{ - request: map[string]any{ - "imp": []any{"invalid"}, - }, + rawRequest: json.RawMessage(`{"imp":["invalid"]}`), }, want: want{ requestData: []*adapters.RequestData{ @@ -675,26 +685,12 @@ func TestMakeRequestForAllImps(t *testing.T) { }, }, { - name: "replace_macros_to_form_endpoint_url", + name: "multiRequestMode_replace_macros_to_form_endpoint_url", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), }, want: want{ requestData: []*adapters.RequestData{ @@ -712,25 +708,18 @@ func TestMakeRequestForAllImps(t *testing.T) { }, }, { - name: "macros_value_absent_in_bidder_params", + name: "multiRequestMode_macros_value_absent_in_bidder_params", fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{}, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http:///publisher/", + Uri: "http:///publisher/", Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, @@ -742,7 +731,7 @@ func TestMakeRequestForAllImps(t *testing.T) { }, }, { - name: "macro_replacement_failure", + name: "multiRequestMode_macro_replacement_failure", fields: fields{ endpointTemplate: func() *template.Template { errorFunc := template.FuncMap{ @@ -755,47 +744,22 @@ func TestMakeRequestForAllImps(t *testing.T) { }(), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{}, - }, - }, - }, + supportSingleImpInRequest: false, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), }, want: want{ requestData: nil, - errs: []error{newBadInputError("failed to form endpoint url, err:template: endpointTemplate:1:2: " + + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, }, }, { - name: "first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", + name: "multiRequestMode_first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - }, - }, - }, - map[string]any{ - "id": "imp_2", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "imp2.host.com", - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), }, want: want{ requestData: []*adapters.RequestData{ @@ -818,26 +782,12 @@ func TestMakeRequestForAllImps(t *testing.T) { endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) + hostMapper.SetLocation("host") extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) + extMapper.SetLocation("device") return map[string]bidderparams.BidderParamMapper{ "host": hostMapper, "ext": extMapper, @@ -860,41 +810,19 @@ func TestMakeRequestForAllImps(t *testing.T) { }, }, { - name: "map_bidder_params_in_multi_imp", + name: "multiRequestMode_map_bidder_params_in_multi_imp", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - map[string]any{ - "id": "imp_2", - "ext": map[string]any{ - "bidder": map[string]any{ - "tagid": "valid_tag_id", - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) + hostMapper.SetLocation("host") extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) + extMapper.SetLocation("device") tagMapper := bidderparams.BidderParamMapper{} - tagMapper.SetLocation([]string{"imp", "tagid"}) + tagMapper.SetLocation("imp.#.tagid") return map[string]bidderparams.BidderParamMapper{ "host": hostMapper, "ext": extMapper, @@ -918,41 +846,17 @@ func TestMakeRequestForAllImps(t *testing.T) { }, }, { - name: "first_imp_bidder_param_has_high_pririty", + name: "multiRequestMode_first_imp_bidder_param_has_high_pririty", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - map[string]any{ - "id": "imp_2", - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_2"}]}`), bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) + hostMapper.SetLocation("host") extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) + extMapper.SetLocation("device") return map[string]bidderparams.BidderParamMapper{ "host": hostMapper, "ext": extMapper, @@ -980,21 +884,7 @@ func TestMakeRequestForAllImps(t *testing.T) { endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), bidderParamMapper: nil, }, want: want{ @@ -1012,89 +902,31 @@ func TestMakeRequestForAllImps(t *testing.T) { errs: nil, }, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - o := adapterInfo{ - endpointTemplate: tt.fields.endpointTemplate, - } - requestData, errs := o.makeRequestForAllImps(tt.args.request, tt.args.bidderParamMapper) - assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - assert.Equalf(t, tt.want.errs, errs, "mismatched errs") - }) - } -} - -func TestMakeRequestPerImp(t *testing.T) { - type fields struct { - endpointTemplate *template.Template - } - type args struct { - request map[string]any - bidderParamMapper map[string]bidderparams.BidderParamMapper - } - type want struct { - requestData []*adapters.RequestData - errs []error - } - tests := []struct { - name string - fields fields - args args - want want - }{ - { - name: "no_imp_object", - fields: fields{}, - args: args{ - request: map[string]any{}, - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("invalid imp object found in request")}, - }, - }, { - name: "invalid_imp_object", - fields: fields{}, - args: args{ - request: map[string]any{ - "imp": []any{"invalid"}, - }, - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("invalid imp object found at index:0")}, - }, - }, - { - name: "replace_macros_to_form_endpoint_url", + name: "singleRequestMode_single_imp_request", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"}]}`), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation("host") + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation("device") + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + supportSingleImpInRequest: true, }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -1105,160 +937,107 @@ func TestMakeRequestPerImp(t *testing.T) { }, }, { - name: "macros_value_absent_in_bidder_params", + name: "singleRequestMode_multi_imps_request", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{}, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{} + hostMapper.SetLocation("host") + extMapper := bidderparams.BidderParamMapper{} + extMapper.SetLocation("device") + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + supportSingleImpInRequest: true, }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http:///publisher/", - Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, }, - }, - errs: nil, - }, - }, - { - name: "macro_replacement_failure", - fields: fields{ - endpointTemplate: func() *template.Template { - errorFunc := template.FuncMap{ - "errorFunc": func() (string, error) { - return "", errors.New("intentional error") - }, - } - t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - return t - }(), - }, - args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{}, + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, }, }, }, - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to form endpoint url for imp at index:0, err:template: endpointTemplate:1:2: " + - "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + errs: nil, }, }, { - name: "single_imp_request", + name: "singleRequestMode_multi_imps_request_with_one_invalid_imp", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) + hostMapper.SetLocation("host") extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) + extMapper.SetLocation("device") return map[string]bidderparams.BidderParamMapper{ "host": hostMapper, "ext": extMapper, } }(), + supportSingleImpInRequest: true, }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, }, }, - errs: nil, + errs: []error{newBadInputError("invalid imp object found at index:1")}, }, }, { - name: "multi_imps_request", + name: "singleRequestMode_one_imp_updates_request_level_param_but_another_imp_does_not_update", fields: fields{ endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "imp1.host.com", - "ext": map[string]any{ - "pubid": 1111, - }, - }, - }, - }, - map[string]any{ - "id": "imp_2", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "imp2.host.com", - "ext": map[string]any{ - "pubid": 2222, - }, - }, - }, - }, - }, - }, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) + hostMapper.SetLocation("host") extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) + extMapper.SetLocation("device") return map[string]bidderparams.BidderParamMapper{ "host": hostMapper, "ext": extMapper, } }(), + supportSingleImpInRequest: true, }, want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -1266,8 +1045,8 @@ func TestMakeRequestPerImp(t *testing.T) { }, { Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Uri: "http:///publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -1278,89 +1057,26 @@ func TestMakeRequestPerImp(t *testing.T) { }, }, { - name: "multi_imps_request_with_one_invalid_imp", + name: "singleRequestMode_macro_replacement_failure", fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "imp1.host.com", - "ext": map[string]any{ - "pubid": 1111, - }, - }, - }, + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") }, - "invalid-imp", - }, - }, - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation([]string{"host"}) - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation([]string{"device"}) - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t }(), }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: []error{newBadInputError("invalid imp object found at index:1")}, - }, - }, - { - name: "bidder_param_mapping_absent", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, args: args{ - request: map[string]any{ - "imp": []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "host": "localhost.com", - "ext": map[string]any{ - "pubid": 5890, - }, - }, - }, - }, - }, - }, - bidderParamMapper: nil, + supportSingleImpInRequest: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), }, want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, + requestData: nil, + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, }, }, } @@ -1369,7 +1085,7 @@ func TestMakeRequestPerImp(t *testing.T) { o := adapterInfo{ endpointTemplate: tt.fields.endpointTemplate, } - requestData, errs := o.makeRequestPerImp(tt.args.request, tt.args.bidderParamMapper) + requestData, errs := o.makeRequest(tt.args.rawRequest, tt.args.bidderParamMapper, tt.args.supportSingleImpInRequest) assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") assert.Equalf(t, tt.want.errs, errs, "mismatched errs") }) diff --git a/adapters/ortbbidder/requestParamMapper.go b/adapters/ortbbidder/requestParamMapper.go index 27633711e88..b7e34c5ef3b 100644 --- a/adapters/ortbbidder/requestParamMapper.go +++ b/adapters/ortbbidder/requestParamMapper.go @@ -1,22 +1,43 @@ package ortbbidder import ( + "strconv" + "strings" + "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" ) -// setRequestParams updates the request and imp object by mapping bidderParams at expected location. -func setRequestParams(request, imp, bidderParams map[string]any, paramsMapper map[string]bidderparams.BidderParamMapper) { - for paramName, paramValue := range bidderParams { +// setRequestParams updates the request object by mapping bidderParams at expected location. +func setRequestParams(request, params map[string]any, paramsMapper map[string]bidderparams.BidderParamMapper, paramIndices []int) { + for paramName, paramValue := range params { paramMapper, ok := paramsMapper[paramName] if !ok { continue } + // add index in path by replacing # macro + location := addIndicesInPath(paramMapper.GetLocation(), paramIndices) // set the value in the request according to the mapping details // remove the parameter from bidderParams after successful mapping - if setValue(request, imp, paramMapper.GetLocation(), paramValue) { - delete(bidderParams, paramName) + if setValue(request, location, paramValue) { + delete(params, paramName) + } + } +} + +// addIndicesInPath updates the path by replacing # by arrayIndices +func addIndicesInPath(path string, indices []int) []string { + parts := strings.Split(path, ".") + j := 0 + for i, part := range parts { + if part == "#" { + if j >= len(indices) { + break + } + parts[i] = strconv.Itoa(indices[j]) + j++ } } + return parts } // getImpExtBidderParams returns imp.ext.bidder parameters diff --git a/adapters/ortbbidder/requestParamMapper_test.go b/adapters/ortbbidder/requestParamMapper_test.go index 46d547c3eaa..12c2d053228 100644 --- a/adapters/ortbbidder/requestParamMapper_test.go +++ b/adapters/ortbbidder/requestParamMapper_test.go @@ -10,13 +10,12 @@ import ( func TestSetRequestParams(t *testing.T) { type args struct { request map[string]any - imp map[string]any bidderParams map[string]any paramsMapper map[string]bidderparams.BidderParamMapper + paramIndices []int } type want struct { request map[string]any - imp map[string]any bidderParams map[string]any } tests := []struct { @@ -30,9 +29,6 @@ func TestSetRequestParams(t *testing.T) { request: map[string]any{ "id": "req_1", }, - imp: map[string]any{ - "id": "imp_1", - }, bidderParams: map[string]any{ "param": "value", }, @@ -42,9 +38,6 @@ func TestSetRequestParams(t *testing.T) { request: map[string]any{ "id": "req_1", }, - imp: map[string]any{ - "id": "imp_1", - }, bidderParams: map[string]any{ "param": "value", }, @@ -56,28 +49,23 @@ func TestSetRequestParams(t *testing.T) { request: map[string]any{ "id": "req_1", }, - imp: map[string]any{ - "id": "imp_1", - }, bidderParams: map[string]any{ "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation([]string{"param"}) + mapper.SetLocation("param") return map[string]bidderparams.BidderParamMapper{ "param": mapper, } }(), + paramIndices: nil, }, want: want{ request: map[string]any{ "param": "value", "id": "req_1", }, - imp: map[string]any{ - "id": "imp_1", - }, bidderParams: map[string]any{}, }, }, @@ -86,38 +74,105 @@ func TestSetRequestParams(t *testing.T) { args: args{ request: map[string]any{ "id": "req_1", + "imp": []any{ + map[string]any{}, + }, }, - imp: map[string]any{ - "id": "imp_1", + bidderParams: map[string]any{ + "param": "value", + }, + paramsMapper: func() map[string]bidderparams.BidderParamMapper { + mapper := bidderparams.BidderParamMapper{} + mapper.SetLocation("imp.#.param") + return map[string]bidderparams.BidderParamMapper{ + "param": mapper, + } + }(), + paramIndices: []int{0}, + }, + want: want{ + request: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "param": "value", + }, + }, + }, + bidderParams: map[string]any{}, + }, + }, + { + name: "attempt_to_set_imp_level_param_in_invalid_index_position", + args: args{ + request: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{}, + }, }, bidderParams: map[string]any{ "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation([]string{"imp", "param"}) + mapper.SetLocation("imp.#.param") return map[string]bidderparams.BidderParamMapper{ "param": mapper, } }(), + paramIndices: []int{1}, }, want: want{ request: map[string]any{ "id": "req_1", + "imp": []any{ + map[string]any{}, + }, }, - imp: map[string]any{ + bidderParams: map[string]any{ + "param": "value", + }, + }, + }, + { + name: "attempt_to_set_imp_level_param_when_no_index_is_given", + args: args{ + request: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{}, + }, + }, + bidderParams: map[string]any{ + "param": "value", + }, + paramsMapper: func() map[string]bidderparams.BidderParamMapper { + mapper := bidderparams.BidderParamMapper{} + mapper.SetLocation("imp.#.param") + return map[string]bidderparams.BidderParamMapper{ + "param": mapper, + } + }(), + paramIndices: []int{}, + }, + want: want{ + request: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{}, + }, + }, + bidderParams: map[string]any{ "param": "value", - "id": "imp_1", }, - bidderParams: map[string]any{}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - setRequestParams(tt.args.request, tt.args.imp, tt.args.bidderParams, tt.args.paramsMapper) + setRequestParams(tt.args.request, tt.args.bidderParams, tt.args.paramsMapper, tt.args.paramIndices) assert.Equal(t, tt.want.bidderParams, tt.args.bidderParams, "mismatched bidderparams") - assert.Equal(t, tt.want.imp, tt.args.imp, "mismatched imp") assert.Equal(t, tt.want.request, tt.args.request, "mismatched request") }) } diff --git a/adapters/ortbbidder/util.go b/adapters/ortbbidder/util.go index 78e1bb18b32..9cb58358739 100644 --- a/adapters/ortbbidder/util.go +++ b/adapters/ortbbidder/util.go @@ -1,6 +1,10 @@ package ortbbidder -import "github.com/prebid/prebid-server/v2/errortypes" +import ( + "strconv" + + "github.com/prebid/prebid-server/v2/errortypes" +) /* setValue updates or creates a value in a node based on a specified location. @@ -10,49 +14,63 @@ create intermediate nodes as necessary if they do not exist. Arguments: - node: the root of the map in which to set the value -- locations: slice of strings indicating the path to set the value. +- location: slice of strings indicating the path to set the value. - value: The value to set at the specified location. Can be of any type. Example: - - location = imp.ext.adunitid; value = 123 ==> {"imp": {"ext" : {"adunitid":123}}} + - location = imp.0.ext.adunitid; value = 123 ==> {"imp": {"ext" : {"adunitid":123}}} */ -func setValue(requestNode, impNode map[string]any, locations []string, value any) bool { - if value == nil || len(locations) == 0 { +func setValue(node map[string]any, location []string, value any) bool { + if node == nil || value == nil { return false } - - lastNodeIndex := len(locations) - 1 - currentNode := requestNode - - for index, loc := range locations { - if len(loc) == 0 { // if location part is empty string + var nextNode any = node + lastNodeIndex := len(location) - 1 + for locIndex, loc := range location { + if len(loc) == 0 { + // if location is invalid return false } - if index == lastNodeIndex { // if it's the last part in location, set the value - currentNode[loc] = value - break - } - nextNode := getNode(currentNode, impNode, loc) - // not the last part, navigate deeper - if nextNode == nil { - // loc does not exist, set currentNode to a new node - newNode := make(map[string]any) - currentNode[loc] = newNode - currentNode = newNode - continue - } - // loc exists, set currentNode to nextNode - nextNodeTyped, ok := nextNode.(map[string]any) - if !ok { + switch nextNodeTyped := nextNode.(type) { + case map[string]any: + if locIndex == lastNodeIndex { + // set value at last index + nextNodeTyped[loc] = value + return true + } + nextNode = getNode(nextNodeTyped, loc) + if nextNode == nil { + // create a new node if the next node does not exist + newNode := make(map[string]any) + nextNodeTyped[loc] = newNode + nextNode = newNode + } + case []any: + // extract the array nodeIndex from the location to determine where to set the value + nodeIndex, err := strconv.Atoi(loc) + if err != nil || nodeIndex < 0 || nodeIndex >= len(nextNodeTyped) { + return false + } + if locIndex == lastNodeIndex { + nextNodeTyped[nodeIndex] = value + return true + } + nextNode = nextNodeTyped[nodeIndex] + if nextNode == nil { + // create a new node if the next node does not exist + newNode := make(map[string]any) + nextNodeTyped[nodeIndex] = newNode + nextNode = newNode + } + default: return false } - currentNode = nextNodeTyped } - return true + return false } // getNode retrieves the value for a given key from a map with special handling for the "appsite", "imp" key -func getNode(requestNode, impNode map[string]any, key string) any { +func getNode(requestNode map[string]any, key string) any { switch key { case appsiteKey: // if key is "appsite" and if nodes contains "site" object then return nodes["site"] else return nodes["app"] @@ -60,8 +78,6 @@ func getNode(requestNode, impNode map[string]any, key string) any { return value } return requestNode[appKey] - case impKey: - return impNode } return requestNode[key] } diff --git a/adapters/ortbbidder/util_test.go b/adapters/ortbbidder/util_test.go index 319ef0eb44f..46792c7aa77 100644 --- a/adapters/ortbbidder/util_test.go +++ b/adapters/ortbbidder/util_test.go @@ -9,14 +9,12 @@ import ( func TestSetValue(t *testing.T) { type args struct { requestNode map[string]any - impNode map[string]any location []string value any } type want struct { node map[string]any status bool - imp map[string]any } tests := []struct { name string @@ -186,40 +184,153 @@ func TestSetValue(t *testing.T) { }, }, { - name: "imp_key_present", + name: "request_has_list_of_interface", args: args{ - requestNode: map[string]any{"id": map[string]any{ + requestNode: map[string]any{ "id": "req_1", - "imp": map[string]any{ - "id": "imp_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, }, - }}, - impNode: map[string]any{ - "id": "imp_1", }, - location: []string{"imp", "ext"}, + location: []string{"imp", "0", "ext"}, value: "value", }, want: want{ status: true, - imp: map[string]any{ - "ext": "value", - "id": "imp_1", + node: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + "ext": "value", + }, + }, }, - node: map[string]any{"id": map[string]any{ + }, + }, + { + name: "request_has_list_of_interface_with_multi_items", + args: args{ + requestNode: map[string]any{ "id": "req_1", - "imp": map[string]any{ - "id": "imp_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + map[string]any{ + "id": "imp_2", + }, }, - }}, + }, + location: []string{"imp", "1", "ext"}, + value: "value", + }, + want: want{ + status: true, + node: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + map[string]any{ + "id": "imp_2", + "ext": "value", + }, + }, + }, + }, + }, + { + name: "request_has_list_of_interface_with_multi_items_but_invalid_index_to_update", + args: args{ + requestNode: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + }, + }, + location: []string{"imp", "3", "ext"}, + value: "value", + }, + want: want{ + status: false, + node: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + }, + }, + }, + }, + { + name: "request_has_list_of_interface_with_multi_items_but_valid_index_to_update", + args: args{ + requestNode: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + }, + }, + location: []string{"imp", "0"}, + value: map[string]any{ + "id": "updated_id", + }, + }, + want: want{ + status: true, + node: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "id": "updated_id", + }, + }, + }, + }, + }, + { + name: "request_has_list_of_interface_where_new_node_need_to_be_created", + args: args{ + requestNode: map[string]any{ + "id": "req_1", + "imp": []any{ + nil, nil, + }, + }, + location: []string{"imp", "0", "ext"}, + value: map[string]any{ + "id": "updated_id", + }, + }, + want: want{ + status: true, + node: map[string]any{ + "id": "req_1", + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "id": "updated_id", + }, + }, + nil, + }, + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := setValue(tt.args.requestNode, tt.args.impNode, tt.args.location, tt.args.value) + got := setValue(tt.args.requestNode, tt.args.location, tt.args.value) assert.Equalf(t, tt.want.node, tt.args.requestNode, "SetValue failed to update node object") - assert.Equalf(t, tt.want.imp, tt.args.impNode, "SetValue failed to update imp object") assert.Equalf(t, tt.want.status, got, "SetValue returned invalid status") }) } @@ -228,7 +339,6 @@ func TestSetValue(t *testing.T) { func TestGetNode(t *testing.T) { type args struct { requestNode map[string]any - impNode map[string]any key string } tests := []struct { @@ -273,21 +383,24 @@ func TestGetNode(t *testing.T) { "device": map[string]any{ "deviceKey": "deviceVal", }, - "imp": []string{}, - }, - impNode: map[string]any{ - "id": "imp_1", + "imp": []any{ + map[string]any{ + "id": "imp_1", + }, + }, }, key: "imp", }, - want: map[string]any{ - "id": "imp_1", + want: []any{ + map[string]any{ + "id": "imp_1", + }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - node := getNode(tt.args.requestNode, tt.args.impNode, tt.args.key) + node := getNode(tt.args.requestNode, tt.args.key) assert.Equal(t, tt.want, node) }) } diff --git a/static/bidder-params/owortb_testbidder.json b/static/bidder-params/owortb_testbidder.json index 01cafb99f2f..2013927a55c 100644 --- a/static/bidder-params/owortb_testbidder.json +++ b/static/bidder-params/owortb_testbidder.json @@ -12,7 +12,7 @@ "tagid": { "type": "string", "description": "tagid param", - "location": "imp.tagid" + "location": "imp.#.tagid" }, "zone": { "type": "string", @@ -22,17 +22,17 @@ "maxduration": { "type": "integer", "description": "maxduration param", - "location": "imp.video.maxduration" + "location": "imp.#.video.maxduration" }, "livestream": { "type": "integer", "description": "livestream param", - "location": "app.cnt.livestream" + "location": "appsite.cnt.livestream" }, "url": { "type": "string", "description": "URL param setting in video-startdelay", - "location": "imp.video.startdelay" + "location": "imp.#.video.startdelay" }, "randomKey": { "type": "string", From 46519dbce3257243a49dc19d33292cd8a839548c Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Fri, 7 Jun 2024 14:57:16 +0530 Subject: [PATCH 3/9] makerequest as per different requestMode --- adapters/ortbbidder/bidderparams/config.go | 25 +- .../ortbbidder/bidderparams/config_test.go | 121 +- adapters/ortbbidder/bidderparams/parser.go | 7 +- .../ortbbidder/bidderparams/parser_test.go | 10 +- adapters/ortbbidder/constant.go | 20 +- adapters/ortbbidder/errors.go | 21 + adapters/ortbbidder/ortbbidder.go | 138 +- adapters/ortbbidder/ortbbidder_test.go | 812 +++++------ adapters/ortbbidder/requestBuilder.go | 209 +++ adapters/ortbbidder/requestBuilder_test.go | 1289 +++++++++++++++++ adapters/ortbbidder/requestParamMapper.go | 9 +- .../ortbbidder/requestParamMapper_test.go | 12 +- adapters/ortbbidder/util.go | 9 - 13 files changed, 1969 insertions(+), 713 deletions(-) create mode 100644 adapters/ortbbidder/errors.go create mode 100644 adapters/ortbbidder/requestBuilder.go create mode 100644 adapters/ortbbidder/requestBuilder_test.go diff --git a/adapters/ortbbidder/bidderparams/config.go b/adapters/ortbbidder/bidderparams/config.go index 618c4847647..eea6f87d209 100644 --- a/adapters/ortbbidder/bidderparams/config.go +++ b/adapters/ortbbidder/bidderparams/config.go @@ -1,23 +1,8 @@ package bidderparams -import ( - "fmt" -) - // BidderParamMapper contains property details like location type BidderParamMapper struct { - location string -} - -// GetLocation returns the location of bidderParam -func (bpm *BidderParamMapper) GetLocation() string { - return bpm.location -} - -// SetLocation sets the location in BidderParamMapper -// Do not modify the location of bidderParam unless you are writing unit test case -func (bpm *BidderParamMapper) SetLocation(location string) { - bpm.location = location + Location string // do not update this parameter for each request, its being shared across all requests } // config contains mappings requestParams and responseParams @@ -32,10 +17,7 @@ type BidderConfig struct { } // setRequestParams sets the bidder specific requestParams -func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) error { - if bcfg == nil { - return fmt.Errorf("BidderConfig is nil") - } +func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) { if bcfg.bidderConfigMap == nil { bcfg.bidderConfigMap = make(map[string]*config) } @@ -43,12 +25,11 @@ func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[ bcfg.bidderConfigMap[bidderName] = &config{} } bcfg.bidderConfigMap[bidderName].requestParams = requestParams - return nil } // GetRequestParams returns bidder specific requestParams func (bcfg *BidderConfig) GetRequestParams(bidderName string) map[string]BidderParamMapper { - if bcfg == nil || len(bcfg.bidderConfigMap) == 0 { + if len(bcfg.bidderConfigMap) == 0 { return nil } bidderConfig := bcfg.bidderConfigMap[bidderName] diff --git a/adapters/ortbbidder/bidderparams/config_test.go b/adapters/ortbbidder/bidderparams/config_test.go index 6f01294ffbd..9ba737ecd96 100644 --- a/adapters/ortbbidder/bidderparams/config_test.go +++ b/adapters/ortbbidder/bidderparams/config_test.go @@ -1,7 +1,6 @@ package bidderparams import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -25,23 +24,6 @@ func TestSetRequestParams(t *testing.T) { args args want want }{ - { - name: "bidderConfig_is_nil", - fields: fields{ - bidderConfig: nil, - }, - args: args{ - bidderName: "test", - requestParams: map[string]BidderParamMapper{ - "adunit": { - location: "ext.adunit", - }, - }, - }, - want: want{ - err: fmt.Errorf("BidderConfig is nil"), - }, - }, { name: "bidderConfigMap_is_nil", fields: fields{ @@ -53,7 +35,7 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "adunit": { - location: "ext.adunit", + Location: "ext.adunit", }, }, }, @@ -63,7 +45,7 @@ func TestSetRequestParams(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "adunit": { - location: "ext.adunit", + Location: "ext.adunit", }, }, }, @@ -82,7 +64,7 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "param-1": { - location: "path", + Location: "path", }, }, }, @@ -92,7 +74,7 @@ func TestSetRequestParams(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-1": { - location: "path", + Location: "path", }, }, }, @@ -108,7 +90,7 @@ func TestSetRequestParams(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-1": { - location: "path-1", + Location: "path-1", }, }, }, @@ -119,7 +101,7 @@ func TestSetRequestParams(t *testing.T) { bidderName: "test", requestParams: map[string]BidderParamMapper{ "param-2": { - location: "path-2", + Location: "path-2", }, }, }, @@ -129,7 +111,7 @@ func TestSetRequestParams(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-2": { - location: "path-2", + Location: "path-2", }, }, }, @@ -140,9 +122,8 @@ func TestSetRequestParams(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.fields.bidderConfig.setRequestParams(tt.args.bidderName, tt.args.requestParams) + tt.fields.bidderConfig.setRequestParams(tt.args.bidderName, tt.args.requestParams) assert.Equal(t, tt.want.bidderCfg, tt.fields.bidderConfig, "mismatched bidderConfig") - assert.Equal(t, tt.want.err, err, "mismatched error") }) } } @@ -163,18 +144,6 @@ func TestGetBidderRequestProperties(t *testing.T) { args args want want }{ - { - name: "BidderConfig_is_nil", - fields: fields{ - biddersConfig: nil, - }, - args: args{ - bidderName: "test", - }, - want: want{ - requestParams: nil, - }, - }, { name: "BidderConfigMap_is_nil", fields: fields{ @@ -229,7 +198,7 @@ func TestGetBidderRequestProperties(t *testing.T) { "test": { requestParams: map[string]BidderParamMapper{ "param-1": { - location: "value-1", + Location: "value-1", }, }, }, @@ -242,7 +211,7 @@ func TestGetBidderRequestProperties(t *testing.T) { want: want{ requestParams: map[string]BidderParamMapper{ "param-1": { - location: "value-1", + Location: "value-1", }, }, }, @@ -255,73 +224,3 @@ func TestGetBidderRequestProperties(t *testing.T) { }) } } - -func TestBidderParamMapperGetLocation(t *testing.T) { - tests := []struct { - name string - bpm BidderParamMapper - want string - }{ - { - name: "location_is_nil", - bpm: BidderParamMapper{ - location: "", - }, - want: "", - }, - { - name: "location_is_non_empty", - bpm: BidderParamMapper{ - location: "req.ext", - }, - want: "req.ext", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.bpm.GetLocation() - assert.Equal(t, tt.want, got, "mismatched location") - }) - } -} - -func TestBidderParamMapperSetLocation(t *testing.T) { - type args struct { - location string - } - tests := []struct { - name string - bpm BidderParamMapper - args args - want BidderParamMapper - }{ - { - name: "set_location", - bpm: BidderParamMapper{}, - args: args{ - location: "req.ext", - }, - want: BidderParamMapper{ - location: "req.ext", - }, - }, - { - name: "override_location", - bpm: BidderParamMapper{ - location: "imp.ext", - }, - args: args{ - location: "req.ext", - }, - want: BidderParamMapper{ - location: "req.ext", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.bpm.SetLocation(tt.args.location) - assert.Equal(t, tt.want, tt.bpm, "mismatched location") - }) - } -} diff --git a/adapters/ortbbidder/bidderparams/parser.go b/adapters/ortbbidder/bidderparams/parser.go index 4dec0a94baf..3ed33f97070 100644 --- a/adapters/ortbbidder/bidderparams/parser.go +++ b/adapters/ortbbidder/bidderparams/parser.go @@ -36,10 +36,7 @@ func LoadBidderConfig(dirPath string, isBidderAllowed func(string) bool) (*Bidde if err != nil { return nil, err } - err = bidderConfigMap.setRequestParams(bidderName, requestParams) - if err != nil { - return nil, fmt.Errorf("error:[fail_to_set_request_param] dir:[%s] filename:[%s] err:[%s]", dirPath, file.Name(), err.Error()) - } + bidderConfigMap.setRequestParams(bidderName, requestParams) } return bidderConfigMap, nil } @@ -81,7 +78,7 @@ func prepareRequestParams(bidderName string, requestParamsConfig map[string]any) return nil, fmt.Errorf("error:[incorrect_location_in_bidderparam] bidder:[%s] bidderParam:[%s]", bidderName, paramName) } requestParams[paramName] = BidderParamMapper{ - location: locationStr, + Location: locationStr, } } return requestParams, nil diff --git a/adapters/ortbbidder/bidderparams/parser_test.go b/adapters/ortbbidder/bidderparams/parser_test.go index bdf31deab10..3d543b9960d 100644 --- a/adapters/ortbbidder/bidderparams/parser_test.go +++ b/adapters/ortbbidder/bidderparams/parser_test.go @@ -119,7 +119,7 @@ func TestPrepareRequestParams(t *testing.T) { }, want: want{ requestParams: map[string]BidderParamMapper{ - "adunitid": {location: "app.adunitid"}, + "adunitid": {Location: "app.adunitid"}, }, err: nil, }, @@ -144,8 +144,8 @@ func TestPrepareRequestParams(t *testing.T) { }, want: want{ requestParams: map[string]BidderParamMapper{ - "adunitid": {location: "app.adunitid"}, - "slotname": {location: "ext.slot"}, + "adunitid": {Location: "app.adunitid"}, + "slotname": {Location: "ext.slot"}, }, err: nil, }, @@ -249,8 +249,8 @@ func TestLoadBidderConfig(t *testing.T) { bidderConfigMap: map[string]*config{ "owortb_test": { requestParams: map[string]BidderParamMapper{ - "adunitid": {location: "app.adunit.id"}, - "slotname": {location: "ext.slotname"}, + "adunitid": {Location: "app.adunit.id"}, + "slotname": {Location: "ext.slotname"}, }, }, }}, diff --git a/adapters/ortbbidder/constant.go b/adapters/ortbbidder/constant.go index 9e773d6a356..d464452adf6 100644 --- a/adapters/ortbbidder/constant.go +++ b/adapters/ortbbidder/constant.go @@ -2,11 +2,17 @@ package ortbbidder // constants required for oRTB adapter const ( - impKey = "imp" - extKey = "ext" - bidderKey = "bidder" - appsiteKey = "appsite" - siteKey = "site" - appKey = "app" - requestModeSingle = "single" + impKey = "imp" + extKey = "ext" + bidderKey = "bidder" + appsiteKey = "appsite" + siteKey = "site" + appKey = "app" +) + +const ( + urlMacroPrefix = "{{." + urlMacroNoValue = "" + requestModeSingle = "single" + locationIndexMacro = "#" ) diff --git a/adapters/ortbbidder/errors.go b/adapters/ortbbidder/errors.go new file mode 100644 index 00000000000..775cdf33e50 --- /dev/null +++ b/adapters/ortbbidder/errors.go @@ -0,0 +1,21 @@ +package ortbbidder + +import ( + "errors" + + "github.com/prebid/prebid-server/v2/errortypes" +) + +// list of constant errors +var ( + errImpMissing error = errors.New("imp object not found in request") + errImpSetToEmpty error = errors.New("failed to empty the imp key in request") + errNilBidderParamCfg error = errors.New("found nil bidderParamsConfig") +) + +// newBadInputError returns the error of type bad-input +func newBadInputError(message string) error { + return &errortypes.BadInput{ + Message: message, + } +} diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index be3c5bc89f9..3e57c85b655 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -3,16 +3,13 @@ package ortbbidder import ( "encoding/json" "fmt" - "net/http" "strings" "text/template" - "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" "github.com/prebid/prebid-server/v2/config" - "github.com/prebid/prebid-server/v2/macros" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/prebid/prebid-server/v2/util/jsonutil" ) @@ -43,126 +40,6 @@ func InitBidderParamsConfig(dirPath string) (err error) { return err } -// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. -// when supportSingleImpInRequest is true, it processes a request to generate 'N' RequestData objects, one for each of the 'N' impressions -// else it create single RequestData object for all impressions. -func (adapterInfo adapterInfo) makeRequest(rawRequest []byte, bidderParamMapper map[string]bidderparams.BidderParamMapper, supportSingleImpInRequest bool) ([]*adapters.RequestData, []error) { - request, err := convertRequestToMap(rawRequest) - if err != nil { - return nil, []error{newBadInputError(err.Error())} - } - imps, ok := request[impKey].([]any) - if !ok { - return nil, []error{newBadInputError("imp object not found in request")} - } - // set "imp" object in request to empty to improve performance while creating deep copy of request - if supportSingleImpInRequest { - rawRequest, err = jsonparser.Set(rawRequest, []byte("[]"), impKey) - if err != nil { - return nil, []error{fmt.Errorf("failed to empty the imp key in request")} - } - } - var ( - uri string - errs []error - requestData []*adapters.RequestData - ) - // iterate through imps in reverse order to ensure setRequestParams prioritizes - // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. - for impIndex := len(imps) - 1; impIndex >= 0; impIndex-- { - imp, ok := imps[impIndex].(map[string]any) - if !ok || imp == nil { - errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) - continue - } - bidderParams := getImpExtBidderParams(imp) - if supportSingleImpInRequest { - // build endpoint-url from bidder-params, it must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. - // for "single" requestMode, build endpoint-uri separately using each imp's bidder-params - uri, err = buildEndpoint(adapterInfo.endpointTemplate, bidderParams) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - continue - } - // override "imp" key in request to ensure request contains single imp - request[impKey] = []any{imp} - // update the request and imp object by mapping bidderParams at expected location. - setRequestParams(request, bidderParams, bidderParamMapper, []int{0}) - requestData, err = appendRequestData(requestData, request, uri) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - } - // create a deep copy of the request to ensure common fields are not altered. - // example - if imp2 modifies the original req.bcat field using its bidder-params, imp1 should still be able to use the original req.bcat value. - if impIndex != 0 { - request, err = convertRequestToMap(rawRequest) - if err != nil { - errs = append(errs, newBadInputError(fmt.Sprintf("failed to build request from rawRequest, err:%s", err.Error()))) - return requestData, errs - } - } - continue - } - // processing for "multi" requestMode - if impIndex == 0 { - // build endpoint-url only once using first imp's bidder-params - uri, err = buildEndpoint(adapterInfo.endpointTemplate, bidderParams) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - return nil, errs - } - } - // update the request and imp object by mapping bidderParams at expected location. - setRequestParams(request, bidderParams, bidderParamMapper, []int{impIndex}) - } - // for "multi" requestMode, combine all the prepared requests - if !supportSingleImpInRequest { - requestData, err = appendRequestData(requestData, request, uri) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - } - } - - return requestData, errs -} - -// appendRequestData creates new RequestData using request and uri then appends it to requestData passed as argument -func appendRequestData(requestData []*adapters.RequestData, request map[string]any, uri string) ([]*adapters.RequestData, error) { - rawRequest, err := jsonutil.Marshal(request) - if err != nil { - return requestData, fmt.Errorf("failed to marshal request after setting bidder-params, err:%s", err.Error()) - } - requestData = append(requestData, &adapters.RequestData{ - Method: http.MethodPost, - Uri: uri, - Body: rawRequest, - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }) - return requestData, nil -} - -// buildEndpoint replaces macros present in the endpoint-url and returns the updated uri -func buildEndpoint(endpointTemplate *template.Template, bidderParams map[string]any) (uri string, err error) { - uri, err = macros.ResolveMacros(endpointTemplate, bidderParams) - if err != nil { - return uri, fmt.Errorf("failed to replace macros in endpoint, err:%s", err.Error()) - } - uri = strings.ReplaceAll(uri, "", "") - return uri, err -} - -// convertRequestToMap converts request from []byte to map[string]any, it creates deep copy of request using json-unmarshal -func convertRequestToMap(rawRequest []byte) (request map[string]any, err error) { - err = jsonutil.Unmarshal(rawRequest, &request) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal request, err:%s", err.Error()) - } - return request, err -} - // Builder returns an instance of oRTB adapter func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { extraAdapterInfo := extraAdapterInfo{} @@ -184,19 +61,16 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co // MakeRequests prepares oRTB bidder-specific request information using which prebid server make call(s) to bidder. func (o *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { - if request == nil { - return nil, []error{newBadInputError("found nil request")} - } if o.bidderParamsConfig == nil { - return nil, []error{newBadInputError("found nil bidderParamsConfig")} + return nil, []error{newBadInputError(errNilBidderParamCfg.Error())} } - rawRequest, err := jsonutil.Marshal(request) + requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) + requestBuilder := newRequestBuilder(o.adapterInfo.extraInfo.RequestMode, o.Endpoint) + err := requestBuilder.parseRequest(request) if err != nil { - return nil, []error{newBadInputError(fmt.Sprintf("failed to marshal request, err:%s", err.Error()))} + return nil, []error{newBadInputError(err.Error())} } - requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) - return o.adapterInfo.makeRequest( - rawRequest, requestParams, o.adapterInfo.extraInfo.RequestMode == requestModeSingle) + return requestBuilder.makeRequest(o.endpointTemplate, requestParams) } // MakeBids prepares bidderResponse from the oRTB bidder server's http.Response diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go index fff01bc0eac..88892939666 100644 --- a/adapters/ortbbidder/ortbbidder_test.go +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -2,7 +2,6 @@ package ortbbidder import ( "encoding/json" - "errors" "fmt" "net/http" "testing" @@ -34,15 +33,6 @@ func TestMakeRequests(t *testing.T) { args args want want }{ - { - name: "request_is_nil", - args: args{ - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - errors: []error{newBadInputError("found nil request")}, - }, - }, { name: "bidderParamsConfig_is_nil", args: args{ @@ -684,410 +674,410 @@ func TestMakeRequest(t *testing.T) { errs: []error{newBadInputError("invalid imp object found at index:0")}, }, }, - { - name: "multiRequestMode_replace_macros_to_form_endpoint_url", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "multiRequestMode_macros_value_absent_in_bidder_params", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http:///publisher/", - Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "multiRequestMode_macro_replacement_failure", - fields: fields{ - endpointTemplate: func() *template.Template { - errorFunc := template.FuncMap{ - "errorFunc": func() (string, error) { - return "", errors.New("intentional error") - }, - } - t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - return t - }(), - }, - args: args{ - supportSingleImpInRequest: false, - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - }, - }, - { - name: "multiRequestMode_first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "map_bidder_params_in_single_imp", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "multiRequestMode_map_bidder_params_in_multi_imp", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - tagMapper := bidderparams.BidderParamMapper{} - tagMapper.SetLocation("imp.#.tagid") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - "tagid": tagMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "multiRequestMode_first_imp_bidder_param_has_high_pririty", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_2"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "bidder_param_mapping_absent", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - bidderParamMapper: nil, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "singleRequestMode_single_imp_request", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - supportSingleImpInRequest: true, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "singleRequestMode_multi_imps_request", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - supportSingleImpInRequest: true, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "singleRequestMode_multi_imps_request_with_one_invalid_imp", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - supportSingleImpInRequest: true, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: []error{newBadInputError("invalid imp object found at index:1")}, - }, - }, - { - name: "singleRequestMode_one_imp_updates_request_level_param_but_another_imp_does_not_update", - fields: fields{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - args: args{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{} - hostMapper.SetLocation("host") - extMapper := bidderparams.BidderParamMapper{} - extMapper.SetLocation("device") - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - supportSingleImpInRequest: true, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http:///publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "singleRequestMode_macro_replacement_failure", - fields: fields{ - endpointTemplate: func() *template.Template { - errorFunc := template.FuncMap{ - "errorFunc": func() (string, error) { - return "", errors.New("intentional error") - }, - } - t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - return t - }(), - }, - args: args{ - supportSingleImpInRequest: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - }, - }, + // { + // name: "multiRequestMode_replace_macros_to_form_endpoint_url", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "multiRequestMode_macros_value_absent_in_bidder_params", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http:///publisher/", + // Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "multiRequestMode_macro_replacement_failure", + // fields: fields{ + // endpointTemplate: func() *template.Template { + // errorFunc := template.FuncMap{ + // "errorFunc": func() (string, error) { + // return "", errors.New("intentional error") + // }, + // } + // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + // return t + // }(), + // }, + // args: args{ + // supportSingleImpInRequest: false, + // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + // }, + // want: want{ + // requestData: nil, + // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + // }, + // }, + // { + // name: "multiRequestMode_first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher", + // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "map_bidder_params_in_single_imp", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "multiRequestMode_map_bidder_params_in_multi_imp", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // tagMapper := bidderparams.BidderParamMapper{} + // tagMapper.SetLocation("imp.#.tagid") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // "tagid": tagMapper, + // } + // }(), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "multiRequestMode_first_imp_bidder_param_has_high_pririty", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_2"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "bidder_param_mapping_absent", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // bidderParamMapper: nil, + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "singleRequestMode_single_imp_request", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // supportSingleImpInRequest: true, + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp1.host.com/publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "singleRequestMode_multi_imps_request", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // supportSingleImpInRequest: true, + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp2.host.com/publisher/2222", + // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // { + // Method: http.MethodPost, + // Uri: "http://imp1.host.com/publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "singleRequestMode_multi_imps_request_with_one_invalid_imp", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // supportSingleImpInRequest: true, + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp1.host.com/publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: []error{newBadInputError("invalid imp object found at index:1")}, + // }, + // }, + // { + // name: "singleRequestMode_one_imp_updates_request_level_param_but_another_imp_does_not_update", + // fields: fields{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // args: args{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{} + // hostMapper.SetLocation("host") + // extMapper := bidderparams.BidderParamMapper{} + // extMapper.SetLocation("device") + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // supportSingleImpInRequest: true, + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp2.host.com/publisher/2222", + // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // { + // Method: http.MethodPost, + // Uri: "http:///publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "singleRequestMode_macro_replacement_failure", + // fields: fields{ + // endpointTemplate: func() *template.Template { + // errorFunc := template.FuncMap{ + // "errorFunc": func() (string, error) { + // return "", errors.New("intentional error") + // }, + // } + // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + // return t + // }(), + // }, + // args: args{ + // supportSingleImpInRequest: true, + // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + // }, + // want: want{ + // requestData: nil, + // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + // }, + // }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - o := adapterInfo{ - endpointTemplate: tt.fields.endpointTemplate, - } - requestData, errs := o.makeRequest(tt.args.rawRequest, tt.args.bidderParamMapper, tt.args.supportSingleImpInRequest) - assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + // o := adapterInfo{ + // endpointTemplate: tt.fields.endpointTemplate, + // } + // requestData, errs := o.makeRequest(tt.args.rawRequest, tt.args.bidderParamMapper, tt.args.supportSingleImpInRequest) + // assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + // assert.Equalf(t, tt.want.errs, errs, "mismatched errs") }) } } diff --git a/adapters/ortbbidder/requestBuilder.go b/adapters/ortbbidder/requestBuilder.go new file mode 100644 index 00000000000..3e9f6d14b26 --- /dev/null +++ b/adapters/ortbbidder/requestBuilder.go @@ -0,0 +1,209 @@ +package ortbbidder + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + "text/template" + + "github.com/buger/jsonparser" + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" + "github.com/prebid/prebid-server/v2/macros" + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + +// requestBuilder is a struct used for constructing RequestData object +type requestBuilder struct { + rawRequest json.RawMessage + requestNode map[string]any + imps []any + endpoint string + hasMacrosInEndpoint bool +} + +// parseRequest parse the incoming request and populates intermediate fields required for building requestData object +func (reqBuilder *requestBuilder) parseRequest(request *openrtb2.BidRequest) (err error) { + reqBuilder.rawRequest, err = jsonutil.Marshal(request) + if err != nil { + return fmt.Errorf("failed to marshal request, err:%s", err.Error()) + } + reqBuilder.requestNode, err = reqBuilder.buildRequestNode() + if err != nil { + return err + } + var ok bool + reqBuilder.imps, ok = reqBuilder.requestNode[impKey].([]any) + if !ok { + return errImpMissing + } + for impIndex := range reqBuilder.imps { + imp := reqBuilder.imps[impIndex].(map[string]any) + delete(imp, "id") + } + + return +} + +// buildRequestNode creates request-map by unmarshaling request-bytes +func (reqBuilder *requestBuilder) buildRequestNode() (requestNode map[string]any, err error) { + err = jsonutil.Unmarshal(reqBuilder.rawRequest, &requestNode) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal request, err:%s", err.Error()) + } + return requestNode, err +} + +// buildEndpoint builds the adapter endpoint, if required it replaces the macros present in endpoint +func (reqBuilder *requestBuilder) buildEndpoint(endpointTemplate *template.Template, bidderParams map[string]any) (string, error) { + if !reqBuilder.hasMacrosInEndpoint { + return reqBuilder.endpoint, nil + } + uri, err := macros.ResolveMacros(endpointTemplate, bidderParams) + if err != nil { + return uri, fmt.Errorf("failed to replace macros in endpoint, err:%s", err.Error()) + } + uri = strings.ReplaceAll(uri, urlMacroNoValue, "") + return uri, err +} + +// requestModeBuilder is an interface containing parseRequest, makeRequest functions +type requestModeBuilder interface { + parseRequest(*openrtb2.BidRequest) error + makeRequest(*template.Template, map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) +} + +// newRequestBuilder returns the request-builder based on requestMode argument +func newRequestBuilder(requestMode, endpoint string) requestModeBuilder { + requestBuilder := requestBuilder{ + endpoint: endpoint, + hasMacrosInEndpoint: strings.Contains(endpoint, urlMacroPrefix), + } + if requestMode == requestModeSingle { + return &singleRequestModeBuilder{&requestBuilder} + } + return &multiRequestModeBuilder{&requestBuilder} +} + +// struct to build the request for single request mode where single imp is supported in a request +type singleRequestModeBuilder struct { + *requestBuilder +} + +// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. +// it processes a request to generate 'N' RequestData objects, one for each of the 'N' impressions +func (reqBuilder *singleRequestModeBuilder) makeRequest(endpointTemplate *template.Template, + paramsMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { + // set "imp" object in request to empty to improve performance while creating deep copy of request + var err error + reqBuilder.rawRequest, err = jsonparser.Set(reqBuilder.rawRequest, []byte("[]"), impKey) + if err != nil { + return nil, []error{newBadInputError(errImpSetToEmpty.Error())} + } + var ( + uri string + errs []error + requestData []*adapters.RequestData + ) + for impIndex := range reqBuilder.imps { + imp, ok := reqBuilder.imps[impIndex].(map[string]any) + if !ok || imp == nil { + errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) + continue + } + bidderParams := getImpExtBidderParams(imp) + // build endpoint-url from bidder-params, it must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. + uri, err = reqBuilder.buildEndpoint(endpointTemplate, bidderParams) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + continue + } + // override "imp" key in request to ensure request contains single imp + reqBuilder.requestNode[impKey] = []any{imp} + // update the request object by mapping bidderParams at expected location. + updatedRequest := setRequestParams(reqBuilder.requestNode, bidderParams, paramsMapper, []int{0}) + requestData, err = appendRequestData(requestData, reqBuilder.requestNode, uri) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + } + // create a deep copy of the request to ensure common fields are not altered. + // example - if imp2 modifies the original req.bcat field using its bidder-params, imp1 should still be able to use the original req.bcat value. + if impIndex != 0 && updatedRequest { + reqBuilder.requestNode, err = reqBuilder.buildRequestNode() + if err != nil { + errs = append(errs, newBadInputError(fmt.Sprintf("failed to build request from rawRequest, err:%s", err.Error()))) + return requestData, errs + } + } + } + return requestData, errs +} + +// struct to build the request for multi request mode where single request supports multiple impressions +type multiRequestModeBuilder struct { + *requestBuilder +} + +// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. +// it create single RequestData object for all impressions. +func (reqBuilder *multiRequestModeBuilder) makeRequest(endpointTemplate *template.Template, + paramsMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { + var ( + uri string + err error + errs []error + requestData []*adapters.RequestData + foundValidImp bool + ) + // reqBuilder.requestNode[impKey] = reqBuilder.imps + // iterate through imps in reverse order to ensure setRequestParams prioritizes + // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. + for impIndex := len(reqBuilder.imps) - 1; impIndex >= 0; impIndex-- { + imp, ok := reqBuilder.imps[impIndex].(map[string]any) + if !ok || imp == nil { + errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) + continue + } + bidderParams := getImpExtBidderParams(imp) + // build endpoint-url only once using first imp's bidder-params + if impIndex == 0 { + uri, err = reqBuilder.buildEndpoint(endpointTemplate, bidderParams) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + return nil, errs + } + } + // update the request object by mapping bidderParams at expected location. + setRequestParams(reqBuilder.requestNode, bidderParams, paramsMapper, []int{impIndex}) + foundValidImp = true + } + // if not single valid imp is found then return error + if !foundValidImp { + return nil, errs + } + requestData, err = appendRequestData(requestData, reqBuilder.requestNode, uri) + if err != nil { + errs = append(errs, newBadInputError(err.Error())) + } + return requestData, errs +} + +// appendRequestData creates new RequestData using request and uri then appends it to requestData passed as argument +func appendRequestData(requestData []*adapters.RequestData, request map[string]any, uri string) ([]*adapters.RequestData, error) { + rawRequest, err := jsonutil.Marshal(request) + if err != nil { + return requestData, fmt.Errorf("failed to marshal request after setting bidder-params, err:%s", err.Error()) + } + requestData = append(requestData, &adapters.RequestData{ + Method: http.MethodPost, + Uri: uri, + Body: rawRequest, + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }) + return requestData, nil +} diff --git a/adapters/ortbbidder/requestBuilder_test.go b/adapters/ortbbidder/requestBuilder_test.go new file mode 100644 index 00000000000..31ecb2bf642 --- /dev/null +++ b/adapters/ortbbidder/requestBuilder_test.go @@ -0,0 +1,1289 @@ +package ortbbidder + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "testing" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" + "github.com/prebid/prebid-server/v2/config" + "github.com/stretchr/testify/assert" +) + +func TestMakeRequests_(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + requestInfo *adapters.ExtraRequestInfo + adapterInfo adapterInfo + bidderCfg *bidderparams.BidderConfig + } + type want struct { + requestData []*adapters.RequestData + errors []error + } + tests := []struct { + name string + args args + want want + }{ + { + name: "request_is_nil", + args: args{ + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + errors: []error{newBadInputError(errImpMissing.Error())}, + }, + }, + { + name: "bidderParamsConfig_is_nil", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, + }, + adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: "single"}, "testbidder", nil}, + bidderCfg: nil, + }, + want: want{ + errors: []error{newBadInputError("found nil bidderParamsConfig")}, + }, + }, + { + name: "bidderParamsConfig_not_contains_bidder_param_data", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errors: nil, + }, + }, + { + name: "single_requestmode_to_form_requestdata", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, + { + name: "single_requestmode_validate_endpoint_macro", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://{{.host}}" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, + { + name: "multi_requestmode_to_form_requestdata", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1"}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://test_bidder.com" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://test_bidder.com", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, + { + name: "multi_requestmode_validate_endpoint_macros", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, + {ID: "imp2", TagID: "tag2"}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://{{.host}}" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} + }(), + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + adapter := &adapter{adapterInfo: tt.args.adapterInfo, bidderParamsConfig: tt.args.bidderCfg} + requestData, errors := adapter.MakeRequests(tt.args.request, tt.args.requestInfo) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errors, errors, "mismatched errors") + }) + } +} + +func TestParseRequest(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + } + type want struct { + err error + rawRequest json.RawMessage + requestNode map[string]any + imps []any + } + tests := []struct { + name string + args args + want want + }{ + // { + // name: "request_is_nil", + // args: args{ + // request: nil, + // }, + // want: want{ + // err: errImpMissing, + // rawRequest: json.RawMessage(`null`), + // }, + // }, + { + name: "request_is_valid", + args: args{ + request: &openrtb2.BidRequest{ + ID: "id", + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + }, + }, + }, + }, + want: want{ + err: nil, + rawRequest: json.RawMessage(`{"id":"id","imp":[{"id":"imp_1"}]}`), + requestNode: map[string]any{ + "id": "id", + "imp": []any{map[string]any{ + "id": "imp_1", + }}, + }, + imps: []any{map[string]any{ + "id": "imp_1", + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reqBuilder := &requestBuilder{} + err := reqBuilder.parseRequest(tt.args.request) + + assert.Equalf(t, tt.want.err, err, "mismatched error") + assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") + assert.Equalf(t, tt.want.requestNode, reqBuilder.requestNode, "mismatched requestNode") + assert.Equalf(t, tt.want.imps, reqBuilder.imps, "mismatched imps") + }) + } +} + +func TestBuildEndpoint(t *testing.T) { + type fields struct { + endpoint string + hasMacrosInEndpoint bool + } + type args struct { + endpointTemplate *template.Template + bidderParams map[string]any + } + type want struct { + err error + endpoint string + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "macros_present_but_hasMacrosInEndpoint_is_false", + fields: fields{ + endpoint: "http://{{.host}}/publisher", + hasMacrosInEndpoint: false, + }, + args: args{ + endpointTemplate: nil, + bidderParams: map[string]any{}, + }, + want: want{ + endpoint: "http://{{.host}}/publisher", + }, + }, + { + name: "macros_present_and_bidder_params_not_present", + fields: fields{ + endpoint: "http://{{.host}}/publisher", + hasMacrosInEndpoint: true, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), + bidderParams: map[string]any{}, + }, + want: want{ + endpoint: "http:///publisher", + }, + }, + { + name: "macros_present_and_bidder_params_present", + fields: fields{ + endpoint: "http://{{.host}}/publisher", + hasMacrosInEndpoint: true, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), + bidderParams: map[string]any{ + "host": "localhost", + }, + }, + want: want{ + endpoint: "http://localhost/publisher", + }, + }, + { + name: "resolveMacros_returns_error", + fields: fields{ + endpoint: "http://{{.errorFunc}}/publisher", + hasMacrosInEndpoint: true, + }, + args: args{ + bidderParams: map[string]any{}, + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + template := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return template + }(), + }, + want: want{ + endpoint: "", + err: fmt.Errorf("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reqBuilder := &requestBuilder{ + endpoint: tt.fields.endpoint, + hasMacrosInEndpoint: tt.fields.hasMacrosInEndpoint, + } + endpoint, err := reqBuilder.buildEndpoint(tt.args.endpointTemplate, tt.args.bidderParams) + assert.Equalf(t, tt.want.endpoint, endpoint, "mismatched endpoint") + assert.Equalf(t, tt.want.err, err, "mismatched error") + }) + } +} + +func TestNewRequestBuilder(t *testing.T) { + type args struct { + requestMode string + endpoint string + } + tests := []struct { + name string + args args + want requestModeBuilder + }{ + { + name: "singleRequestMode", + args: args{ + requestMode: requestModeSingle, + endpoint: "http://localhost/publisher", + }, + want: &singleRequestModeBuilder{ + &requestBuilder{ + endpoint: "http://localhost/publisher", + }, + }, + }, + + { + name: "multiRequestMode", + args: args{ + requestMode: requestModeSingle, + endpoint: "http://{{.host}}/publisher", + }, + want: &singleRequestModeBuilder{ + &requestBuilder{ + endpoint: "http://{{.host}}/publisher", + hasMacrosInEndpoint: true, + }, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := newRequestBuilder(tt.args.requestMode, tt.args.endpoint) + assert.Equalf(t, tt.want, got, "mismacthed requestbuilder") + }) + } +} + +func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { + type fields struct { + requestBuilder *requestBuilder + } + type args struct { + endpointTemplate *template.Template + bidderParamMapper map[string]bidderparams.BidderParamMapper + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "nil_request", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: nil, + }, + }, + args: args{}, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to empty the imp key in request")}, + }, + }, + { + name: "no_imp_object_in_builder", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: json.RawMessage(`{}`), + }, + }, + args: args{ + endpointTemplate: nil, + }, + want: want{ + requestData: nil, + errs: nil, + }, + }, + { + name: "invalid_imp_object", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: json.RawMessage(`{"imp":["invalid"]}`), + imps: []any{"invalid"}, + }, + }, + args: args{}, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp object found at index:0")}, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + imps: []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, + }, + "host": "localhost.com", + }, + }, + }, + }, + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, + }, + "host": "localhost.com", + }, + }, + "id": "imp_1", + }, + }, + }, + hasMacrosInEndpoint: true, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macros_value_absent_in_bidder_params", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/", + Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "buildEndpoint_returns_error", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + args: args{ + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "multi_imps_request", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "multi_imps_request_with_one_invalid_imp", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + "invalid", + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + "invalid", + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: []error{newBadInputError("invalid imp object found at index:1")}, + }, + }, + { + name: "one_imp_updates_request_level_param_but_another_imp_does_not_update", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sreq := &singleRequestModeBuilder{ + requestBuilder: tt.fields.requestBuilder, + } + requestData, errs := sreq.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} + +func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { + type fields struct { + requestBuilder *requestBuilder + } + type args struct { + endpointTemplate *template.Template + bidderParamMapper map[string]bidderparams.BidderParamMapper + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + args args + want want + }{ + // { + // name: "no_imp_object_in_builder", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // rawRequest: json.RawMessage(`{}`), + // }, + // }, + // args: args{ + // endpointTemplate: nil, + // }, + // want: want{ + // requestData: nil, + // errs: nil, + // }, + // }, + // { + // name: "invalid_imp_object", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // rawRequest: json.RawMessage(`{"imp":["invalid"]}`), + // imps: []any{"invalid"}, + // }, + // }, + // args: args{}, + // want: want{ + // requestData: nil, + // errs: []error{newBadInputError("invalid imp object found at index:0")}, + // }, + // }, + // { + // name: "replace_macros_to_form_endpoint_url", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // imps: []any{ + // map[string]any{ + // "id": "imp_1", + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 5890, + // }, + // "host": "localhost.com", + // }, + // }, + // }, + // }, + // requestNode: map[string]any{ + // "imp": []any{ + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 5890, + // }, + // "host": "localhost.com", + // }, + // }, + // "id": "imp_1", + // }, + // }, + // }, + // hasMacrosInEndpoint: true, + // }, + // }, + // args: args{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://localhost.com/publisher/5890", + // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + // { + // name: "buildEndpoint_returns_error", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // hasMacrosInEndpoint: true, + // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + // requestNode: map[string]any{ + // "imp": []any{ + // map[string]any{ + // "ext": map[string]any{}, + // "id": "imp_1", + // }, + // }, + // }, + // imps: []any{ + // map[string]any{ + // "ext": map[string]any{}, + // "id": "imp_1", + // }, + // }, + // }, + // }, + // args: args{ + // endpointTemplate: func() *template.Template { + // errorFunc := template.FuncMap{ + // "errorFunc": func() (string, error) { + // return "", errors.New("intentional error") + // }, + // } + // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + // return t + // }(), + // }, + // want: want{ + // requestData: nil, + // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + // }, + // }, + { + name: "multiRequestMode_map_bidder_params_in_multi_imp", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), + requestNode: map[string]any{ + "imp": []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, + }, + "host": "localhost.com", + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "tagid": "valid_tag_id", + }, + }, + "id": "imp_2", + }, + }, + }, + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, + }, + "host": "localhost.com", + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "tagid": "valid_tag_id", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + tagMapper := bidderparams.BidderParamMapper{Location: "imp.#.tagid"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + "tagid": tagMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + // { + // name: "multi_imps_request_with_one_invalid_imp", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // hasMacrosInEndpoint: true, + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), + // requestNode: map[string]any{ + // "imp": []any{ + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 1111, + // }, + // "host": "imp1.host.com", + // }, + // }, + // "id": "imp_1", + // }, + // "invalid", + // }, + // }, + // imps: []any{ + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 1111, + // }, + // "host": "imp1.host.com", + // }, + // }, + // "id": "imp_1", + // }, + // "invalid", + // }, + // }, + // }, + // args: args{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{Location: "host"} + // extMapper := bidderparams.BidderParamMapper{Location: "device"} + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp1.host.com/publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: []error{newBadInputError("invalid imp object found at index:1")}, + // }, + // }, + // { + // name: "one_imp_updates_request_level_param_but_another_imp_does_not_update", + // fields: fields{ + // requestBuilder: &requestBuilder{ + // hasMacrosInEndpoint: true, + // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + // requestNode: map[string]any{ + // "imp": []any{ + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 1111, + // }, + // }, + // }, + // "id": "imp_1", + // }, + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 2222, + // }, + // "host": "imp2.host.com", + // }, + // }, + // "id": "imp_2", + // }, + // }, + // }, + // imps: []any{ + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 1111, + // }, + // }, + // }, + // "id": "imp_1", + // }, + // map[string]any{ + // "ext": map[string]any{ + // "bidder": map[string]any{ + // "ext": map[string]any{ + // "pubid": 2222, + // }, + // "host": "imp2.host.com", + // }, + // }, + // "id": "imp_2", + // }, + // }, + // }, + // }, + // args: args{ + // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + // hostMapper := bidderparams.BidderParamMapper{Location: "host"} + // extMapper := bidderparams.BidderParamMapper{Location: "device"} + // return map[string]bidderparams.BidderParamMapper{ + // "host": hostMapper, + // "ext": extMapper, + // } + // }(), + // }, + // want: want{ + // requestData: []*adapters.RequestData{ + // { + // Method: http.MethodPost, + // Uri: "http://imp2.host.com/publisher/2222", + // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // { + // Method: http.MethodPost, + // Uri: "http:///publisher/1111", + // Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + // Headers: http.Header{ + // "Content-Type": {"application/json;charset=utf-8"}, + // "Accept": {"application/json"}, + // }, + // }, + // }, + // errs: nil, + // }, + // }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + builder := &multiRequestModeBuilder{ + requestBuilder: tt.fields.requestBuilder, + } + requestData, errs := builder.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} diff --git a/adapters/ortbbidder/requestParamMapper.go b/adapters/ortbbidder/requestParamMapper.go index b7e34c5ef3b..b453abd4bc4 100644 --- a/adapters/ortbbidder/requestParamMapper.go +++ b/adapters/ortbbidder/requestParamMapper.go @@ -8,20 +8,23 @@ import ( ) // setRequestParams updates the request object by mapping bidderParams at expected location. -func setRequestParams(request, params map[string]any, paramsMapper map[string]bidderparams.BidderParamMapper, paramIndices []int) { +func setRequestParams(request, params map[string]any, paramsMapper map[string]bidderparams.BidderParamMapper, paramIndices []int) bool { + updatedRequest := false for paramName, paramValue := range params { paramMapper, ok := paramsMapper[paramName] if !ok { continue } // add index in path by replacing # macro - location := addIndicesInPath(paramMapper.GetLocation(), paramIndices) + location := addIndicesInPath(paramMapper.Location, paramIndices) // set the value in the request according to the mapping details // remove the parameter from bidderParams after successful mapping if setValue(request, location, paramValue) { delete(params, paramName) + updatedRequest = true } } + return updatedRequest } // addIndicesInPath updates the path by replacing # by arrayIndices @@ -29,7 +32,7 @@ func addIndicesInPath(path string, indices []int) []string { parts := strings.Split(path, ".") j := 0 for i, part := range parts { - if part == "#" { + if part == locationIndexMacro { if j >= len(indices) { break } diff --git a/adapters/ortbbidder/requestParamMapper_test.go b/adapters/ortbbidder/requestParamMapper_test.go index 12c2d053228..6b19bd906c8 100644 --- a/adapters/ortbbidder/requestParamMapper_test.go +++ b/adapters/ortbbidder/requestParamMapper_test.go @@ -53,8 +53,7 @@ func TestSetRequestParams(t *testing.T) { "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { - mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation("param") + mapper := bidderparams.BidderParamMapper{Location: "param"} return map[string]bidderparams.BidderParamMapper{ "param": mapper, } @@ -82,8 +81,7 @@ func TestSetRequestParams(t *testing.T) { "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { - mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation("imp.#.param") + mapper := bidderparams.BidderParamMapper{Location: "imp.#.param"} return map[string]bidderparams.BidderParamMapper{ "param": mapper, } @@ -115,8 +113,7 @@ func TestSetRequestParams(t *testing.T) { "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { - mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation("imp.#.param") + mapper := bidderparams.BidderParamMapper{Location: "imp.#.param"} return map[string]bidderparams.BidderParamMapper{ "param": mapper, } @@ -148,8 +145,7 @@ func TestSetRequestParams(t *testing.T) { "param": "value", }, paramsMapper: func() map[string]bidderparams.BidderParamMapper { - mapper := bidderparams.BidderParamMapper{} - mapper.SetLocation("imp.#.param") + mapper := bidderparams.BidderParamMapper{Location: "imp.#.param"} return map[string]bidderparams.BidderParamMapper{ "param": mapper, } diff --git a/adapters/ortbbidder/util.go b/adapters/ortbbidder/util.go index 9cb58358739..1aa5881b073 100644 --- a/adapters/ortbbidder/util.go +++ b/adapters/ortbbidder/util.go @@ -2,8 +2,6 @@ package ortbbidder import ( "strconv" - - "github.com/prebid/prebid-server/v2/errortypes" ) /* @@ -81,10 +79,3 @@ func getNode(requestNode map[string]any, key string) any { } return requestNode[key] } - -// newBadInputError returns the error of type bad-input -func newBadInputError(message string) error { - return &errortypes.BadInput{ - Message: message, - } -} From 7db6cea9e2023ac548d85b923da98f880d407884 Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Fri, 7 Jun 2024 16:53:36 +0530 Subject: [PATCH 4/9] fix unit test --- adapters/ortbbidder/bidderparams/config.go | 3 - .../ortbbidder/bidderparams/config_test.go | 30 - adapters/ortbbidder/ortbbidder.go | 2 +- adapters/ortbbidder/ortbbidder_test.go | 492 +------------ adapters/ortbbidder/requestBuilder.go | 6 - adapters/ortbbidder/requestBuilder_test.go | 683 ++++-------------- 6 files changed, 174 insertions(+), 1042 deletions(-) diff --git a/adapters/ortbbidder/bidderparams/config.go b/adapters/ortbbidder/bidderparams/config.go index eea6f87d209..36aa8282997 100644 --- a/adapters/ortbbidder/bidderparams/config.go +++ b/adapters/ortbbidder/bidderparams/config.go @@ -18,9 +18,6 @@ type BidderConfig struct { // setRequestParams sets the bidder specific requestParams func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) { - if bcfg.bidderConfigMap == nil { - bcfg.bidderConfigMap = make(map[string]*config) - } if _, found := bcfg.bidderConfigMap[bidderName]; !found { bcfg.bidderConfigMap[bidderName] = &config{} } diff --git a/adapters/ortbbidder/bidderparams/config_test.go b/adapters/ortbbidder/bidderparams/config_test.go index 9ba737ecd96..f4b80ee7843 100644 --- a/adapters/ortbbidder/bidderparams/config_test.go +++ b/adapters/ortbbidder/bidderparams/config_test.go @@ -16,7 +16,6 @@ func TestSetRequestParams(t *testing.T) { } type want struct { bidderCfg *BidderConfig - err error } tests := []struct { name string @@ -24,35 +23,6 @@ func TestSetRequestParams(t *testing.T) { args args want want }{ - { - name: "bidderConfigMap_is_nil", - fields: fields{ - bidderConfig: &BidderConfig{ - bidderConfigMap: nil, - }, - }, - args: args{ - bidderName: "test", - requestParams: map[string]BidderParamMapper{ - "adunit": { - Location: "ext.adunit", - }, - }, - }, - want: want{ - bidderCfg: &BidderConfig{ - bidderConfigMap: map[string]*config{ - "test": { - requestParams: map[string]BidderParamMapper{ - "adunit": { - Location: "ext.adunit", - }, - }, - }, - }, - }, - }, - }, { name: "bidderName_not_found", fields: fields{ diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index 3e57c85b655..12420c4e75c 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -64,12 +64,12 @@ func (o *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte if o.bidderParamsConfig == nil { return nil, []error{newBadInputError(errNilBidderParamCfg.Error())} } - requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) requestBuilder := newRequestBuilder(o.adapterInfo.extraInfo.RequestMode, o.Endpoint) err := requestBuilder.parseRequest(request) if err != nil { return nil, []error{newBadInputError(err.Error())} } + requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) return requestBuilder.makeRequest(o.endpointTemplate, requestParams) } diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go index 88892939666..51852955842 100644 --- a/adapters/ortbbidder/ortbbidder_test.go +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -33,6 +33,15 @@ func TestMakeRequests(t *testing.T) { args args want want }{ + { + name: "request_is_nil", + args: args{ + bidderCfg: &bidderparams.BidderConfig{}, + }, + want: want{ + errors: []error{newBadInputError(errImpMissing.Error())}, + }, + }, { name: "bidderParamsConfig_is_nil", args: args{ @@ -98,7 +107,7 @@ func TestMakeRequests(t *testing.T) { { Method: http.MethodPost, Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -107,7 +116,7 @@ func TestMakeRequests(t *testing.T) { { Method: http.MethodPost, Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -137,8 +146,8 @@ func TestMakeRequests(t *testing.T) { requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -146,8 +155,8 @@ func TestMakeRequests(t *testing.T) { }, { Method: http.MethodPost, - Uri: "http://localhost.com", - Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), + Uri: "http://", + Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -228,7 +237,6 @@ func TestMakeRequests(t *testing.T) { }) } } - func TestMakeBids(t *testing.T) { type args struct { request *openrtb2.BidRequest @@ -611,473 +619,3 @@ func TestIsORTBBidder(t *testing.T) { }) } } - -func TestMakeRequest(t *testing.T) { - type fields struct { - endpointTemplate *template.Template - } - type args struct { - rawRequest json.RawMessage - bidderParamMapper map[string]bidderparams.BidderParamMapper - supportSingleImpInRequest bool - } - type want struct { - requestData []*adapters.RequestData - errs []error - } - tests := []struct { - name string - fields fields - args args - want want - }{ - { - name: "nil_request", - fields: fields{}, - args: args{ - rawRequest: nil, - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to unmarshal request, err:expect { or n, but found \x00")}, - }, - }, - { - name: "no_imp_object", - fields: fields{}, - args: args{ - rawRequest: json.RawMessage(`{}`), - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("imp object not found in request")}, - }, - }, - { - name: "invalid_imp_object", - fields: fields{}, - args: args{ - rawRequest: json.RawMessage(`{"imp":["invalid"]}`), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "", - Body: json.RawMessage(`{"imp":["invalid"]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: []error{newBadInputError("invalid imp object found at index:0")}, - }, - }, - // { - // name: "multiRequestMode_replace_macros_to_form_endpoint_url", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "multiRequestMode_macros_value_absent_in_bidder_params", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http:///publisher/", - // Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "multiRequestMode_macro_replacement_failure", - // fields: fields{ - // endpointTemplate: func() *template.Template { - // errorFunc := template.FuncMap{ - // "errorFunc": func() (string, error) { - // return "", errors.New("intentional error") - // }, - // } - // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - // return t - // }(), - // }, - // args: args{ - // supportSingleImpInRequest: false, - // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - // }, - // want: want{ - // requestData: nil, - // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - // }, - // }, - // { - // name: "multiRequestMode_first_imp_bidder_params_has_high_priority_while_replacing_macros_in_endpoint", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher", - // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"host":"imp2.host.com"}},"id":"imp_2"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "map_bidder_params_in_single_imp", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "multiRequestMode_map_bidder_params_in_multi_imp", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // tagMapper := bidderparams.BidderParamMapper{} - // tagMapper.SetLocation("imp.#.tagid") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // "tagid": tagMapper, - // } - // }(), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "multiRequestMode_first_imp_bidder_param_has_high_pririty", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_2"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "bidder_param_mapping_absent", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // bidderParamMapper: nil, - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "singleRequestMode_single_imp_request", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // supportSingleImpInRequest: true, - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp1.host.com/publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "singleRequestMode_multi_imps_request", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // supportSingleImpInRequest: true, - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp2.host.com/publisher/2222", - // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // { - // Method: http.MethodPost, - // Uri: "http://imp1.host.com/publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "singleRequestMode_multi_imps_request_with_one_invalid_imp", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // supportSingleImpInRequest: true, - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp1.host.com/publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: []error{newBadInputError("invalid imp object found at index:1")}, - // }, - // }, - // { - // name: "singleRequestMode_one_imp_updates_request_level_param_but_another_imp_does_not_update", - // fields: fields{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // args: args{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{} - // hostMapper.SetLocation("host") - // extMapper := bidderparams.BidderParamMapper{} - // extMapper.SetLocation("device") - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // supportSingleImpInRequest: true, - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp2.host.com/publisher/2222", - // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // { - // Method: http.MethodPost, - // Uri: "http:///publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "singleRequestMode_macro_replacement_failure", - // fields: fields{ - // endpointTemplate: func() *template.Template { - // errorFunc := template.FuncMap{ - // "errorFunc": func() (string, error) { - // return "", errors.New("intentional error") - // }, - // } - // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - // return t - // }(), - // }, - // args: args{ - // supportSingleImpInRequest: true, - // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - // }, - // want: want{ - // requestData: nil, - // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - // }, - // }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // o := adapterInfo{ - // endpointTemplate: tt.fields.endpointTemplate, - // } - // requestData, errs := o.makeRequest(tt.args.rawRequest, tt.args.bidderParamMapper, tt.args.supportSingleImpInRequest) - // assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - // assert.Equalf(t, tt.want.errs, errs, "mismatched errs") - }) - } -} diff --git a/adapters/ortbbidder/requestBuilder.go b/adapters/ortbbidder/requestBuilder.go index 3e9f6d14b26..f198d220ad4 100644 --- a/adapters/ortbbidder/requestBuilder.go +++ b/adapters/ortbbidder/requestBuilder.go @@ -39,11 +39,6 @@ func (reqBuilder *requestBuilder) parseRequest(request *openrtb2.BidRequest) (er if !ok { return errImpMissing } - for impIndex := range reqBuilder.imps { - imp := reqBuilder.imps[impIndex].(map[string]any) - delete(imp, "id") - } - return } @@ -157,7 +152,6 @@ func (reqBuilder *multiRequestModeBuilder) makeRequest(endpointTemplate *templat requestData []*adapters.RequestData foundValidImp bool ) - // reqBuilder.requestNode[impKey] = reqBuilder.imps // iterate through imps in reverse order to ensure setRequestParams prioritizes // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. for impIndex := len(reqBuilder.imps) - 1; impIndex >= 0; impIndex-- { diff --git a/adapters/ortbbidder/requestBuilder_test.go b/adapters/ortbbidder/requestBuilder_test.go index 31ecb2bf642..7dada8b95db 100644 --- a/adapters/ortbbidder/requestBuilder_test.go +++ b/adapters/ortbbidder/requestBuilder_test.go @@ -11,231 +11,9 @@ import ( "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" - "github.com/prebid/prebid-server/v2/config" "github.com/stretchr/testify/assert" ) -func TestMakeRequests_(t *testing.T) { - type args struct { - request *openrtb2.BidRequest - requestInfo *adapters.ExtraRequestInfo - adapterInfo adapterInfo - bidderCfg *bidderparams.BidderConfig - } - type want struct { - requestData []*adapters.RequestData - errors []error - } - tests := []struct { - name string - args args - want want - }{ - { - name: "request_is_nil", - args: args{ - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - errors: []error{newBadInputError(errImpMissing.Error())}, - }, - }, - { - name: "bidderParamsConfig_is_nil", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, - }, - adapterInfo: adapterInfo{config.Adapter{Endpoint: "http://test_bidder.com"}, extraAdapterInfo{RequestMode: "single"}, "testbidder", nil}, - bidderCfg: nil, - }, - want: want{ - errors: []error{newBadInputError("found nil bidderParamsConfig")}, - }, - }, - { - name: "bidderParamsConfig_not_contains_bidder_param_data", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{{ID: "imp1", TagID: "tag1"}}, - }, - adapterInfo: func() adapterInfo { - endpoint := "http://test_bidder.com" - template, _ := template.New("endpointTemplate").Parse(endpoint) - return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} - }(), - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errors: nil, - }, - }, - { - name: "single_requestmode_to_form_requestdata", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{ - {ID: "imp1", TagID: "tag1"}, - {ID: "imp2", TagID: "tag2"}, - }, - }, - adapterInfo: func() adapterInfo { - endpoint := "http://test_bidder.com" - template, _ := template.New("endpointTemplate").Parse(endpoint) - return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} - }(), - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - }, - }, - { - name: "single_requestmode_validate_endpoint_macro", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{ - {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, - {ID: "imp2", TagID: "tag2"}, - }, - }, - adapterInfo: func() adapterInfo { - endpoint := "http://{{.host}}" - template, _ := template.New("endpointTemplate").Parse(endpoint) - return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} - }(), - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp2","tagid":"tag2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http://localhost.com", - Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - }, - }, - { - name: "multi_requestmode_to_form_requestdata", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{ - {ID: "imp1", TagID: "tag1"}, - {ID: "imp2", TagID: "tag2"}, - }, - }, - adapterInfo: func() adapterInfo { - endpoint := "http://test_bidder.com" - template, _ := template.New("endpointTemplate").Parse(endpoint) - return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} - }(), - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://test_bidder.com", - Body: []byte(`{"id":"reqid","imp":[{"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - }, - }, - { - name: "multi_requestmode_validate_endpoint_macros", - args: args{ - request: &openrtb2.BidRequest{ - ID: "reqid", - Imp: []openrtb2.Imp{ - {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, - {ID: "imp2", TagID: "tag2"}, - }, - }, - adapterInfo: func() adapterInfo { - endpoint := "http://{{.host}}" - template, _ := template.New("endpointTemplate").Parse(endpoint) - return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} - }(), - bidderCfg: &bidderparams.BidderConfig{}, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com", - Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{"host":"localhost.com"}},"id":"imp1","tagid":"tag1"},{"id":"imp2","tagid":"tag2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - adapter := &adapter{adapterInfo: tt.args.adapterInfo, bidderParamsConfig: tt.args.bidderCfg} - requestData, errors := adapter.MakeRequests(tt.args.request, tt.args.requestInfo) - assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - assert.Equalf(t, tt.want.errors, errors, "mismatched errors") - }) - } -} - func TestParseRequest(t *testing.T) { type args struct { request *openrtb2.BidRequest @@ -292,7 +70,6 @@ func TestParseRequest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { reqBuilder := &requestBuilder{} err := reqBuilder.parseRequest(tt.args.request) - assert.Equalf(t, tt.want.err, err, "mismatched error") assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") assert.Equalf(t, tt.want.requestNode, reqBuilder.requestNode, "mismatched requestNode") @@ -712,8 +489,8 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -721,8 +498,8 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { }, { Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, @@ -918,157 +695,114 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { args args want want }{ - // { - // name: "no_imp_object_in_builder", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // rawRequest: json.RawMessage(`{}`), - // }, - // }, - // args: args{ - // endpointTemplate: nil, - // }, - // want: want{ - // requestData: nil, - // errs: nil, - // }, - // }, - // { - // name: "invalid_imp_object", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // rawRequest: json.RawMessage(`{"imp":["invalid"]}`), - // imps: []any{"invalid"}, - // }, - // }, - // args: args{}, - // want: want{ - // requestData: nil, - // errs: []error{newBadInputError("invalid imp object found at index:0")}, - // }, - // }, - // { - // name: "replace_macros_to_form_endpoint_url", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // imps: []any{ - // map[string]any{ - // "id": "imp_1", - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 5890, - // }, - // "host": "localhost.com", - // }, - // }, - // }, - // }, - // requestNode: map[string]any{ - // "imp": []any{ - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 5890, - // }, - // "host": "localhost.com", - // }, - // }, - // "id": "imp_1", - // }, - // }, - // }, - // hasMacrosInEndpoint: true, - // }, - // }, - // args: args{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://localhost.com/publisher/5890", - // Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, - // { - // name: "buildEndpoint_returns_error", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // hasMacrosInEndpoint: true, - // rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - // requestNode: map[string]any{ - // "imp": []any{ - // map[string]any{ - // "ext": map[string]any{}, - // "id": "imp_1", - // }, - // }, - // }, - // imps: []any{ - // map[string]any{ - // "ext": map[string]any{}, - // "id": "imp_1", - // }, - // }, - // }, - // }, - // args: args{ - // endpointTemplate: func() *template.Template { - // errorFunc := template.FuncMap{ - // "errorFunc": func() (string, error) { - // return "", errors.New("intentional error") - // }, - // } - // t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - // return t - // }(), - // }, - // want: want{ - // requestData: nil, - // errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - // "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - // }, - // }, { - name: "multiRequestMode_map_bidder_params_in_multi_imp", + name: "no_imp_object_in_builder", fields: fields{ requestBuilder: &requestBuilder{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), - requestNode: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 5890, - }, - "host": "localhost.com", - }, - }, - "id": "imp_1", - }, - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "tagid": "valid_tag_id", + rawRequest: json.RawMessage(`{}`), + }, + }, + args: args{ + endpointTemplate: nil, + }, + want: want{ + requestData: nil, + errs: nil, + }, + }, + { + name: "invalid_imp_object", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: json.RawMessage(`{"imp":["invalid"]}`), + imps: []any{"invalid"}, + }, + }, + args: args{}, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp object found at index:0")}, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + requestBuilder: &requestBuilder{ + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + imps: []any{ + map[string]any{ + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, }, + "host": "localhost.com", }, - "id": "imp_2", }, }, }, + requestNode: map[string]any{}, + hasMacrosInEndpoint: true, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "buildEndpoint_returns_error", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + requestNode: map[string]any{}, + imps: []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + args: args{ + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "map_bidder_params_in_multi_imp", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), + requestNode: map[string]any{}, imps: []any{ map[string]any{ "ext": map[string]any{ @@ -1120,167 +854,66 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { errs: nil, }, }, - // { - // name: "multi_imps_request_with_one_invalid_imp", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // hasMacrosInEndpoint: true, - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - // requestNode: map[string]any{ - // "imp": []any{ - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 1111, - // }, - // "host": "imp1.host.com", - // }, - // }, - // "id": "imp_1", - // }, - // "invalid", - // }, - // }, - // imps: []any{ - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 1111, - // }, - // "host": "imp1.host.com", - // }, - // }, - // "id": "imp_1", - // }, - // "invalid", - // }, - // }, - // }, - // args: args{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{Location: "host"} - // extMapper := bidderparams.BidderParamMapper{Location: "device"} - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp1.host.com/publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: []error{newBadInputError("invalid imp object found at index:1")}, - // }, - // }, - // { - // name: "one_imp_updates_request_level_param_but_another_imp_does_not_update", - // fields: fields{ - // requestBuilder: &requestBuilder{ - // hasMacrosInEndpoint: true, - // rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - // requestNode: map[string]any{ - // "imp": []any{ - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 1111, - // }, - // }, - // }, - // "id": "imp_1", - // }, - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 2222, - // }, - // "host": "imp2.host.com", - // }, - // }, - // "id": "imp_2", - // }, - // }, - // }, - // imps: []any{ - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 1111, - // }, - // }, - // }, - // "id": "imp_1", - // }, - // map[string]any{ - // "ext": map[string]any{ - // "bidder": map[string]any{ - // "ext": map[string]any{ - // "pubid": 2222, - // }, - // "host": "imp2.host.com", - // }, - // }, - // "id": "imp_2", - // }, - // }, - // }, - // }, - // args: args{ - // endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - // bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - // hostMapper := bidderparams.BidderParamMapper{Location: "host"} - // extMapper := bidderparams.BidderParamMapper{Location: "device"} - // return map[string]bidderparams.BidderParamMapper{ - // "host": hostMapper, - // "ext": extMapper, - // } - // }(), - // }, - // want: want{ - // requestData: []*adapters.RequestData{ - // { - // Method: http.MethodPost, - // Uri: "http://imp2.host.com/publisher/2222", - // Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // { - // Method: http.MethodPost, - // Uri: "http:///publisher/1111", - // Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - // Headers: http.Header{ - // "Content-Type": {"application/json;charset=utf-8"}, - // "Accept": {"application/json"}, - // }, - // }, - // }, - // errs: nil, - // }, - // }, + { + name: "multi_imps_request_with_one_invalid_imp", + fields: fields{ + requestBuilder: &requestBuilder{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), + requestNode: map[string]any{}, + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + "invalid", + }, + }, + }, + args: args{ + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + tagMapper := bidderparams.BidderParamMapper{Location: "imp.#.tagid"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + "tagid": tagMapper, + } + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},"invalid"]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: []error{newBadInputError("invalid imp object found at index:1")}, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { builder := &multiRequestModeBuilder{ requestBuilder: tt.fields.requestBuilder, } + if builder.requestNode != nil { + builder.requestNode[impKey] = builder.imps + } requestData, errs := builder.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") assert.Equalf(t, tt.want.errs, errs, "mismatched errs") From 5cd22ddc6536f0b461c2a99e3f7623465f73c40a Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Mon, 10 Jun 2024 16:51:57 +0530 Subject: [PATCH 5/9] remove commentsd --- adapters/ortbbidder/requestBuilder_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/adapters/ortbbidder/requestBuilder_test.go b/adapters/ortbbidder/requestBuilder_test.go index 7dada8b95db..5794f9f1368 100644 --- a/adapters/ortbbidder/requestBuilder_test.go +++ b/adapters/ortbbidder/requestBuilder_test.go @@ -29,16 +29,16 @@ func TestParseRequest(t *testing.T) { args args want want }{ - // { - // name: "request_is_nil", - // args: args{ - // request: nil, - // }, - // want: want{ - // err: errImpMissing, - // rawRequest: json.RawMessage(`null`), - // }, - // }, + { + name: "request_is_nil", + args: args{ + request: nil, + }, + want: want{ + err: errImpMissing, + rawRequest: json.RawMessage(`null`), + }, + }, { name: "request_is_valid", args: args{ From d391720b898eaedc699d4afa35ccda843bd3d8a1 Mon Sep 17 00:00:00 2001 From: Viral Vala Date: Mon, 10 Jun 2024 19:20:43 +0530 Subject: [PATCH 6/9] OTT-1799 incorporated code review comments --- adapters/ortbbidder/multiRequestBuilder.go | 82 +++++++++ adapters/ortbbidder/ortbbidder.go | 15 +- adapters/ortbbidder/requestBuilder.go | 188 ++++---------------- adapters/ortbbidder/requestBuilder_test.go | 82 ++++----- adapters/ortbbidder/singleRequestBuilder.go | 67 +++++++ 5 files changed, 237 insertions(+), 197 deletions(-) create mode 100644 adapters/ortbbidder/multiRequestBuilder.go create mode 100644 adapters/ortbbidder/singleRequestBuilder.go diff --git a/adapters/ortbbidder/multiRequestBuilder.go b/adapters/ortbbidder/multiRequestBuilder.go new file mode 100644 index 00000000000..78809617aac --- /dev/null +++ b/adapters/ortbbidder/multiRequestBuilder.go @@ -0,0 +1,82 @@ +package ortbbidder + +import ( + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + +// struct to build the request for single request mode where single imp is supported in a request +type multiRequestBuilder struct { + requestBuilderImpl + imps []map[string]any +} + +// parseRequest parse the incoming request and populates intermediate fields required for building requestData object +func (rb *multiRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err error) { + if len(request.Imp) == 0 { + //set errors + return err + } + + //get rawrequests without impression objects + tmpImp := request.Imp[0:] + request.Imp = nil + if rb.rawRequest, err = jsonutil.Marshal(request); err != nil { + return err + } + // request.Imp = tmpImp[0:] //resetting is not required + + //cache impression from request + data, err := jsonutil.Marshal(tmpImp) + if err != nil { + return err + } + if err = jsonutil.Unmarshal(data, rb.imps); err != nil { + return err + } + + return nil +} + +// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. +// it processes a request to generate 'N' RequestData objects, one for each of the 'N' impressions +func (rb *multiRequestBuilder) makeRequest() (requestData []*adapters.RequestData, errs []error) { + var ( + endpoint string + newRequest map[string]any + err error + requestCloneRequired bool + ) + + requestCloneRequired = true + + for index := range rb.imps { + //step 1: clone request + if requestCloneRequired { + if newRequest, err = cloneRequest(rb.rawRequest); err != nil { + continue + } + } + + //step 2: get impression extension + // set "imp" object in request to empty to improve performance while creating deep copy of request + imp := rb.imps[index] + bidderParams := getImpExtBidderParams(imp) + + //step 3: get endpoint + if endpoint, err = rb.getEndpoint(bidderParams); err != nil { + continue + } + + //step 4: update the request object by mapping bidderParams at expected location. + newRequest[impKey] = []any{imp} + requestCloneRequired = setRequestParams(newRequest, bidderParams, rb.requestParams, []int{0}) + + //step 5: append new request data + if requestData, err = appendRequestData(requestData, newRequest, endpoint); err != nil { + errs = append(errs, newBadInputError(err.Error())) + } + } + return requestData, errs +} diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index 12420c4e75c..2901d59efdd 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -64,13 +64,18 @@ func (o *adapter) MakeRequests(request *openrtb2.BidRequest, requestInfo *adapte if o.bidderParamsConfig == nil { return nil, []error{newBadInputError(errNilBidderParamCfg.Error())} } - requestBuilder := newRequestBuilder(o.adapterInfo.extraInfo.RequestMode, o.Endpoint) - err := requestBuilder.parseRequest(request) - if err != nil { + + requestBuilder := newRequestBuilder( + o.adapterInfo.extraInfo.RequestMode, + o.Endpoint, + o.endpointTemplate, + o.bidderParamsConfig.GetRequestParams(o.bidderName.String())) + + if err := requestBuilder.parseRequest(request); err != nil { return nil, []error{newBadInputError(err.Error())} } - requestParams := o.bidderParamsConfig.GetRequestParams(o.bidderName.String()) - return requestBuilder.makeRequest(o.endpointTemplate, requestParams) + + return requestBuilder.makeRequest() } // MakeBids prepares bidderResponse from the oRTB bidder server's http.Response diff --git a/adapters/ortbbidder/requestBuilder.go b/adapters/ortbbidder/requestBuilder.go index f198d220ad4..0bf9976b2f2 100644 --- a/adapters/ortbbidder/requestBuilder.go +++ b/adapters/ortbbidder/requestBuilder.go @@ -2,12 +2,10 @@ package ortbbidder import ( "encoding/json" - "fmt" "net/http" "strings" "text/template" - "github.com/buger/jsonparser" "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" @@ -15,180 +13,68 @@ import ( "github.com/prebid/prebid-server/v2/util/jsonutil" ) -// requestBuilder is a struct used for constructing RequestData object -type requestBuilder struct { - rawRequest json.RawMessage - requestNode map[string]any - imps []any - endpoint string - hasMacrosInEndpoint bool -} - -// parseRequest parse the incoming request and populates intermediate fields required for building requestData object -func (reqBuilder *requestBuilder) parseRequest(request *openrtb2.BidRequest) (err error) { - reqBuilder.rawRequest, err = jsonutil.Marshal(request) - if err != nil { - return fmt.Errorf("failed to marshal request, err:%s", err.Error()) - } - reqBuilder.requestNode, err = reqBuilder.buildRequestNode() - if err != nil { - return err - } - var ok bool - reqBuilder.imps, ok = reqBuilder.requestNode[impKey].([]any) - if !ok { - return errImpMissing - } - return -} - -// buildRequestNode creates request-map by unmarshaling request-bytes -func (reqBuilder *requestBuilder) buildRequestNode() (requestNode map[string]any, err error) { - err = jsonutil.Unmarshal(reqBuilder.rawRequest, &requestNode) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal request, err:%s", err.Error()) - } - return requestNode, err -} - -// buildEndpoint builds the adapter endpoint, if required it replaces the macros present in endpoint -func (reqBuilder *requestBuilder) buildEndpoint(endpointTemplate *template.Template, bidderParams map[string]any) (string, error) { - if !reqBuilder.hasMacrosInEndpoint { - return reqBuilder.endpoint, nil - } - uri, err := macros.ResolveMacros(endpointTemplate, bidderParams) - if err != nil { - return uri, fmt.Errorf("failed to replace macros in endpoint, err:%s", err.Error()) - } - uri = strings.ReplaceAll(uri, urlMacroNoValue, "") - return uri, err +// requestBuilder is an interface containing parseRequest, makeRequest functions +type requestBuilder interface { + parseRequest(*openrtb2.BidRequest) error + makeRequest() ([]*adapters.RequestData, []error) } -// requestModeBuilder is an interface containing parseRequest, makeRequest functions -type requestModeBuilder interface { - parseRequest(*openrtb2.BidRequest) error - makeRequest(*template.Template, map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) +// requestBuilderImpl is a struct used for constructing RequestData object +type requestBuilderImpl struct { + endpoint string + endpointTemplate *template.Template + requestParams map[string]bidderparams.BidderParamMapper + hasMacrosInEndpoint bool + rawRequest json.RawMessage } // newRequestBuilder returns the request-builder based on requestMode argument -func newRequestBuilder(requestMode, endpoint string) requestModeBuilder { - requestBuilder := requestBuilder{ +func newRequestBuilder(requestMode, endpoint string, endpointTemplate *template.Template, requestParams map[string]bidderparams.BidderParamMapper) requestBuilder { + requestBuilder := requestBuilderImpl{ endpoint: endpoint, + endpointTemplate: endpointTemplate, + requestParams: requestParams, hasMacrosInEndpoint: strings.Contains(endpoint, urlMacroPrefix), } + if requestMode == requestModeSingle { - return &singleRequestModeBuilder{&requestBuilder} + return &multiRequestBuilder{ + requestBuilderImpl: requestBuilder, + } } - return &multiRequestModeBuilder{&requestBuilder} -} -// struct to build the request for single request mode where single imp is supported in a request -type singleRequestModeBuilder struct { - *requestBuilder + return &singleRequestBuilder{ + requestBuilderImpl: requestBuilder, + } } -// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. -// it processes a request to generate 'N' RequestData objects, one for each of the 'N' impressions -func (reqBuilder *singleRequestModeBuilder) makeRequest(endpointTemplate *template.Template, - paramsMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { - // set "imp" object in request to empty to improve performance while creating deep copy of request - var err error - reqBuilder.rawRequest, err = jsonparser.Set(reqBuilder.rawRequest, []byte("[]"), impKey) - if err != nil { - return nil, []error{newBadInputError(errImpSetToEmpty.Error())} - } - var ( - uri string - errs []error - requestData []*adapters.RequestData - ) - for impIndex := range reqBuilder.imps { - imp, ok := reqBuilder.imps[impIndex].(map[string]any) - if !ok || imp == nil { - errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) - continue - } - bidderParams := getImpExtBidderParams(imp) - // build endpoint-url from bidder-params, it must be done before calling setRequestParams, as it removes the imp.ext.bidder parameters. - uri, err = reqBuilder.buildEndpoint(endpointTemplate, bidderParams) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - continue - } - // override "imp" key in request to ensure request contains single imp - reqBuilder.requestNode[impKey] = []any{imp} - // update the request object by mapping bidderParams at expected location. - updatedRequest := setRequestParams(reqBuilder.requestNode, bidderParams, paramsMapper, []int{0}) - requestData, err = appendRequestData(requestData, reqBuilder.requestNode, uri) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - } - // create a deep copy of the request to ensure common fields are not altered. - // example - if imp2 modifies the original req.bcat field using its bidder-params, imp1 should still be able to use the original req.bcat value. - if impIndex != 0 && updatedRequest { - reqBuilder.requestNode, err = reqBuilder.buildRequestNode() - if err != nil { - errs = append(errs, newBadInputError(fmt.Sprintf("failed to build request from rawRequest, err:%s", err.Error()))) - return requestData, errs - } - } +func (rb *requestBuilderImpl) getEndpoint(values map[string]any) (string, error) { + if !rb.hasMacrosInEndpoint { + return rb.endpoint, nil } - return requestData, errs -} -// struct to build the request for multi request mode where single request supports multiple impressions -type multiRequestModeBuilder struct { - *requestBuilder + uri, err := macros.ResolveMacros(rb.endpointTemplate, values) + if err != nil { + return uri, err + } + uri = strings.ReplaceAll(uri, urlMacroNoValue, "") + return uri, err } -// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. -// it create single RequestData object for all impressions. -func (reqBuilder *multiRequestModeBuilder) makeRequest(endpointTemplate *template.Template, - paramsMapper map[string]bidderparams.BidderParamMapper) ([]*adapters.RequestData, []error) { - var ( - uri string - err error - errs []error - requestData []*adapters.RequestData - foundValidImp bool - ) - // iterate through imps in reverse order to ensure setRequestParams prioritizes - // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. - for impIndex := len(reqBuilder.imps) - 1; impIndex >= 0; impIndex-- { - imp, ok := reqBuilder.imps[impIndex].(map[string]any) - if !ok || imp == nil { - errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp object found at index:%d", impIndex))) - continue - } - bidderParams := getImpExtBidderParams(imp) - // build endpoint-url only once using first imp's bidder-params - if impIndex == 0 { - uri, err = reqBuilder.buildEndpoint(endpointTemplate, bidderParams) - if err != nil { - errs = append(errs, newBadInputError(err.Error())) - return nil, errs - } - } - // update the request object by mapping bidderParams at expected location. - setRequestParams(reqBuilder.requestNode, bidderParams, paramsMapper, []int{impIndex}) - foundValidImp = true - } - // if not single valid imp is found then return error - if !foundValidImp { - return nil, errs - } - requestData, err = appendRequestData(requestData, reqBuilder.requestNode, uri) +func cloneRequest(request json.RawMessage) (map[string]any, error) { + req := map[string]any{} + err := jsonutil.Unmarshal(request, &req) if err != nil { - errs = append(errs, newBadInputError(err.Error())) + return nil, err } - return requestData, errs + return req, nil } // appendRequestData creates new RequestData using request and uri then appends it to requestData passed as argument func appendRequestData(requestData []*adapters.RequestData, request map[string]any, uri string) ([]*adapters.RequestData, error) { rawRequest, err := jsonutil.Marshal(request) if err != nil { - return requestData, fmt.Errorf("failed to marshal request after setting bidder-params, err:%s", err.Error()) + return requestData, err } requestData = append(requestData, &adapters.RequestData{ Method: http.MethodPost, diff --git a/adapters/ortbbidder/requestBuilder_test.go b/adapters/ortbbidder/requestBuilder_test.go index 5794f9f1368..6308b203a5f 100644 --- a/adapters/ortbbidder/requestBuilder_test.go +++ b/adapters/ortbbidder/requestBuilder_test.go @@ -68,11 +68,11 @@ func TestParseRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reqBuilder := &requestBuilder{} + reqBuilder := &requestBuilderImpl{} err := reqBuilder.parseRequest(tt.args.request) assert.Equalf(t, tt.want.err, err, "mismatched error") assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") - assert.Equalf(t, tt.want.requestNode, reqBuilder.requestNode, "mismatched requestNode") + assert.Equalf(t, tt.want.requestNode, reqBuilder.request, "mismatched requestNode") assert.Equalf(t, tt.want.imps, reqBuilder.imps, "mismatched imps") }) } @@ -168,7 +168,7 @@ func TestBuildEndpoint(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reqBuilder := &requestBuilder{ + reqBuilder := &requestBuilderImpl{ endpoint: tt.fields.endpoint, hasMacrosInEndpoint: tt.fields.hasMacrosInEndpoint, } @@ -187,7 +187,7 @@ func TestNewRequestBuilder(t *testing.T) { tests := []struct { name string args args - want requestModeBuilder + want requestBuilder }{ { name: "singleRequestMode", @@ -195,8 +195,8 @@ func TestNewRequestBuilder(t *testing.T) { requestMode: requestModeSingle, endpoint: "http://localhost/publisher", }, - want: &singleRequestModeBuilder{ - &requestBuilder{ + want: &multiRequestBuilder{ + &requestBuilderImpl{ endpoint: "http://localhost/publisher", }, }, @@ -208,8 +208,8 @@ func TestNewRequestBuilder(t *testing.T) { requestMode: requestModeSingle, endpoint: "http://{{.host}}/publisher", }, - want: &singleRequestModeBuilder{ - &requestBuilder{ + want: &multiRequestBuilder{ + &requestBuilderImpl{ endpoint: "http://{{.host}}/publisher", hasMacrosInEndpoint: true, }, @@ -225,7 +225,7 @@ func TestNewRequestBuilder(t *testing.T) { func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { type fields struct { - requestBuilder *requestBuilder + requestBuilder *requestBuilderImpl } type args struct { endpointTemplate *template.Template @@ -244,7 +244,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "nil_request", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: nil, }, }, @@ -257,7 +257,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "no_imp_object_in_builder", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{}`), }, }, @@ -272,7 +272,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "invalid_imp_object", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{"imp":["invalid"]}`), imps: []any{"invalid"}, }, @@ -286,7 +286,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "replace_macros_to_form_endpoint_url", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), imps: []any{ map[string]any{ @@ -301,7 +301,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { }, }, }, - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{ @@ -340,10 +340,10 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "macros_value_absent_in_bidder_params", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{}, @@ -380,10 +380,10 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "buildEndpoint_returns_error", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{}, @@ -419,10 +419,10 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "multi_imps_request", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{ @@ -512,10 +512,10 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "multi_imps_request_with_one_invalid_imp", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{ @@ -576,10 +576,10 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { { name: "one_imp_updates_request_level_param_but_another_imp_does_not_update", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - requestNode: map[string]any{ + request: map[string]any{ "imp": []any{ map[string]any{ "ext": map[string]any{ @@ -667,8 +667,8 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sreq := &singleRequestModeBuilder{ - requestBuilder: tt.fields.requestBuilder, + sreq := &multiRequestBuilder{ + requestBuilderImpl: tt.fields.requestBuilder, } requestData, errs := sreq.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") @@ -679,7 +679,7 @@ func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { type fields struct { - requestBuilder *requestBuilder + requestBuilder *requestBuilderImpl } type args struct { endpointTemplate *template.Template @@ -698,7 +698,7 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "no_imp_object_in_builder", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{}`), }, }, @@ -713,7 +713,7 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "invalid_imp_object", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{"imp":["invalid"]}`), imps: []any{"invalid"}, }, @@ -727,7 +727,7 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "replace_macros_to_form_endpoint_url", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), imps: []any{ map[string]any{ @@ -742,7 +742,7 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { }, }, }, - requestNode: map[string]any{}, + request: map[string]any{}, hasMacrosInEndpoint: true, }, }, @@ -767,10 +767,10 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "buildEndpoint_returns_error", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - requestNode: map[string]any{}, + request: map[string]any{}, imps: []any{ map[string]any{ "ext": map[string]any{}, @@ -799,10 +799,10 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "map_bidder_params_in_multi_imp", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), - requestNode: map[string]any{}, + request: map[string]any{}, imps: []any{ map[string]any{ "ext": map[string]any{ @@ -857,10 +857,10 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { { name: "multi_imps_request_with_one_invalid_imp", fields: fields{ - requestBuilder: &requestBuilder{ + requestBuilder: &requestBuilderImpl{ hasMacrosInEndpoint: true, rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - requestNode: map[string]any{}, + request: map[string]any{}, imps: []any{ map[string]any{ "ext": map[string]any{ @@ -908,11 +908,11 @@ func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - builder := &multiRequestModeBuilder{ - requestBuilder: tt.fields.requestBuilder, + builder := &singleRequestBuilder{ + requestBuilderImpl: tt.fields.requestBuilder, } - if builder.requestNode != nil { - builder.requestNode[impKey] = builder.imps + if builder.request != nil { + builder.request[impKey] = builder.imps } requestData, errs := builder.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") diff --git a/adapters/ortbbidder/singleRequestBuilder.go b/adapters/ortbbidder/singleRequestBuilder.go new file mode 100644 index 00000000000..00255c7bf69 --- /dev/null +++ b/adapters/ortbbidder/singleRequestBuilder.go @@ -0,0 +1,67 @@ +package ortbbidder + +import ( + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/util/jsonutil" +) + +// struct to build the request for multi request mode where single request supports multiple impressions +type singleRequestBuilder struct { + requestBuilderImpl + newRequest map[string]any + imps []map[string]any +} + +// parseRequest parse the incoming request and populates intermediate fields required for building requestData object +func (rb *singleRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err error) { + rb.rawRequest, err = jsonutil.Marshal(request) + if err != nil { + return err + } + + rb.newRequest, err = cloneRequest(rb.rawRequest) + if err != nil { + return err + } + + imps, ok := rb.newRequest[impKey].([]map[string]any) + if !ok || len(imps) == 0 { + //TODO append error + return err + } + return +} + +// makeRequest constructs the endpoint URL and maps the bidder-parameters in request to create the RequestData objects. +// it create single RequestData object for all impressions. +func (rb *singleRequestBuilder) makeRequest() (requestData []*adapters.RequestData, errs []error) { + if len(rb.imps) == 0 { + return nil, errs + } + + var ( + endpoint string + newRequest map[string]any + err error + ) + + //step 1: get endpoint + if endpoint, err = rb.getEndpoint(getImpExtBidderParams(rb.imps[0])); err != nil { + errs = append(errs, newBadInputError(err.Error())) + return nil, errs + } + + //step 2: replace parameters + // iterate through imps in reverse order to ensure setRequestParams prioritizes + // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. + for index := len(rb.imps) - 1; index >= 0; index-- { + setRequestParams(newRequest, getImpExtBidderParams(rb.imps[index]), rb.requestParams, []int{index}) + } + + //step 3: append new request data + if requestData, err = appendRequestData(requestData, newRequest, endpoint); err != nil { + errs = append(errs, newBadInputError(err.Error())) + } + return requestData, errs +} From eb685f1461ff0383d38f15f9421ca76613d72474 Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Tue, 11 Jun 2024 11:44:23 +0530 Subject: [PATCH 7/9] adding UTs --- adapters/ortbbidder/errors.go | 1 - adapters/ortbbidder/multiRequestBuilder.go | 10 +- .../ortbbidder/multiRequestBuilder_test.go | 360 ++++++++ adapters/ortbbidder/requestBuilder.go | 8 +- adapters/ortbbidder/requestBuilder_test.go | 817 ++---------------- adapters/ortbbidder/singleRequestBuilder.go | 38 +- .../ortbbidder/singleRequestBuilder_test.go | 340 ++++++++ static/bidder-info/owortb_testbidder.yaml | 2 +- 8 files changed, 825 insertions(+), 751 deletions(-) create mode 100644 adapters/ortbbidder/multiRequestBuilder_test.go create mode 100644 adapters/ortbbidder/singleRequestBuilder_test.go diff --git a/adapters/ortbbidder/errors.go b/adapters/ortbbidder/errors.go index 775cdf33e50..b7528abdc10 100644 --- a/adapters/ortbbidder/errors.go +++ b/adapters/ortbbidder/errors.go @@ -9,7 +9,6 @@ import ( // list of constant errors var ( errImpMissing error = errors.New("imp object not found in request") - errImpSetToEmpty error = errors.New("failed to empty the imp key in request") errNilBidderParamCfg error = errors.New("found nil bidderParamsConfig") ) diff --git a/adapters/ortbbidder/multiRequestBuilder.go b/adapters/ortbbidder/multiRequestBuilder.go index 78809617aac..b8733f8ca19 100644 --- a/adapters/ortbbidder/multiRequestBuilder.go +++ b/adapters/ortbbidder/multiRequestBuilder.go @@ -6,7 +6,7 @@ import ( "github.com/prebid/prebid-server/v2/util/jsonutil" ) -// struct to build the request for single request mode where single imp is supported in a request +// struct to build the multi requests each containing sinlge impression when requestMode="single" type multiRequestBuilder struct { requestBuilderImpl imps []map[string]any @@ -15,8 +15,7 @@ type multiRequestBuilder struct { // parseRequest parse the incoming request and populates intermediate fields required for building requestData object func (rb *multiRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err error) { if len(request.Imp) == 0 { - //set errors - return err + return errImpMissing } //get rawrequests without impression objects @@ -32,7 +31,7 @@ func (rb *multiRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err e if err != nil { return err } - if err = jsonutil.Unmarshal(data, rb.imps); err != nil { + if err = jsonutil.Unmarshal(data, &rb.imps); err != nil { return err } @@ -55,17 +54,18 @@ func (rb *multiRequestBuilder) makeRequest() (requestData []*adapters.RequestDat //step 1: clone request if requestCloneRequired { if newRequest, err = cloneRequest(rb.rawRequest); err != nil { + errs = append(errs, newBadInputError(err.Error())) continue } } //step 2: get impression extension - // set "imp" object in request to empty to improve performance while creating deep copy of request imp := rb.imps[index] bidderParams := getImpExtBidderParams(imp) //step 3: get endpoint if endpoint, err = rb.getEndpoint(bidderParams); err != nil { + errs = append(errs, newBadInputError(err.Error())) continue } diff --git a/adapters/ortbbidder/multiRequestBuilder_test.go b/adapters/ortbbidder/multiRequestBuilder_test.go new file mode 100644 index 00000000000..2f3e178a0a1 --- /dev/null +++ b/adapters/ortbbidder/multiRequestBuilder_test.go @@ -0,0 +1,360 @@ +package ortbbidder + +import ( + "encoding/json" + "errors" + "net/http" + "testing" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" + "github.com/stretchr/testify/assert" +) + +func TestMultiRequestBuilderParseRequest(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + } + type want struct { + err error + rawRequest json.RawMessage + imps []map[string]any + } + tests := []struct { + name string + args args + want want + }{ + { + name: "request_without_imps", + args: args{ + request: &openrtb2.BidRequest{ + ID: "id", + }, + }, + want: want{ + err: errImpMissing, + rawRequest: nil, + imps: nil, + }, + }, + { + name: "request_is_valid", + args: args{ + request: &openrtb2.BidRequest{ + ID: "id", + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + }, + }, + }, + }, + want: want{ + err: nil, + rawRequest: json.RawMessage(`{"id":"id","imp":null}`), + imps: []map[string]any{{ + "id": "imp_1", + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reqBuilder := &multiRequestBuilder{} + err := reqBuilder.parseRequest(tt.args.request) + assert.Equalf(t, tt.want.err, err, "mismatched error") + assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") + assert.Equalf(t, tt.want.imps, reqBuilder.imps, "mismatched imps") + }) + } +} + +func TestMultiRequestBuilderMakeRequest(t *testing.T) { + type fields struct { + requestBuilder multiRequestBuilder + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + want want + }{ + { + name: "nil_request", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + rawRequest: nil, + }, + }, + }, + want: want{ + requestData: nil, + errs: nil, + }, + }, + { + name: "no_imp_object_in_builder", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + rawRequest: json.RawMessage(`{}`), + }, + }, + }, + want: want{ + requestData: nil, + errs: nil, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + imps: []map[string]any{ + { + "id": "imp_1", + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 5890, + }, + "host": "localhost.com", + }, + }, + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macros_value_absent_in_bidder_params", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), + }, + imps: []map[string]any{ + { + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/", + Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "buildEndpoint_returns_error", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + imps: []map[string]any{ + { + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "multi_imps_request", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + requestParams: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + imps: []map[string]any{ + { + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + { + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "one_imp_updates_request_level_param_but_another_imp_expects_original_request_param", + fields: fields{ + requestBuilder: multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + requestParams: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + imps: []map[string]any{ + { + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + }, + }, + "id": "imp_1", + }, + { + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://imp2.host.com/publisher/2222", + Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + requestData, errs := tt.fields.requestBuilder.makeRequest() + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} diff --git a/adapters/ortbbidder/requestBuilder.go b/adapters/ortbbidder/requestBuilder.go index 0bf9976b2f2..e784c0cbc5c 100644 --- a/adapters/ortbbidder/requestBuilder.go +++ b/adapters/ortbbidder/requestBuilder.go @@ -2,6 +2,7 @@ package ortbbidder import ( "encoding/json" + "fmt" "net/http" "strings" "text/template" @@ -19,7 +20,6 @@ type requestBuilder interface { makeRequest() ([]*adapters.RequestData, []error) } -// requestBuilderImpl is a struct used for constructing RequestData object type requestBuilderImpl struct { endpoint string endpointTemplate *template.Template @@ -36,26 +36,24 @@ func newRequestBuilder(requestMode, endpoint string, endpointTemplate *template. requestParams: requestParams, hasMacrosInEndpoint: strings.Contains(endpoint, urlMacroPrefix), } - if requestMode == requestModeSingle { return &multiRequestBuilder{ requestBuilderImpl: requestBuilder, } } - return &singleRequestBuilder{ requestBuilderImpl: requestBuilder, } } +// getEndpoint returns the endpoint-url, if required replaces macros func (rb *requestBuilderImpl) getEndpoint(values map[string]any) (string, error) { if !rb.hasMacrosInEndpoint { return rb.endpoint, nil } - uri, err := macros.ResolveMacros(rb.endpointTemplate, values) if err != nil { - return uri, err + return uri, fmt.Errorf("failed to replace macros in endpoint, err:%s", err.Error()) } uri = strings.ReplaceAll(uri, urlMacroNoValue, "") return uri, err diff --git a/adapters/ortbbidder/requestBuilder_test.go b/adapters/ortbbidder/requestBuilder_test.go index 6308b203a5f..da7240919f6 100644 --- a/adapters/ortbbidder/requestBuilder_test.go +++ b/adapters/ortbbidder/requestBuilder_test.go @@ -8,84 +8,65 @@ import ( "testing" "text/template" - "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" "github.com/stretchr/testify/assert" ) -func TestParseRequest(t *testing.T) { +func TestNewRequestBuilder(t *testing.T) { type args struct { - request *openrtb2.BidRequest - } - type want struct { - err error - rawRequest json.RawMessage - requestNode map[string]any - imps []any + requestMode string + endpoint string + endpointTemplate *template.Template + requestParams map[string]bidderparams.BidderParamMapper } tests := []struct { name string args args - want want + want requestBuilder }{ { - name: "request_is_nil", + name: "singleRequestMode", args: args{ - request: nil, + requestMode: requestModeSingle, + endpoint: "http://localhost/publisher", }, - want: want{ - err: errImpMissing, - rawRequest: json.RawMessage(`null`), + want: &multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + endpoint: "http://localhost/publisher", + }, }, }, + { - name: "request_is_valid", + name: "multiRequestMode", args: args{ - request: &openrtb2.BidRequest{ - ID: "id", - Imp: []openrtb2.Imp{ - { - ID: "imp_1", - }, - }, - }, + requestMode: requestModeSingle, + endpoint: "http://{{.host}}/publisher", }, - want: want{ - err: nil, - rawRequest: json.RawMessage(`{"id":"id","imp":[{"id":"imp_1"}]}`), - requestNode: map[string]any{ - "id": "id", - "imp": []any{map[string]any{ - "id": "imp_1", - }}, + want: &multiRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + endpoint: "http://{{.host}}/publisher", + hasMacrosInEndpoint: true, }, - imps: []any{map[string]any{ - "id": "imp_1", - }}, }, - }, - } + }} for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - reqBuilder := &requestBuilderImpl{} - err := reqBuilder.parseRequest(tt.args.request) - assert.Equalf(t, tt.want.err, err, "mismatched error") - assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") - assert.Equalf(t, tt.want.requestNode, reqBuilder.request, "mismatched requestNode") - assert.Equalf(t, tt.want.imps, reqBuilder.imps, "mismatched imps") + got := newRequestBuilder(tt.args.requestMode, tt.args.endpoint, tt.args.endpointTemplate, tt.args.requestParams) + assert.Equalf(t, tt.want, got, "mismacthed requestbuilder") }) } } -func TestBuildEndpoint(t *testing.T) { +func TestGetEndpoint(t *testing.T) { type fields struct { endpoint string hasMacrosInEndpoint bool + endpointTemplate *template.Template } type args struct { - endpointTemplate *template.Template - bidderParams map[string]any + bidderParams map[string]any } type want struct { err error @@ -102,10 +83,10 @@ func TestBuildEndpoint(t *testing.T) { fields: fields{ endpoint: "http://{{.host}}/publisher", hasMacrosInEndpoint: false, + endpointTemplate: nil, }, args: args{ - endpointTemplate: nil, - bidderParams: map[string]any{}, + bidderParams: map[string]any{}, }, want: want{ endpoint: "http://{{.host}}/publisher", @@ -116,10 +97,10 @@ func TestBuildEndpoint(t *testing.T) { fields: fields{ endpoint: "http://{{.host}}/publisher", hasMacrosInEndpoint: true, + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), }, args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), - bidderParams: map[string]any{}, + bidderParams: map[string]any{}, }, want: want{ endpoint: "http:///publisher", @@ -130,9 +111,9 @@ func TestBuildEndpoint(t *testing.T) { fields: fields{ endpoint: "http://{{.host}}/publisher", hasMacrosInEndpoint: true, + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), }, args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher`)), bidderParams: map[string]any{ "host": "localhost", }, @@ -146,9 +127,6 @@ func TestBuildEndpoint(t *testing.T) { fields: fields{ endpoint: "http://{{.errorFunc}}/publisher", hasMacrosInEndpoint: true, - }, - args: args{ - bidderParams: map[string]any{}, endpointTemplate: func() *template.Template { errorFunc := template.FuncMap{ "errorFunc": func() (string, error) { @@ -159,6 +137,9 @@ func TestBuildEndpoint(t *testing.T) { return template }(), }, + args: args{ + bidderParams: map[string]any{}, + }, want: want{ endpoint: "", err: fmt.Errorf("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + @@ -171,752 +152,136 @@ func TestBuildEndpoint(t *testing.T) { reqBuilder := &requestBuilderImpl{ endpoint: tt.fields.endpoint, hasMacrosInEndpoint: tt.fields.hasMacrosInEndpoint, + endpointTemplate: tt.fields.endpointTemplate, } - endpoint, err := reqBuilder.buildEndpoint(tt.args.endpointTemplate, tt.args.bidderParams) + endpoint, err := reqBuilder.getEndpoint(tt.args.bidderParams) assert.Equalf(t, tt.want.endpoint, endpoint, "mismatched endpoint") assert.Equalf(t, tt.want.err, err, "mismatched error") }) } } -func TestNewRequestBuilder(t *testing.T) { +func TestCloneRequest(t *testing.T) { type args struct { - requestMode string - endpoint string - } - tests := []struct { - name string - args args - want requestBuilder - }{ - { - name: "singleRequestMode", - args: args{ - requestMode: requestModeSingle, - endpoint: "http://localhost/publisher", - }, - want: &multiRequestBuilder{ - &requestBuilderImpl{ - endpoint: "http://localhost/publisher", - }, - }, - }, - - { - name: "multiRequestMode", - args: args{ - requestMode: requestModeSingle, - endpoint: "http://{{.host}}/publisher", - }, - want: &multiRequestBuilder{ - &requestBuilderImpl{ - endpoint: "http://{{.host}}/publisher", - hasMacrosInEndpoint: true, - }, - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := newRequestBuilder(tt.args.requestMode, tt.args.endpoint) - assert.Equalf(t, tt.want, got, "mismacthed requestbuilder") - }) - } -} - -func Test_singleRequestModeBuilder_makeRequest(t *testing.T) { - type fields struct { - requestBuilder *requestBuilderImpl - } - type args struct { - endpointTemplate *template.Template - bidderParamMapper map[string]bidderparams.BidderParamMapper + request json.RawMessage } type want struct { - requestData []*adapters.RequestData - errs []error + requestNode map[string]any + err error } tests := []struct { - name string - fields fields - args args - want want + name string + args args + want want }{ { - name: "nil_request", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: nil, - }, - }, - args: args{}, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to empty the imp key in request")}, - }, - }, - { - name: "no_imp_object_in_builder", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{}`), - }, - }, + name: "clone_request", args: args{ - endpointTemplate: nil, + request: json.RawMessage(`{"id":"reqId","imps":[{"id":"impId"}]}`), }, want: want{ - requestData: nil, - errs: nil, - }, - }, - { - name: "invalid_imp_object", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{"imp":["invalid"]}`), - imps: []any{"invalid"}, - }, - }, - args: args{}, - want: want{ - requestData: nil, - errs: []error{newBadInputError("invalid imp object found at index:0")}, - }, - }, - { - name: "replace_macros_to_form_endpoint_url", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - imps: []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 5890, - }, - "host": "localhost.com", - }, - }, - }, - }, - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 5890, - }, - "host": "localhost.com", - }, - }, - "id": "imp_1", - }, - }, - }, - hasMacrosInEndpoint: true, - }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "macros_value_absent_in_bidder_params", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{}, - "id": "imp_1", - }, - }, - }, - imps: []any{ - map[string]any{ - "ext": map[string]any{}, - "id": "imp_1", - }, - }, - }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http:///publisher/", - Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "buildEndpoint_returns_error", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{}, - "id": "imp_1", - }, - }, - }, - imps: []any{ - map[string]any{ - "ext": map[string]any{}, - "id": "imp_1", - }, - }, - }, - }, - args: args{ - endpointTemplate: func() *template.Template { - errorFunc := template.FuncMap{ - "errorFunc": func() (string, error) { - return "", errors.New("intentional error") - }, - } - t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - return t - }(), - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - }, - }, - { - name: "multi_imps_request", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - "host": "imp1.host.com", - }, - }, - "id": "imp_1", - }, - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 2222, - }, - "host": "imp2.host.com", - }, - }, - "id": "imp_2", - }, - }, - }, - imps: []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - "host": "imp1.host.com", - }, - }, - "id": "imp_1", - }, - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 2222, - }, - "host": "imp2.host.com", - }, - }, - "id": "imp_2", - }, - }, - }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{Location: "host"} - extMapper := bidderparams.BidderParamMapper{Location: "device"} - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, - }, - { - name: "multi_imps_request_with_one_invalid_imp", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - "host": "imp1.host.com", - }, - }, - "id": "imp_1", - }, - "invalid", - }, - }, - imps: []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - "host": "imp1.host.com", - }, - }, - "id": "imp_1", - }, - "invalid", - }, - }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{Location: "host"} - extMapper := bidderparams.BidderParamMapper{Location: "device"} - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: []error{newBadInputError("invalid imp object found at index:1")}, - }, - }, - { - name: "one_imp_updates_request_level_param_but_another_imp_does_not_update", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111}}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), - request: map[string]any{ - "imp": []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - }, - }, - "id": "imp_1", - }, - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 2222, - }, - "host": "imp2.host.com", - }, - }, - "id": "imp_2", - }, - }, - }, - imps: []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - }, - }, - "id": "imp_1", - }, + requestNode: map[string]any{ + "id": "reqId", + "imps": []any{ map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 2222, - }, - "host": "imp2.host.com", - }, - }, - "id": "imp_2", + "id": "impId", }, }, }, }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{Location: "host"} - extMapper := bidderparams.BidderParamMapper{Location: "device"} - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "http:///publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"imp":[{"ext":{"bidder":{}},"id":"imp_1"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - { - Method: http.MethodPost, - Uri: "http://imp2.host.com/publisher/2222", - Body: json.RawMessage(`{"device":{"pubid":2222},"host":"imp2.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_2"}]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: nil, - }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - sreq := &multiRequestBuilder{ - requestBuilderImpl: tt.fields.requestBuilder, - } - requestData, errs := sreq.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) - assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + requstNode, err := cloneRequest(tt.args.request) + assert.Equal(t, tt.want.requestNode, requstNode, "mismatched requestnode") + assert.Equal(t, tt.want.err, err, "mismatched error") }) } } -func Test_multiRequestModeBuilder_makeRequest(t *testing.T) { - type fields struct { - requestBuilder *requestBuilderImpl - } +func TestAppendRequestData(t *testing.T) { type args struct { - endpointTemplate *template.Template - bidderParamMapper map[string]bidderparams.BidderParamMapper + requestData []*adapters.RequestData + request map[string]any + uri string } type want struct { - requestData []*adapters.RequestData - errs []error + reqData []*adapters.RequestData + err error } tests := []struct { - name string - fields fields - args args - want want + name string + args args + want want }{ { - name: "no_imp_object_in_builder", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{}`), - }, - }, + name: "append_request_data_to_nil_object", args: args{ - endpointTemplate: nil, - }, - want: want{ requestData: nil, - errs: nil, - }, - }, - { - name: "invalid_imp_object", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{"imp":["invalid"]}`), - imps: []any{"invalid"}, + request: map[string]any{ + "id": "reqId", }, + uri: "http://endpoint.com", }, - args: args{}, want: want{ - requestData: nil, - errs: []error{newBadInputError("invalid imp object found at index:0")}, + reqData: []*adapters.RequestData{{ + Method: http.MethodPost, + Uri: "http://endpoint.com", + Body: []byte(`{"id":"reqId"}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }}, }, }, { - name: "replace_macros_to_form_endpoint_url", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), - imps: []any{ - map[string]any{ - "id": "imp_1", - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 5890, - }, - "host": "localhost.com", - }, - }, - }, - }, - request: map[string]any{}, - hasMacrosInEndpoint: true, - }, - }, + name: "append_request_data_to_non_empty_object", args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - }, - want: want{ requestData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Uri: "http://endpoint.com", + Body: []byte(`{"id":"req_1"}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, }, }, - errs: nil, - }, - }, - { - name: "buildEndpoint_returns_error", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), - request: map[string]any{}, - imps: []any{ - map[string]any{ - "ext": map[string]any{}, - "id": "imp_1", - }, - }, - }, - }, - args: args{ - endpointTemplate: func() *template.Template { - errorFunc := template.FuncMap{ - "errorFunc": func() (string, error) { - return "", errors.New("intentional error") - }, - } - t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) - return t - }(), - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + - "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, - }, - }, - { - name: "map_bidder_params_in_multi_imp", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"},{"ext":{"bidder":{"tagid":"valid_tag_id"}},"id":"imp_2"}]}`), - request: map[string]any{}, - imps: []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 5890, - }, - "host": "localhost.com", - }, - }, - "id": "imp_1", - }, - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "tagid": "valid_tag_id", - }, - }, - "id": "imp_2", - }, - }, + request: map[string]any{ + "id": "req_2", }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{Location: "host"} - extMapper := bidderparams.BidderParamMapper{Location: "device"} - tagMapper := bidderparams.BidderParamMapper{Location: "imp.#.tagid"} - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - "tagid": tagMapper, - } - }(), + uri: "http://endpoint.com", }, want: want{ - requestData: []*adapters.RequestData{ + reqData: []*adapters.RequestData{ { Method: http.MethodPost, - Uri: "http://localhost.com/publisher/5890", - Body: json.RawMessage(`{"device":{"pubid":5890},"host":"localhost.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2","tagid":"valid_tag_id"}]}`), + Uri: "http://endpoint.com", + Body: []byte(`{"id":"req_1"}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, - }, - }, - errs: nil, - }, - }, - { - name: "multi_imps_request_with_one_invalid_imp", - fields: fields{ - requestBuilder: &requestBuilderImpl{ - hasMacrosInEndpoint: true, - rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},"invalid-imp"]}`), - request: map[string]any{}, - imps: []any{ - map[string]any{ - "ext": map[string]any{ - "bidder": map[string]any{ - "ext": map[string]any{ - "pubid": 1111, - }, - "host": "imp1.host.com", - }, - }, - "id": "imp_1", - }, - "invalid", - }, - }, - }, - args: args{ - endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), - bidderParamMapper: func() map[string]bidderparams.BidderParamMapper { - hostMapper := bidderparams.BidderParamMapper{Location: "host"} - extMapper := bidderparams.BidderParamMapper{Location: "device"} - tagMapper := bidderparams.BidderParamMapper{Location: "imp.#.tagid"} - return map[string]bidderparams.BidderParamMapper{ - "host": hostMapper, - "ext": extMapper, - "tagid": tagMapper, - } - }(), - }, - want: want{ - requestData: []*adapters.RequestData{ - { + }, { Method: http.MethodPost, - Uri: "http://imp1.host.com/publisher/1111", - Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},"invalid"]}`), + Uri: "http://endpoint.com", + Body: []byte(`{"id":"req_2"}`), Headers: http.Header{ "Content-Type": {"application/json;charset=utf-8"}, "Accept": {"application/json"}, }, - }, - }, - errs: []error{newBadInputError("invalid imp object found at index:1")}, + }}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - builder := &singleRequestBuilder{ - requestBuilderImpl: tt.fields.requestBuilder, - } - if builder.request != nil { - builder.request[impKey] = builder.imps - } - requestData, errs := builder.makeRequest(tt.args.endpointTemplate, tt.args.bidderParamMapper) - assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") - assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + got, err := appendRequestData(tt.args.requestData, tt.args.request, tt.args.uri) + assert.Equal(t, tt.want.reqData, got, "mismatched request-data") + assert.Equal(t, tt.want.err, err, "mismatched error") }) } } diff --git a/adapters/ortbbidder/singleRequestBuilder.go b/adapters/ortbbidder/singleRequestBuilder.go index 00255c7bf69..4556376644c 100644 --- a/adapters/ortbbidder/singleRequestBuilder.go +++ b/adapters/ortbbidder/singleRequestBuilder.go @@ -1,16 +1,18 @@ package ortbbidder import ( + "fmt" + "github.com/prebid/openrtb/v20/openrtb2" "github.com/prebid/prebid-server/v2/adapters" "github.com/prebid/prebid-server/v2/util/jsonutil" ) -// struct to build the request for multi request mode where single request supports multiple impressions +// struct to build the single request containing multi impressions when requestMode="multi" type singleRequestBuilder struct { requestBuilderImpl newRequest map[string]any - imps []map[string]any + imps []any } // parseRequest parse the incoming request and populates intermediate fields required for building requestData object @@ -25,10 +27,10 @@ func (rb *singleRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err return err } - imps, ok := rb.newRequest[impKey].([]map[string]any) - if !ok || len(imps) == 0 { - //TODO append error - return err + var ok bool + rb.imps, ok = rb.newRequest[impKey].([]any) + if !ok || len(rb.imps) == 0 { + return errImpMissing } return } @@ -37,17 +39,22 @@ func (rb *singleRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err // it create single RequestData object for all impressions. func (rb *singleRequestBuilder) makeRequest() (requestData []*adapters.RequestData, errs []error) { if len(rb.imps) == 0 { - return nil, errs + errs = append(errs, newBadInputError(errImpMissing.Error())) + return } var ( - endpoint string - newRequest map[string]any - err error + endpoint string + err error ) //step 1: get endpoint - if endpoint, err = rb.getEndpoint(getImpExtBidderParams(rb.imps[0])); err != nil { + imp, ok := rb.imps[0].(map[string]any) + if !ok { + errs = append(errs, newBadInputError("invalid imp found at index:0")) + return nil, errs + } + if endpoint, err = rb.getEndpoint(getImpExtBidderParams(imp)); err != nil { errs = append(errs, newBadInputError(err.Error())) return nil, errs } @@ -56,11 +63,16 @@ func (rb *singleRequestBuilder) makeRequest() (requestData []*adapters.RequestDa // iterate through imps in reverse order to ensure setRequestParams prioritizes // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. for index := len(rb.imps) - 1; index >= 0; index-- { - setRequestParams(newRequest, getImpExtBidderParams(rb.imps[index]), rb.requestParams, []int{index}) + imp, ok := rb.imps[index].(map[string]any) + if !ok { + errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp found at index:%d", index))) + continue // ignore particular impression + } + setRequestParams(rb.newRequest, getImpExtBidderParams(imp), rb.requestParams, []int{index}) } //step 3: append new request data - if requestData, err = appendRequestData(requestData, newRequest, endpoint); err != nil { + if requestData, err = appendRequestData(requestData, rb.newRequest, endpoint); err != nil { errs = append(errs, newBadInputError(err.Error())) } return requestData, errs diff --git a/adapters/ortbbidder/singleRequestBuilder_test.go b/adapters/ortbbidder/singleRequestBuilder_test.go new file mode 100644 index 00000000000..18190a31be4 --- /dev/null +++ b/adapters/ortbbidder/singleRequestBuilder_test.go @@ -0,0 +1,340 @@ +package ortbbidder + +import ( + "encoding/json" + "errors" + "net/http" + "testing" + "text/template" + + "github.com/prebid/openrtb/v20/openrtb2" + "github.com/prebid/prebid-server/v2/adapters" + "github.com/prebid/prebid-server/v2/adapters/ortbbidder/bidderparams" + "github.com/stretchr/testify/assert" +) + +func TestSingleRequestBuilderParseRequest(t *testing.T) { + type args struct { + request *openrtb2.BidRequest + } + type want struct { + err error + rawRequest json.RawMessage + newRequest map[string]any + imps []any + } + tests := []struct { + name string + args args + want want + }{ + { + name: "request_without_imps", + args: args{ + request: &openrtb2.BidRequest{ + ID: "id", + }, + }, + want: want{ + err: errImpMissing, + rawRequest: json.RawMessage(`{"id":"id","imp":null}`), + imps: nil, + newRequest: map[string]any{ + "id": "id", + "imp": nil, + }, + }, + }, + { + name: "request_is_valid", + args: args{ + request: &openrtb2.BidRequest{ + ID: "id", + Imp: []openrtb2.Imp{ + { + ID: "imp_1", + }, + }, + }, + }, + want: want{ + err: nil, + rawRequest: json.RawMessage(`{"id":"id","imp":[{"id":"imp_1"}]}`), + newRequest: map[string]any{ + "id": "id", + "imp": []any{map[string]any{ + "id": "imp_1", + }}, + }, + imps: []any{ + map[string]any{ + "id": "imp_1", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reqBuilder := &singleRequestBuilder{} + err := reqBuilder.parseRequest(tt.args.request) + assert.Equalf(t, tt.want.err, err, "mismatched error") + assert.Equalf(t, string(tt.want.rawRequest), string(reqBuilder.rawRequest), "mismatched rawRequest") + assert.Equalf(t, tt.want.imps, reqBuilder.imps, "mismatched imps") + assert.Equalf(t, tt.want.newRequest, reqBuilder.newRequest, "mismatched newRequest") + }) + } +} + +func TestSingleRequestBuilderMakeRequest(t *testing.T) { + type fields struct { + requestBuilder singleRequestBuilder + } + type want struct { + requestData []*adapters.RequestData + errs []error + } + tests := []struct { + name string + fields fields + want want + }{ + { + name: "no_imps", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + rawRequest: nil, + }, + imps: nil, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError(errImpMissing.Error())}, + }, + }, + { + name: "invalid_imp_object", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + rawRequest: nil, + }, + imps: []any{ + "invalid", + }, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("invalid imp found at index:0")}, + }, + }, + { + name: "replace_macros_to_form_endpoint_url", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + }, + newRequest: make(map[string]any), + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{"pubid": 5890}, + "host": "localhost.com", + }, + }, + "id": "imp_1", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com/publisher/5890", + Body: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":5890},"host":"localhost.com"}},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "macros_value_absent_in_bidder_params", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), + }, + newRequest: make(map[string]any), + imps: []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http:///publisher/", + Body: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "buildEndpoint_returns_error", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{},"id":"imp_1"}]}`), + endpointTemplate: func() *template.Template { + errorFunc := template.FuncMap{ + "errorFunc": func() (string, error) { + return "", errors.New("intentional error") + }, + } + t := template.Must(template.New("endpointTemplate").Funcs(errorFunc).Parse(`{{errorFunc}}`)) + return t + }(), + }, + newRequest: make(map[string]any), + imps: []any{ + map[string]any{ + "ext": map[string]any{}, + "id": "imp_1", + }, + }, + }, + }, + want: want{ + requestData: nil, + errs: []error{newBadInputError("failed to replace macros in endpoint, err:template: endpointTemplate:1:2: " + + "executing \"endpointTemplate\" at : error calling errorFunc: intentional error")}, + }, + }, + { + name: "multi_imps_request", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + hasMacrosInEndpoint: true, + rawRequest: json.RawMessage(`{"imp":[{"ext":{"bidder":{"ext":{"pubid":1111},"host":"imp1.host.com"}},"id":"imp_1"},{"ext":{"bidder":{"ext":{"pubid":2222},"host":"imp2.host.com"}},"id":"imp_2"}]}`), + endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), + requestParams: func() map[string]bidderparams.BidderParamMapper { + hostMapper := bidderparams.BidderParamMapper{Location: "host"} + extMapper := bidderparams.BidderParamMapper{Location: "device"} + return map[string]bidderparams.BidderParamMapper{ + "host": hostMapper, + "ext": extMapper, + } + }(), + }, + newRequest: make(map[string]any), + imps: []any{ + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 1111, + }, + "host": "imp1.host.com", + }, + }, + "id": "imp_1", + }, + map[string]any{ + "ext": map[string]any{ + "bidder": map[string]any{ + "ext": map[string]any{ + "pubid": 2222, + }, + "host": "imp2.host.com", + }, + }, + "id": "imp_2", + }, + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://imp1.host.com/publisher/1111", + Body: json.RawMessage(`{"device":{"pubid":1111},"host":"imp1.host.com","imp":[{"ext":{"bidder":{}},"id":"imp_1"},{"ext":{"bidder":{}},"id":"imp_2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: nil, + }, + }, + { + name: "multi_imps_request_with_one_invalid_imp_object", + fields: fields{ + requestBuilder: singleRequestBuilder{ + requestBuilderImpl: requestBuilderImpl{ + rawRequest: nil, + }, + newRequest: make(map[string]any), + imps: []any{ + map[string]any{ + "id": "imp_1", + }, + "invalid", + }, + }, + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "", + Body: json.RawMessage(`{"imp":[{"id":"imp_1"},"invalid"]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + errs: []error{newBadInputError("invalid imp found at index:1")}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + if tt.fields.requestBuilder.newRequest != nil { + tt.fields.requestBuilder.newRequest[impKey] = tt.fields.requestBuilder.imps + } + + requestData, errs := tt.fields.requestBuilder.makeRequest() + assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") + assert.Equalf(t, tt.want.errs, errs, "mismatched errs") + }) + } +} diff --git a/static/bidder-info/owortb_testbidder.yaml b/static/bidder-info/owortb_testbidder.yaml index a8175136b10..05e501aa50d 100644 --- a/static/bidder-info/owortb_testbidder.yaml +++ b/static/bidder-info/owortb_testbidder.yaml @@ -1,7 +1,7 @@ # sample bidder-info yaml for testbidder (oRTB Integration) maintainer: email: "header-bidding@pubmatic.com" -endpoint: "http://test.endpoint.com" +endpoint: "http://{{.host}}.endpoint.com/zone={{.zone}}" capabilities: app: mediaTypes: From 475224e021d501ddc11907eba8236f46fa8e5717 Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Tue, 11 Jun 2024 12:49:53 +0530 Subject: [PATCH 8/9] use constants for endpointTemplate --- adapters/ortbbidder/constant.go | 2 ++ adapters/ortbbidder/ortbbidder.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/adapters/ortbbidder/constant.go b/adapters/ortbbidder/constant.go index d464452adf6..3b56198636b 100644 --- a/adapters/ortbbidder/constant.go +++ b/adapters/ortbbidder/constant.go @@ -15,4 +15,6 @@ const ( urlMacroNoValue = "" requestModeSingle = "single" locationIndexMacro = "#" + endpointTemplate = "endpointTemplate" + templateOption = "missingkey=zero" ) diff --git a/adapters/ortbbidder/ortbbidder.go b/adapters/ortbbidder/ortbbidder.go index 2901d59efdd..223e855c248 100644 --- a/adapters/ortbbidder/ortbbidder.go +++ b/adapters/ortbbidder/ortbbidder.go @@ -49,7 +49,7 @@ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server co return nil, fmt.Errorf("failed to parse extra_info: %s", err.Error()) } } - template, err := template.New("endpointTemplate").Option("missingkey=zero").Parse(config.Endpoint) + template, err := template.New(endpointTemplate).Option(templateOption).Parse(config.Endpoint) if err != nil || template == nil { return nil, fmt.Errorf("failed to parse endpoint url template: %v", err) } From 5e3e429a6c5d1e6cbdc1f62b15e75a80082c0c8f Mon Sep 17 00:00:00 2001 From: "ashish.shinde" Date: Wed, 12 Jun 2024 09:09:27 +0530 Subject: [PATCH 9/9] cache imps in case of singleRequestBuilder --- adapters/ortbbidder/bidderparams/config.go | 11 ++- .../ortbbidder/bidderparams/config_test.go | 2 +- adapters/ortbbidder/bidderparams/parser.go | 4 +- adapters/ortbbidder/errors.go | 7 +- adapters/ortbbidder/ortbbidder_test.go | 59 +++++++++++++-- adapters/ortbbidder/singleRequestBuilder.go | 28 +++---- .../ortbbidder/singleRequestBuilder_test.go | 74 +++---------------- 7 files changed, 93 insertions(+), 92 deletions(-) diff --git a/adapters/ortbbidder/bidderparams/config.go b/adapters/ortbbidder/bidderparams/config.go index 36aa8282997..94f1f9ba9af 100644 --- a/adapters/ortbbidder/bidderparams/config.go +++ b/adapters/ortbbidder/bidderparams/config.go @@ -16,8 +16,15 @@ type BidderConfig struct { bidderConfigMap map[string]*config } -// setRequestParams sets the bidder specific requestParams -func (bcfg *BidderConfig) setRequestParams(bidderName string, requestParams map[string]BidderParamMapper) { +// NewBidderConfig initializes and returns the object of BidderConfig +func NewBidderConfig() *BidderConfig { + return &BidderConfig{ + bidderConfigMap: make(map[string]*config), + } +} + +// SetRequestParams sets the bidder specific requestParams +func (bcfg *BidderConfig) SetRequestParams(bidderName string, requestParams map[string]BidderParamMapper) { if _, found := bcfg.bidderConfigMap[bidderName]; !found { bcfg.bidderConfigMap[bidderName] = &config{} } diff --git a/adapters/ortbbidder/bidderparams/config_test.go b/adapters/ortbbidder/bidderparams/config_test.go index f4b80ee7843..2dd4b09c585 100644 --- a/adapters/ortbbidder/bidderparams/config_test.go +++ b/adapters/ortbbidder/bidderparams/config_test.go @@ -92,7 +92,7 @@ func TestSetRequestParams(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.fields.bidderConfig.setRequestParams(tt.args.bidderName, tt.args.requestParams) + tt.fields.bidderConfig.SetRequestParams(tt.args.bidderName, tt.args.requestParams) assert.Equal(t, tt.want.bidderCfg, tt.fields.bidderConfig, "mismatched bidderConfig") }) } diff --git a/adapters/ortbbidder/bidderparams/parser.go b/adapters/ortbbidder/bidderparams/parser.go index 3ed33f97070..278629608b7 100644 --- a/adapters/ortbbidder/bidderparams/parser.go +++ b/adapters/ortbbidder/bidderparams/parser.go @@ -19,7 +19,7 @@ func LoadBidderConfig(dirPath string, isBidderAllowed func(string) bool) (*Bidde if err != nil { return nil, fmt.Errorf("error:[%s] dirPath:[%s]", err.Error(), dirPath) } - bidderConfigMap := &BidderConfig{bidderConfigMap: make(map[string]*config)} + bidderConfigMap := NewBidderConfig() for _, file := range files { bidderName, ok := strings.CutSuffix(file.Name(), ".json") if !ok { @@ -36,7 +36,7 @@ func LoadBidderConfig(dirPath string, isBidderAllowed func(string) bool) (*Bidde if err != nil { return nil, err } - bidderConfigMap.setRequestParams(bidderName, requestParams) + bidderConfigMap.SetRequestParams(bidderName, requestParams) } return bidderConfigMap, nil } diff --git a/adapters/ortbbidder/errors.go b/adapters/ortbbidder/errors.go index b7528abdc10..d1866947a9e 100644 --- a/adapters/ortbbidder/errors.go +++ b/adapters/ortbbidder/errors.go @@ -2,6 +2,7 @@ package ortbbidder import ( "errors" + "fmt" "github.com/prebid/prebid-server/v2/errortypes" ) @@ -13,8 +14,8 @@ var ( ) // newBadInputError returns the error of type bad-input -func newBadInputError(message string) error { - return &errortypes.BadInput{ - Message: message, +func newBadInputError(message string, args ...any) error { + return &errortypes.BadServerResponse{ + Message: fmt.Sprintf(message, args...), } } diff --git a/adapters/ortbbidder/ortbbidder_test.go b/adapters/ortbbidder/ortbbidder_test.go index 51852955842..1b9397449bb 100644 --- a/adapters/ortbbidder/ortbbidder_test.go +++ b/adapters/ortbbidder/ortbbidder_test.go @@ -36,7 +36,7 @@ func TestMakeRequests(t *testing.T) { { name: "request_is_nil", args: args{ - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ errors: []error{newBadInputError(errImpMissing.Error())}, @@ -68,7 +68,7 @@ func TestMakeRequests(t *testing.T) { template, _ := template.New("endpointTemplate").Parse(endpoint) return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} }(), - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ requestData: []*adapters.RequestData{ @@ -100,7 +100,7 @@ func TestMakeRequests(t *testing.T) { template, _ := template.New("endpointTemplate").Parse(endpoint) return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} }(), - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ requestData: []*adapters.RequestData{ @@ -140,7 +140,7 @@ func TestMakeRequests(t *testing.T) { template, _ := template.New("endpointTemplate").Parse(endpoint) return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} }(), - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ requestData: []*adapters.RequestData{ @@ -180,7 +180,7 @@ func TestMakeRequests(t *testing.T) { template, _ := template.New("endpointTemplate").Parse(endpoint) return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} }(), - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ requestData: []*adapters.RequestData{ @@ -211,7 +211,7 @@ func TestMakeRequests(t *testing.T) { template, _ := template.New("endpointTemplate").Parse(endpoint) return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: ""}, "testbidder", template} }(), - bidderCfg: &bidderparams.BidderConfig{}, + bidderCfg: bidderparams.NewBidderConfig(), }, want: want{ requestData: []*adapters.RequestData{ @@ -227,6 +227,53 @@ func TestMakeRequests(t *testing.T) { }, }, }, + { + name: "single_requestmode_add_request_params_in_request", + args: args{ + request: &openrtb2.BidRequest{ + ID: "reqid", + Imp: []openrtb2.Imp{ + {ID: "imp1", TagID: "tag1", Ext: json.RawMessage(`{"bidder": {"host": "localhost.com"}}`)}, + {ID: "imp2", TagID: "tag2", Ext: json.RawMessage(`{"bidder": {"zone": "testZone"}}`)}, + }, + }, + adapterInfo: func() adapterInfo { + endpoint := "http://{{.host}}" + template, _ := template.New("endpointTemplate").Parse(endpoint) + return adapterInfo{config.Adapter{Endpoint: endpoint}, extraAdapterInfo{RequestMode: "single"}, "testbidder", template} + }(), + bidderCfg: func() *bidderparams.BidderConfig { + cfg := bidderparams.NewBidderConfig() + cfg.SetRequestParams("testbidder", map[string]bidderparams.BidderParamMapper{ + "host": {Location: "server.host"}, + "zone": {Location: "ext.zone"}, + }) + return cfg + }(), + }, + want: want{ + requestData: []*adapters.RequestData{ + { + Method: http.MethodPost, + Uri: "http://localhost.com", + Body: []byte(`{"id":"reqid","imp":[{"ext":{"bidder":{}},"id":"imp1","tagid":"tag1"}],"server":{"host":"localhost.com"}}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + { + Method: http.MethodPost, + Uri: "http://", + Body: []byte(`{"ext":{"zone":"testZone"},"id":"reqid","imp":[{"ext":{"bidder":{}},"id":"imp2","tagid":"tag2"}]}`), + Headers: http.Header{ + "Content-Type": {"application/json;charset=utf-8"}, + "Accept": {"application/json"}, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/adapters/ortbbidder/singleRequestBuilder.go b/adapters/ortbbidder/singleRequestBuilder.go index 4556376644c..cf0fb79ed0e 100644 --- a/adapters/ortbbidder/singleRequestBuilder.go +++ b/adapters/ortbbidder/singleRequestBuilder.go @@ -12,7 +12,7 @@ import ( type singleRequestBuilder struct { requestBuilderImpl newRequest map[string]any - imps []any + imps []map[string]any } // parseRequest parse the incoming request and populates intermediate fields required for building requestData object @@ -27,11 +27,17 @@ func (rb *singleRequestBuilder) parseRequest(request *openrtb2.BidRequest) (err return err } - var ok bool - rb.imps, ok = rb.newRequest[impKey].([]any) - if !ok || len(rb.imps) == 0 { + imps, ok := rb.newRequest[impKey].([]any) + if !ok { return errImpMissing } + for index, imp := range imps { + imp, ok := imp.(map[string]any) + if !ok { + return fmt.Errorf("invalid imp found at index:%d", index) + } + rb.imps = append(rb.imps, imp) + } return } @@ -49,12 +55,7 @@ func (rb *singleRequestBuilder) makeRequest() (requestData []*adapters.RequestDa ) //step 1: get endpoint - imp, ok := rb.imps[0].(map[string]any) - if !ok { - errs = append(errs, newBadInputError("invalid imp found at index:0")) - return nil, errs - } - if endpoint, err = rb.getEndpoint(getImpExtBidderParams(imp)); err != nil { + if endpoint, err = rb.getEndpoint(getImpExtBidderParams(rb.imps[0])); err != nil { errs = append(errs, newBadInputError(err.Error())) return nil, errs } @@ -63,12 +64,7 @@ func (rb *singleRequestBuilder) makeRequest() (requestData []*adapters.RequestDa // iterate through imps in reverse order to ensure setRequestParams prioritizes // the parameters from imp[0].ext.bidder over those from imp[1..N].ext.bidder. for index := len(rb.imps) - 1; index >= 0; index-- { - imp, ok := rb.imps[index].(map[string]any) - if !ok { - errs = append(errs, newBadInputError(fmt.Sprintf("invalid imp found at index:%d", index))) - continue // ignore particular impression - } - setRequestParams(rb.newRequest, getImpExtBidderParams(imp), rb.requestParams, []int{index}) + setRequestParams(rb.newRequest, getImpExtBidderParams(rb.imps[index]), rb.requestParams, []int{index}) } //step 3: append new request data diff --git a/adapters/ortbbidder/singleRequestBuilder_test.go b/adapters/ortbbidder/singleRequestBuilder_test.go index 18190a31be4..50e02ddeea1 100644 --- a/adapters/ortbbidder/singleRequestBuilder_test.go +++ b/adapters/ortbbidder/singleRequestBuilder_test.go @@ -21,7 +21,7 @@ func TestSingleRequestBuilderParseRequest(t *testing.T) { err error rawRequest json.RawMessage newRequest map[string]any - imps []any + imps []map[string]any } tests := []struct { name string @@ -66,8 +66,8 @@ func TestSingleRequestBuilderParseRequest(t *testing.T) { "id": "imp_1", }}, }, - imps: []any{ - map[string]any{ + imps: []map[string]any{ + { "id": "imp_1", }, }, @@ -114,23 +114,6 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { errs: []error{newBadInputError(errImpMissing.Error())}, }, }, - { - name: "invalid_imp_object", - fields: fields{ - requestBuilder: singleRequestBuilder{ - requestBuilderImpl: requestBuilderImpl{ - rawRequest: nil, - }, - imps: []any{ - "invalid", - }, - }, - }, - want: want{ - requestData: nil, - errs: []error{newBadInputError("invalid imp found at index:0")}, - }, - }, { name: "replace_macros_to_form_endpoint_url", fields: fields{ @@ -141,8 +124,8 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { endpointTemplate: template.Must(template.New("endpointTemplate").Parse(`http://{{.host}}/publisher/{{.ext.pubid}}`)), }, newRequest: make(map[string]any), - imps: []any{ - map[string]any{ + imps: []map[string]any{ + { "ext": map[string]any{ "bidder": map[string]any{ "ext": map[string]any{"pubid": 5890}, @@ -179,8 +162,8 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { endpointTemplate: template.Must(template.New("endpointTemplate").Option("missingkey=default").Parse(`http://{{.host}}/publisher/{{.pubid}}`)), }, newRequest: make(map[string]any), - imps: []any{ - map[string]any{ + imps: []map[string]any{ + { "ext": map[string]any{}, "id": "imp_1", }, @@ -220,8 +203,8 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { }(), }, newRequest: make(map[string]any), - imps: []any{ - map[string]any{ + imps: []map[string]any{ + { "ext": map[string]any{}, "id": "imp_1", }, @@ -252,8 +235,8 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { }(), }, newRequest: make(map[string]any), - imps: []any{ - map[string]any{ + imps: []map[string]any{ + { "ext": map[string]any{ "bidder": map[string]any{ "ext": map[string]any{ @@ -264,7 +247,7 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { }, "id": "imp_1", }, - map[string]any{ + { "ext": map[string]any{ "bidder": map[string]any{ "ext": map[string]any{ @@ -293,45 +276,12 @@ func TestSingleRequestBuilderMakeRequest(t *testing.T) { errs: nil, }, }, - { - name: "multi_imps_request_with_one_invalid_imp_object", - fields: fields{ - requestBuilder: singleRequestBuilder{ - requestBuilderImpl: requestBuilderImpl{ - rawRequest: nil, - }, - newRequest: make(map[string]any), - imps: []any{ - map[string]any{ - "id": "imp_1", - }, - "invalid", - }, - }, - }, - want: want{ - requestData: []*adapters.RequestData{ - { - Method: http.MethodPost, - Uri: "", - Body: json.RawMessage(`{"imp":[{"id":"imp_1"},"invalid"]}`), - Headers: http.Header{ - "Content-Type": {"application/json;charset=utf-8"}, - "Accept": {"application/json"}, - }, - }, - }, - errs: []error{newBadInputError("invalid imp found at index:1")}, - }, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.fields.requestBuilder.newRequest != nil { tt.fields.requestBuilder.newRequest[impKey] = tt.fields.requestBuilder.imps } - requestData, errs := tt.fields.requestBuilder.makeRequest() assert.Equalf(t, tt.want.requestData, requestData, "mismatched requestData") assert.Equalf(t, tt.want.errs, errs, "mismatched errs")