Prebid.js icon indicating copy to clipboard operation
Prebid.js copied to clipboard

Proposal: pbjs-ortb conversion library

Open dgirardi opened this issue 3 years ago • 0 comments

Type of issue

Feature

Description

There is an increasing number of adapters that interface with ORTB backends. Currently, each needs to replicate logic to convert PBJS bid objects to ORTB and back again; a dialect of that logic is also implemented in the PBS adapter.

We should extract that into one canonical implementation, and make it trivial to add a new adapter if it talks to an ORTB backend.

Proposal

  1. Move the enrichmentFpdModule logic into core and extend it to populate any ortb2 field that is not specific to the impression or bidder (for example, device.ua). Currently this is left to each individual adapter, but they rarely should have a reason to differ in their implementation. This is true for "traditional" (non-ORTB) adapters as well; consolidating these items into first-party data should allow some simplification in that regard too.

  2. Implement utilities bids2ortb and ortb2bids such that an adapter may use them in this manner:

  buildRequests(bidRequests, bidderRequest) {
      return {         
           method: ...,
           url: ...,
           data: bids2ortb({bidderRequest})           
      }
  },
  interpretResponse(response, request) {
      return ortb2bids({response: response.body, request: request.data})
  }

these utilities should handle everything that the PBS adapter now understands, namely:

  • banner, video, and native mediatypes
  • coppa / gdpr / usp
  • price floors
  • multibid
  • schain
  • gpid / pbadslot
  • any others?

Adapter-specific customization

Current ortb bidders are surprisingly similar, but not identical. Adapters are free to modify the return value they get from bids2ortb / ortb2bids, but that pattern is not ideal for some cases.

Bid filtering

The Rubicon adapter wants to create a separate ortb request for each one in a set of particular bids: https://github.com/prebid/Prebid.js/blob/48ad7190decf77cdda00827b38f334856eff4de8/modules/rubiconBidAdapter.js#L179-L183

Proposed solution - accept an optional list of bid requests in bids2ortb:

  const videoRequests = bidRequests.filter(bidRequest => bidType(bidRequest) === 'video')
         .map(bidRequest => bids2ortb({bidderRequest, bidRequests: [bidRequest]})

Bid-level parameters

For example, from the ADF adapter:

https://github.com/prebid/Prebid.js/blob/f7dca5b3d5be74855f3d466776a02ff13b74831e/modules/adfBidAdapter.js#L107-L120

Proposed solution - provide an imp-level override in bids2ortb:

bids2ortb({
   bidderRequest, 
   imp: function(bid2imp, bidRequest) {
        const { mid, inv, mname } = bidRequest.params;
        return mergeDeep(bid2Imp({bidRequest}), {           
           tagid: mid,
           ext: {
              bidder: {
                inv,
                mname                 
              }
           }
        });
   }
});

The same idea can be applied on the response side - for example ADF's video renderer:

https://github.com/prebid/Prebid.js/blob/48ad7190decf77cdda00827b38f334856eff4de8/modules/adfBidAdapter.js#L272-L275

could look like:

ortb2bids({
    request,
    response,
    bid: function(imp2bid, imp, bidRequest) {
         const bid = imp2bid({imp});
         if (bidRequest.mediaType === VIDEO && deepAccess(bidRequest, 'mediaTypes.video.context') === 'outstream') {
           bid.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL, adUnitCode: bid.adUnitCode});
           bid.renderer.setRender(renderer);              
         }
         return bid;
    }
})

Feature override

Because it makes practical sense to separate the ortb implementation of individual features (e.g. bidfloor should only be populated if the priceFloors module is installed), it would be relatively straightforward to allow adapters to override their translation. I have not found a great use case for this yet but I'll use the OpenXNew adapter as inspiration:

https://github.com/prebid/Prebid.js/blob/df48483e0e122a635884c2ca763db48487fbf185/modules/openxOrtbBidAdapter.js#L90-L95

Suppose that logic was changed to say "if bid.params.customFloor is defined, use that instead of the 'real' floor". We could allow that with something like:

bids2ortb({
   bidderRequest,
   impOverride: {
      bidfloor: function(applyOriginal, imp, bidRequest) {
              if (bidRequest.params.customFloor) {
                   Object.assign(imp, {
                        bidfloor: bidRequest.params.customFloor,
                        bidfloorcur: 'USD'                        
                   })
              } else {
                applyOriginal(imp, bidRequest);
              }
       }
   }
})

which would mean "override the ortb translation logic for the bidfloor feature with this custom logic instead". The difference with simply overwriting the contents of imp like we did in the previous example is that:

  • the original logic would not run at all - which could conceivably help performance if the feature is complex enough;
  • related fields are grouped together under a single feature name, which allows adapters to treat them as a logical entity without worrying about changes in core. For example an adapter that is not interested in GPDR could disable it with:
    return bids2ortb({
      bidderRequest
      override: {
        gdpr: function(){}
      }
    })
    
    instead of learning what fields are currently set by core for gdpr and listing them like so:
    const request = bids2ortb({bidderRequest});
    delete request.regs?.ext?.gdpr;
    delete request.user?.ext?.consent;
    delete request.user?.ext?.ConsentedProvidersSettings?.consented_providers;
    return request;
    

dgirardi avatar Jun 14 '22 23:06 dgirardi