Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GPID Reserved Bidder Name #2107

Merged
merged 8 commits into from
Dec 16, 2021
Merged

Add GPID Reserved Bidder Name #2107

merged 8 commits into from
Dec 16, 2021

Conversation

SyntaxNode
Copy link
Contributor

Implements #2088

I needed to refactor existing code for handling bidderparams due to a parsing problem it introduced. That code failed if any field in imp.ext is not an embedded object type, which is a problem since imp.ext.gpid is a string. Since this is code is upstream from validation (which makes sense as it could cause an invalid request to become valid) it should not "step on the toes" of the validator. If there is a problem with the request, we should let the validate method continue to handle validation issues.

"github.com/mxmCherry/openrtb/v15/openrtb2"
"github.com/prebid/prebid-server/openrtb_ext"
)

func ExtractAdapterReqBidderParams(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) {
func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Provided a more precise name to better align with the other method in this file.

func TestExtractAdapterReqBidderParams(t *testing.T) {
type args struct {
bidRequest *openrtb2.BidRequest
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactors to remove the args object, since it only contains a single argument I don't see the need for a separate structure.

name: "nil req",
givenBidRequest: nil,
want: nil,
wantErr: errors.New("error bidRequest should not be nil"),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to compare the actual error message rather than verify the count of errors.

}

if len(reqBidderParams) == 0 {
return nil
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimization to avoid the expensive operations of the loop if there are no bidderparams to apply.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Do you think checking err != nil and len(reqBidderParams) == 0 separately make for a better read? Or can we compact?

338   func mergeBidderParams(req *openrtb_ext.RequestWrapper) error {
339       reqBidderParams, err := adapters.ExtractReqExtBidderParamsEmbeddedMap(req.BidRequest)
340 -     if err != nil {
    +     if err != nil || len(reqBidderParams) == 0 {
341           return nil
342       }
343 - 
344 -     if len(reqBidderParams) == 0 {
345 -         return nil
346 -     }
347   
endpoints/openrtb2/auction.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no preference. I can combine them if you prefer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine. No need to hold this PR over this.

iExt, err := json.Marshal(impExt)
if err != nil {
return fmt.Errorf("error marshalling imp[].ext : %s", err.Error())
if impExtModified || impExtPrebidModified {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slight optimization to avoid marshaling if no changes were made, such as the edge case when all values are more specifically defined in imp.ext.

case openrtb_ext.BidderReservedPrebid:
return false
case openrtb_ext.BidderReservedSKAdN:
return false
case openrtb_ext.BidderReservedBidder:
return false
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bidderparams feature does not make special use of "bidder", so I don't see why it needed to add this reserved bidder name. Perhaps it did in an earlier version? Removed.

@@ -1146,6 +1146,301 @@ func TestStoredRequests(t *testing.T) {
}
}

func TestMergeBidderParams(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added extensive unit tests for the merge methods. I left the json based integration tests alone.

@@ -27,5 +27,5 @@
}
},
"expectedReturnCode": 400,
"expectedErrorMessage": "Invalid request: error decoding Request.ext : request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
"expectedErrorMessage": "Invalid request: request.ext is invalid: request.ext.prebid.cache requires one of the \"bids\" or \"vastxml\" properties\n"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted. The bidderparams feature should not alter the validation flow.

@@ -20,5 +20,5 @@
}
},
"expectedReturnCode": 400,
"expectedErrorMessage": "Invalid request: json: cannot unmarshal string into Go value of type map[string]json.RawMessage"
"expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted. The bidderparams feature should not alter the validation flow.

@@ -104,15 +104,15 @@ func TestIsBidderNameReserved(t *testing.T) {
{"general", true},
{"gEnErAl", true},
{"GENERAL", true},
{"skadn", true},
{"skADN", true},
{"SKADN", true},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ordered alphabetically.

},
expectedImpExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedImpExt"),
expectedReqExt: getObject(t, "req-ext-bidder-params-merge-reserved-keyword.json", "expectedReqExt"),
errL: 0,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. I don't understand the use case of a reserved bidder keyword and I didn't see mention of special handling in the issue. Please correct me if I missed something.

Copy link
Contributor

@guscarreon guscarreon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some questions below

}

if len(reqBidderParams) == 0 {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Do you think checking err != nil and len(reqBidderParams) == 0 separately make for a better read? Or can we compact?

338   func mergeBidderParams(req *openrtb_ext.RequestWrapper) error {
339       reqBidderParams, err := adapters.ExtractReqExtBidderParamsEmbeddedMap(req.BidRequest)
340 -     if err != nil {
    +     if err != nil || len(reqBidderParams) == 0 {
341           return nil
342       }
343 - 
344 -     if len(reqBidderParams) == 0 {
345 -         return nil
346 -     }
347   
endpoints/openrtb2/auction.go

if err := json.Unmarshal(bidderExt, &params); err != nil {
// bidder should be parsable, but we haven't validated yet so there may be another issue. ignore it here
// and let the validation later on catch it.
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I'm wrong, but by unmarshaling to a map[string]json.RawMessage in line 357 inside mergeBidderParams(req *openrtb_ext.RequestWrapper) we are effectively moving that additional json.Unmarshal call down here, right? Are we doing this to filter out the malformed imp[].ext fields and salvage the good ones? If so, I think downstream the entire imp gets discarded anyways, even if only one of the entries is found to not pass validation:

1152     for bidder, ext := range bidderExts {
1153         if isBidderToValidate(bidder) {
1154             coreBidder := bidder
1155             if tmp, isAlias := aliases[bidder]; isAlias {
1156                 coreBidder = tmp
1157             }
1158             if bidderName, isValid := deps.bidderMap[coreBidder]; isValid {
1159                 if err := deps.paramsValidator.Validate(bidderName, ext); err != nil { 
1160 /*DISCARDS IMP-->*/ return []error{fmt.Errorf("request.imp[%d].ext.%s failed validation.\n%v", impIndex, coreBidder, err)}
1161                 }
1162             } else {
1163                 if msg, isDisabled := deps.disabledBidders[bidder]; isDisabled {
1164                     errL = append(errL, &errortypes.BidderTemporarilyDisabled{Message: msg})
1165                     disabledBidders = append(disabledBidders, bidder)
1166                 } else {
1167                     return []error{fmt.Errorf("request.imp[%d].ext contains unknown bidder: %s. Did you forget an alias in request.ext.prebid.aliases?", impIndex, bidder)}
1168                 }
1169             }
1170         } else {
1171             otherExtElements++
1172         }
1173     }
endpoints/openrtb2/auction.go

We can verify this behavior if we slightly modify endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json to include a valid bidder ext. Notice how the test still passes, in other words, the entire imp[0] element gets discarded:

 1   {
 2     "description": "Bid request's sole imp element has invalid ext value",
 3     "mockBidRequest": {
 4       "id": "req-id",
 5       "imp": [
 6         {
 7           "id": "imp-id",
 8           "audio": {
 9             "mimes": [
10               "video/mp4"
11               ]
12           },
13           "ext": {
   +           "aja": {
   +             "asi": "test-asi"
   +           },
14             "appnexus": "invalidParams"
15           }
16         }
17       ],
18       "app": {
19         "id": "app_001"
20       }
21     },
22     "expectedReturnCode": 400,
23     "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n"
24   }
endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json

If, on the other hand, the test is modified so the "aja" entry fails before the "appnexus" one does, the test does not pass because the error message mentions aja and not appnexus:

 1   {
 2     "description": "Bid request's sole imp element has invalid ext value",
 3     "mockBidRequest": {
 4       "id": "req-id",
 5       "imp": [
 6         {
 7           "id": "imp-id",
 8           "audio": {
 9             "mimes": [
10               "video/mp4"
11               ]
12           },
13           "ext": {
   +           "aja": "malformed",
14             "appnexus": "invalidParams"
15           }
16         }
17       ],
18       "app": {
19         "id": "app_001"
20       }
21     },
22     "expectedReturnCode": 400,
23     "expectedErrorMessage": "Invalid request: request.imp[0].ext.appnexus failed validation.\n(root): Invalid type. Expected: object, given: string\n"
24   }
endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json

Test failure message:

endpoints/openrtb2/auction_test.go|132| 
|| 	Error Trace:	auction_test.go:169
|| 	            				auction_test.go:132
|| 	Error:      	Should be true
|| 	Test:       	TestJsonSampleRequests
|| 	Messages:   	Actual: Invalid request: request.imp[0].ext.aja failed validation.
|| 	            	(root): Invalid type. Expected: object, given: string
|| 	            	 
|| 	            	Expected: Invalid request: request.imp[0].ext.appnexus failed validation.
|| 	            	(root): Invalid type. Expected: object, given: string
|| 	            	. Filename: sample-requests/invalid-whole/imp-ext-invalid-params.json 

If at the end we are going to fail fast anyways, should go back to unmarshaling the entire thing in mergeBidderParams(req *openrtb_ext.RequestWrapper)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we doing this to filter out the malformed imp[].ext fields and salvage the good ones?

No. There is no validation intended or performed by the merge method. The map[string]map[string]json.RawMessage is not a valid data structure for imp.ext since it forces all fields to be an object or fails to parse. This PR adds support for imp.ext.gpid which is a string. That's the reason for the refactor.

should go back to unmarshaling the entire thing in mergeBidderParams(req *openrtb_ext.RequestWrapper)?

We cannot. If we do, then imp.ext.gpid will always cause a validation error in mergeBidderParams.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see

}

//merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
impExtPrebidModified, err := addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that request.imp.ext.prebid.bidder.BIDDER takes precedence over request.imp.ext.BIDDER, does this become an "either-or" situation? If a given imp.ext comes with a prebid field, should we only call addMissingReqExtParamsInImpExtPrebid and not both addMissingReqExtParamsInImpExtPrebid and addMissingReqExtParamsInImpExt? If the imp.exts that come with a prebid field will ignore request.imp.ext.BIDDER values anyways, maybe it'd be a desirable optimization to not copy them to request.imp.ext.BIDDER at all.

1124 func (deps *endpointDeps) validateImpExt(imp *openrtb2.Imp, aliases map[string]string, impIndex int) []error {
1125     errL := []error{}
            .
            .
1134
1135     // Prefer bidder params from request.imp.ext.prebid.bidder.BIDDER over request.imp.ext.BIDDER
1136     // to avoid confusion beteween prebid specific adapter config and other ext protocols.
1137     if extPrebidJSON, ok := bidderExts[openrtb_ext.PrebidExtKey]; ok {
1138         var extPrebid openrtb_ext.ExtImpPrebid
1139         if err := json.Unmarshal(extPrebidJSON, &extPrebid); err == nil && extPrebid.Bidder != nil {
1140             for bidder, ext := range extPrebid.Bidder {
1141                 if ext == nil {
1142                     continue
1143                 }
1144                 bidderExts[bidder] = ext
1145             }
1146         }
1147     }
1148
1149     /* Process all the bidder exts in the request */
endpoints/openrtb2/auction.go

If implemented, the change would make mergeBidderParams behave differently, and a test case like the one shown below would expect a different outcome:

1149   func TestMergeBidderParams(t *testing.T) {
1150       testCases := []struct {
1151           description         string
1152           givenRequest        openrtb2.BidRequest
1153           expectedRequestImps []openrtb2.Imp
1154       }{
1155   *- 55 lines: {-------------------------------------------------------------------------------
1210           {
1211 -             description: "One Imp - imp.ext + imp.ext.prebid.bidder Modified - Same Bidder",
     +             description: "One Imp - modify imp.ext.prebid.bidder ONLY - Same Bidder",
1212               givenRequest: openrtb2.BidRequest{
1213                   Imp: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one"}}}}`)}},
1214                   Ext: json.RawMessage(`{"prebid":{"bidderparams":{"bidder1":{"b":2}}}}`),
1215               },
1216 -             expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1,"b":2},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)}},
     +             expectedRequestImps: []openrtb2.Imp{{ID: "1", Ext: json.RawMessage(`{"bidder1":{"a":1},"prebid":{"bidder":{"bidder1":{"a":"one","b":2}}}}`)}},
1217           },
endpoints/openrtb2/auction_test.go

Unless I'm missing something and those values are still needed in request.imp.ext.BIDDER. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that request.imp.ext.prebid.bidder.BIDDER takes precedence over request.imp.ext.BIDDER, does this become an "either-or" situation?

Yes. Later code will pick one or the other, but not both. Referencing the precedence logic here would increase it's complexity for IMHO minimal processing savings.

I'm not sure why it was designed this way, but separating the merging pf params from the decision on which params to use seems like a good isolation of responsibilities.

This is pre-existing behavior. The reason I didn't refactor that in this PR is that it's not required for imp.ext.gpid to function, which is the goal of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. It can be refactored separately

guscarreon
guscarreon previously approved these changes Dec 14, 2021
Copy link
Contributor

@guscarreon guscarreon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

}

if len(reqBidderParams) == 0 {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fine. No need to hold this PR over this.

}

//merges bidder parameters passed at req.ext level with imp[].ext.prebid.bidder level.
err = addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
impExtPrebidModified, err := addMissingReqExtParamsInImpExtPrebid(impExt, reqBidderParams)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense. It can be refactored separately

if err := json.Unmarshal(bidderExt, &params); err != nil {
// bidder should be parsable, but we haven't validated yet so there may be another issue. ignore it here
// and let the validation later on catch it.
continue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see

@mansinahar mansinahar merged commit a60dfe2 into prebid:master Dec 16, 2021
wwwyyy pushed a commit to wwwyyy/prebid-server that referenced this pull request Dec 20, 2021
* 'master' of https://github.com/wwwyyy/prebid-server: (23 commits)
  Add GPID Reserved Bidder Name (prebid#2107)
  Marsmedia adapter - param zoneId can get string and int (prebid#2105)
  Algorix: add Server Region Support (prebid#2096)
  New Adapter: Bizzclick (prebid#2056)
  Collect Prometheus Go process metrics (prebid#2101)
  Added channel support (prebid#2037)
  Rubicon: update fpd fields resolving (prebid#2091)
  Beachfront - Currency conversion (prebid#2078)
  New Adapter: Groupm (Alias Of PubMatic) (prebid#2042)
  Adform adapter lacked gross/net parameter support (prebid#2084)
  add iframe sync for openx (prebid#2094)
  changes to the correct user sync url (prebid#2092)
  Smaato: Add instl to tests (prebid#2089)
  Correct MakeRequests() output assertion in adapter JSON tests (prebid#2087)
  Adview: adapter currency fix. (prebid#2060)
  New Adapter: Coinzilla (prebid#2074)
  Syncer Level ExternalURL Override (prebid#2072)
  33Across: Enable Support for SRA requests (prebid#2079)
  Currency conversion on adapter JSON tests (prebid#2071)
  New Adapter: VideoByte (prebid#2058)
  ...
@SyntaxNode SyntaxNode deleted the gpid branch March 21, 2022 20:30
jorgeluisrocha pushed a commit to jwplayer/prebid-server that referenced this pull request Sep 28, 2022
shunj-nb pushed a commit to ParticleMedia/prebid-server that referenced this pull request Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants